-
Notifications
You must be signed in to change notification settings - Fork 5k
Added snake and kebab naming policies to JSON serializer #69613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
acc2a0b
Added snake and kebab naming policies to JSON serializer
YohDeadfall 3b0ff1b
Code styling issues
YohDeadfall 748b947
Explicit types
YohDeadfall 762f819
Fixed range slicing issue
YohDeadfall 191eace
Fixed tests
YohDeadfall 9294107
Forgotten conversion in tests
YohDeadfall 076877c
Fixed docs
YohDeadfall 927fac1
Used nameof instead of hardcoded names in source generator
YohDeadfall 6afee1e
Updated public API
YohDeadfall f39065b
Fixed kebab case lower policy
YohDeadfall 23f5d46
Added tests for long inputs
YohDeadfall b694e22
Performance improvements
YohDeadfall 393ddb5
Made ConvertName sealed
YohDeadfall ef7524d
Explicit variable type
YohDeadfall ead9f87
Clear only a dirty part of the buffer
YohDeadfall c11d048
Fixed exception on slicing more that exists
YohDeadfall f9d3bed
Better variable name
YohDeadfall febb3c4
End-to-end serialization tests
YohDeadfall File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/libraries/System.Text.Json/Common/JsonKebabCaseLowerNamingPolicy.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace System.Text.Json | ||
{ | ||
internal sealed class JsonKebabCaseLowerNamingPolicy : JsonSeparatorNamingPolicy | ||
{ | ||
public JsonKebabCaseLowerNamingPolicy() | ||
: base(lowercase: true, separator: '-') | ||
{ | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/libraries/System.Text.Json/Common/JsonKebabCaseUpperNamingPolicy.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace System.Text.Json | ||
{ | ||
internal sealed class JsonKebabCaseUpperNamingPolicy : JsonSeparatorNamingPolicy | ||
{ | ||
public JsonKebabCaseUpperNamingPolicy() | ||
: base(lowercase: false, separator: '-') | ||
{ | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Buffers; | ||
using System.Globalization; | ||
|
||
namespace System.Text.Json | ||
{ | ||
internal abstract class JsonSeparatorNamingPolicy : JsonNamingPolicy | ||
{ | ||
private readonly bool _lowercase; | ||
private readonly char _separator; | ||
|
||
internal JsonSeparatorNamingPolicy(bool lowercase, char separator) => | ||
(_lowercase, _separator) = (lowercase, separator); | ||
|
||
public sealed override string ConvertName(string name) | ||
{ | ||
// Rented buffer 20% longer that the input. | ||
int rentedBufferLength = (12 * name.Length) / 10; | ||
char[]? rentedBuffer = rentedBufferLength > JsonConstants.StackallocCharThreshold | ||
? ArrayPool<char>.Shared.Rent(rentedBufferLength) | ||
: null; | ||
|
||
int resultUsedLength = 0; | ||
Span<char> result = rentedBuffer is null | ||
? stackalloc char[JsonConstants.StackallocCharThreshold] | ||
: rentedBuffer; | ||
|
||
void ExpandBuffer(ref Span<char> result) | ||
{ | ||
char[] newBuffer = ArrayPool<char>.Shared.Rent(result.Length * 2); | ||
|
||
result.CopyTo(newBuffer); | ||
|
||
if (rentedBuffer is not null) | ||
{ | ||
result.Slice(0, resultUsedLength).Clear(); | ||
ArrayPool<char>.Shared.Return(rentedBuffer); | ||
} | ||
|
||
rentedBuffer = newBuffer; | ||
result = rentedBuffer; | ||
} | ||
|
||
void WriteWord(ReadOnlySpan<char> word, ref Span<char> result) | ||
{ | ||
if (word.IsEmpty) | ||
{ | ||
return; | ||
} | ||
|
||
int written; | ||
while (true) | ||
{ | ||
var destinationOffset = resultUsedLength != 0 | ||
? resultUsedLength + 1 | ||
: resultUsedLength; | ||
|
||
if (destinationOffset < result.Length) | ||
{ | ||
Span<char> destination = result.Slice(destinationOffset); | ||
|
||
written = _lowercase | ||
? word.ToLowerInvariant(destination) | ||
: word.ToUpperInvariant(destination); | ||
|
||
if (written > 0) | ||
{ | ||
break; | ||
} | ||
} | ||
|
||
ExpandBuffer(ref result); | ||
} | ||
|
||
if (resultUsedLength != 0) | ||
{ | ||
result[resultUsedLength] = _separator; | ||
resultUsedLength += 1; | ||
} | ||
|
||
resultUsedLength += written; | ||
} | ||
|
||
int first = 0; | ||
ReadOnlySpan<char> chars = name.AsSpan(); | ||
CharCategory previousCategory = CharCategory.Boundary; | ||
|
||
for (int index = 0; index < chars.Length; index++) | ||
{ | ||
char current = chars[index]; | ||
UnicodeCategory currentCategoryUnicode = char.GetUnicodeCategory(current); | ||
|
||
if (currentCategoryUnicode == UnicodeCategory.SpaceSeparator || | ||
currentCategoryUnicode >= UnicodeCategory.ConnectorPunctuation && | ||
currentCategoryUnicode <= UnicodeCategory.OtherPunctuation) | ||
{ | ||
WriteWord(chars.Slice(first, index - first), ref result); | ||
|
||
previousCategory = CharCategory.Boundary; | ||
first = index + 1; | ||
|
||
continue; | ||
} | ||
|
||
if (index + 1 < chars.Length) | ||
{ | ||
char next = chars[index + 1]; | ||
CharCategory currentCategory = currentCategoryUnicode switch | ||
{ | ||
UnicodeCategory.LowercaseLetter => CharCategory.Lowercase, | ||
UnicodeCategory.UppercaseLetter => CharCategory.Uppercase, | ||
_ => previousCategory | ||
}; | ||
|
||
if (currentCategory == CharCategory.Lowercase && char.IsUpper(next) || | ||
next == '_') | ||
{ | ||
WriteWord(chars.Slice(first, index - first + 1), ref result); | ||
|
||
previousCategory = CharCategory.Boundary; | ||
first = index + 1; | ||
|
||
continue; | ||
} | ||
|
||
if (previousCategory == CharCategory.Uppercase && | ||
currentCategoryUnicode == UnicodeCategory.UppercaseLetter && | ||
char.IsLower(next)) | ||
{ | ||
WriteWord(chars.Slice(first, index - first), ref result); | ||
|
||
previousCategory = CharCategory.Boundary; | ||
first = index; | ||
|
||
continue; | ||
} | ||
|
||
previousCategory = currentCategory; | ||
} | ||
} | ||
|
||
WriteWord(chars.Slice(first), ref result); | ||
|
||
name = result.Slice(0, resultUsedLength).ToString(); | ||
|
||
if (rentedBuffer is not null) | ||
{ | ||
result.Slice(0, resultUsedLength).Clear(); | ||
ArrayPool<char>.Shared.Return(rentedBuffer); | ||
} | ||
|
||
return name; | ||
} | ||
|
||
private enum CharCategory | ||
{ | ||
Boundary, | ||
Lowercase, | ||
Uppercase, | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/libraries/System.Text.Json/Common/JsonSnakeCaseLowerNamingPolicy.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace System.Text.Json | ||
{ | ||
internal sealed class JsonSnakeCaseLowerNamingPolicy : JsonSeparatorNamingPolicy | ||
{ | ||
public JsonSnakeCaseLowerNamingPolicy() | ||
: base(lowercase: true, separator: '_') | ||
{ | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/libraries/System.Text.Json/Common/JsonSnakeCaseUpperNamingPolicy.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace System.Text.Json | ||
{ | ||
internal sealed class JsonSnakeCaseUpperNamingPolicy : JsonSeparatorNamingPolicy | ||
{ | ||
public JsonSnakeCaseUpperNamingPolicy() | ||
: base(lowercase: false, separator: '_') | ||
{ | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious - should we be using identical rules to CamelCase except also adding
-
or_
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I can do that, but knowing about "compatibility concerns" I expect to hear "no" from other reviewers (:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And actually, what you asked is how naming policies work in other projects and platforms. They always split the input and then transform each word and concatenate them back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please file an issue to consider joining this code with CamelCasing and I think we're ready to merge after that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#77262