-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added snake and kebab naming policies to JSON serializer (#69613)
* Added snake and kebab naming policies to JSON serializer * Code styling issues * Explicit types * Fixed range slicing issue * Fixed tests * Forgotten conversion in tests * Fixed docs Co-authored-by: Daniel Stockhammer <daniel@stockhammer.it> * Used nameof instead of hardcoded names in source generator * Updated public API * Fixed kebab case lower policy * Added tests for long inputs * Performance improvements * Made ConvertName sealed Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com> * Explicit variable type * Clear only a dirty part of the buffer * Fixed exception on slicing more that exists * Better variable name * End-to-end serialization tests Co-authored-by: Daniel Stockhammer <daniel@stockhammer.it> Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
- Loading branch information
1 parent
95d36a9
commit 24813dc
Showing
18 changed files
with
572 additions
and
85 deletions.
There are no files selected for viewing
This file contains 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 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 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 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 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 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 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 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 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 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 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 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 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 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.