From acc2a0be876d832543bd3585a11c77793fbce037 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Fri, 20 May 2022 19:04:43 +0300 Subject: [PATCH 01/18] Added snake and kebab naming policies to JSON serializer --- .../Common/JsonKnownNamingPolicy.cs | 22 +- .../Common/JsonNamingPolicy.cs | 23 ++ .../Common/JsonSimpleNamingPolicy.cs | 144 +++++++++++++ .../gen/JsonSourceGenerator.Emitter.cs | 14 +- .../gen/JsonSourceGenerator.Parser.cs | 16 +- .../System.Text.Json.SourceGeneration.targets | 1 + .../System.Text.Json/ref/System.Text.Json.cs | 8 + .../src/System.Text.Json.csproj | 1 + .../Serialization/CamelCaseUnitTests.cs | 52 ----- .../Serialization/NamingPolicyUnitTests.cs | 199 ++++++++++++++++++ .../System.Text.Json.Tests.csproj | 2 +- 11 files changed, 421 insertions(+), 61 deletions(-) create mode 100644 src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs delete mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CamelCaseUnitTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs index 73868fcd7c9357..f41f19abf2b8c3 100644 --- a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs @@ -21,6 +21,26 @@ enum JsonKnownNamingPolicy /// /// Specifies that the built-in be used to convert JSON property names. /// - CamelCase = 1 + CamelCase = 1, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + SnakeLowerCase = 2, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + SnakeUpperCase = 3, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + KebabLowerCase = 4, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + KebabUpperCase = 5, } } diff --git a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs index ee0a86c2321bc8..acffcf4f814b40 100644 --- a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs @@ -13,6 +13,9 @@ namespace System.Text.Json #endif abstract class JsonNamingPolicy { + private const char SnakeWordBoundary = '_'; + private const char KebabWordBoundary = '-'; + /// /// Initializes a new instance of . /// @@ -23,6 +26,26 @@ protected JsonNamingPolicy() { } /// public static JsonNamingPolicy CamelCase { get; } = new JsonCamelCaseNamingPolicy(); + /// + /// Returns the naming policy for lower snake-casing. + /// + public static JsonNamingPolicy SnakeLowerCase { get; } = new JsonSimpleNamingPolicy(lowercase: true, SnakeWordBoundary); + + /// + /// Returns the naming policy for upper snake-casing. + /// + public static JsonNamingPolicy SnakeUpperCase { get; } = new JsonSimpleNamingPolicy(lowercase: false, SnakeWordBoundary); + + /// + /// Returns the naming policy for lower kebab-casing. + /// + public static JsonNamingPolicy KebabLowerCase { get; } = new JsonSimpleNamingPolicy(lowercase: true, KebabWordBoundary); + + /// + /// Returns the naming policy for upper kebab-casing. + /// + public static JsonNamingPolicy KebabUpperCase { get; } = new JsonSimpleNamingPolicy(lowercase: false, KebabWordBoundary); + /// /// When overridden in a derived class, converts the specified name according to the policy. /// diff --git a/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs new file mode 100644 index 00000000000000..54682111e13add --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs @@ -0,0 +1,144 @@ +// 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 sealed class JsonSimpleNamingPolicy : JsonNamingPolicy + { + private readonly bool _lowercase; + private readonly char _boundary; + + internal JsonSimpleNamingPolicy(bool lowercase, char boundary) => + (_lowercase, _boundary) = (lowercase, boundary); + + public override string ConvertName(string name) + { + var bufferLength = name.Length * 2; + var buffer = bufferLength > 512 + ? ArrayPool.Shared.Rent(bufferLength) + : null; + + var resultLength = 0; + Span result = buffer is null + ? stackalloc char[512] + : buffer; + + void WriteWord(ref Span result, ReadOnlySpan word) + { + if (word.IsEmpty) + return; + + var required = result.IsEmpty + ? word.Length + : word.Length + 1; + + if (required >= result.Length) + { + var bufferLength = result.Length * 2; + var bufferNew = ArrayPool.Shared.Rent(bufferLength); + + result.CopyTo(bufferNew); + + if (buffer is not null) + ArrayPool.Shared.Return(buffer); + + buffer = bufferNew; + } + + if (resultLength != 0) + { + result[resultLength] = _boundary; + resultLength += 1; + } + + var destination = result.Slice(resultLength); + if (_lowercase) + { + word.ToLowerInvariant(destination); + } + else + { + word.ToUpperInvariant(destination); + } + + resultLength += word.Length; + } + + int first = 0; + var chars = name.AsSpan(); + var previousCategory = CharCategory.Boundary; + for (int index = 0; index < chars.Length; index++) + { + var current = chars[index]; + var currentCategoryUnicode = char.GetUnicodeCategory(current); + if (currentCategoryUnicode == UnicodeCategory.SpaceSeparator || + currentCategoryUnicode >= UnicodeCategory.ConnectorPunctuation && + currentCategoryUnicode <= UnicodeCategory.OtherPunctuation) + { + WriteWord(ref result, chars.Slice(first, index - first)); + + previousCategory = CharCategory.Boundary; + first = index + 1; + + continue; + } + + if (index + 1 < chars.Length) + { + var next = chars[index + 1]; + var currentCategory = currentCategoryUnicode switch + { + UnicodeCategory.LowercaseLetter => CharCategory.Lowercase, + UnicodeCategory.UppercaseLetter => CharCategory.Uppercase, + _ => previousCategory + }; + + if (currentCategory == CharCategory.Lowercase && char.IsUpper(next) || + next == '_') + { + WriteWord(ref result, chars.Slice(first, index + 1)); + + previousCategory = CharCategory.Boundary; + first = index + 1; + + continue; + } + + if (previousCategory == CharCategory.Uppercase && + currentCategoryUnicode == UnicodeCategory.UppercaseLetter && + char.IsLower(next)) + { + WriteWord(ref result, chars.Slice(first, index - first)); + + previousCategory = CharCategory.Boundary; + first = index; + + continue; + } + + previousCategory = currentCategory; + } + } + + WriteWord(ref result, chars.Slice(first)); + + name = result + .Slice(0, resultLength) + .ToString(); + + if (buffer is not null) + ArrayPool.Shared.Return(buffer); + + return name; + } + + private enum CharCategory + { + Boundary, + Lowercase, + Uppercase, + } + } +} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index db7b990e095d92..f1e722e38f7ebb 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1167,9 +1167,19 @@ private string GetLogicForDefaultSerializerOptionsInit() { JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions; - string? namingPolicyInit = options.PropertyNamingPolicy == JsonKnownNamingPolicy.CamelCase + string? namingPolicyName = options.PropertyNamingPolicy switch + { + JsonKnownNamingPolicy.CamelCase => "CamelCase", + JsonKnownNamingPolicy.SnakeLowerCase => "SnakeLowerCase", + JsonKnownNamingPolicy.SnakeUpperCase => "SnakeUpperCase", + JsonKnownNamingPolicy.KebabLowerCase => "KebabLowerCase", + JsonKnownNamingPolicy.KebabUpperCase => "KebabUpperCase", + _ => null, + }; + + string? namingPolicyInit = namingPolicyName != null ? $@" - PropertyNamingPolicy = {JsonNamingPolicyTypeRef}.CamelCase" + PropertyNamingPolicy = {JsonNamingPolicyTypeRef}.{namingPolicyName}" : null; return $@" diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 60a0be1265182a..fd10cbf6c32e1a 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -1514,13 +1514,19 @@ private static string DetermineRuntimePropName(string clrPropName, string? jsonP { runtimePropName = jsonPropName; } - else if (namingPolicy == JsonKnownNamingPolicy.CamelCase) - { - runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName); - } else { - runtimePropName = clrPropName; + JsonNamingPolicy? instance = namingPolicy switch + { + JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase, + JsonKnownNamingPolicy.SnakeLowerCase => JsonNamingPolicy.SnakeLowerCase, + JsonKnownNamingPolicy.SnakeUpperCase => JsonNamingPolicy.SnakeUpperCase, + JsonKnownNamingPolicy.KebabLowerCase => JsonNamingPolicy.KebabLowerCase, + JsonKnownNamingPolicy.KebabUpperCase => JsonNamingPolicy.KebabUpperCase, + _ => null, + }; + + runtimePropName = instance?.ConvertName(clrPropName) ?? clrPropName; } return runtimePropName; diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index 596ce0c49163cb..178d3ae5a7c28d 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -33,6 +33,7 @@ + diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 3afd4bfe4cec9c..fb9cb006520107 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -157,6 +157,10 @@ public abstract partial class JsonNamingPolicy { protected JsonNamingPolicy() { } public static System.Text.Json.JsonNamingPolicy CamelCase { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy SnakeLowerCase { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy SnakeUpperCase { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy KebabLowerCase { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy KebabUpperCase { get { throw null; } } public abstract string ConvertName(string name); } public readonly partial struct JsonProperty @@ -914,6 +918,10 @@ public enum JsonKnownNamingPolicy { Unspecified = 0, CamelCase = 1, + SnakeLowerCase = 2, + SnakeUpperCase = 3, + KebabLowerCase = 4, + KebabUpperCase = 5, } [System.FlagsAttribute] public enum JsonNumberHandling diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index f0341a109304e8..560c539fcdfee2 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -33,6 +33,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CamelCaseUnitTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CamelCaseUnitTests.cs deleted file mode 100644 index aebce3dcce17ab..00000000000000 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CamelCaseUnitTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace System.Text.Json.Serialization.Tests -{ - public static class CamelCaseUnitTests - { - [Fact] - public static void ToCamelCaseTest() - { - // These test cases were copied from Json.NET. - Assert.Equal("urlValue", ConvertToCamelCase("URLValue")); - Assert.Equal("url", ConvertToCamelCase("URL")); - Assert.Equal("id", ConvertToCamelCase("ID")); - Assert.Equal("i", ConvertToCamelCase("I")); - Assert.Equal("", ConvertToCamelCase("")); - Assert.Null(ConvertToCamelCase(null)); - Assert.Equal("person", ConvertToCamelCase("Person")); - Assert.Equal("iPhone", ConvertToCamelCase("iPhone")); - Assert.Equal("iPhone", ConvertToCamelCase("IPhone")); - Assert.Equal("i Phone", ConvertToCamelCase("I Phone")); - Assert.Equal("i Phone", ConvertToCamelCase("I Phone")); - Assert.Equal(" IPhone", ConvertToCamelCase(" IPhone")); - Assert.Equal(" IPhone ", ConvertToCamelCase(" IPhone ")); - Assert.Equal("isCIA", ConvertToCamelCase("IsCIA")); - Assert.Equal("vmQ", ConvertToCamelCase("VmQ")); - Assert.Equal("xml2Json", ConvertToCamelCase("Xml2Json")); - Assert.Equal("snAkEcAsE", ConvertToCamelCase("SnAkEcAsE")); - Assert.Equal("snA__kEcAsE", ConvertToCamelCase("SnA__kEcAsE")); - Assert.Equal("snA__ kEcAsE", ConvertToCamelCase("SnA__ kEcAsE")); - Assert.Equal("already_snake_case_ ", ConvertToCamelCase("already_snake_case_ ")); - Assert.Equal("isJSONProperty", ConvertToCamelCase("IsJSONProperty")); - Assert.Equal("shoutinG_CASE", ConvertToCamelCase("SHOUTING_CASE")); - Assert.Equal("9999-12-31T23:59:59.9999999Z", ConvertToCamelCase("9999-12-31T23:59:59.9999999Z")); - Assert.Equal("hi!! This is text. Time to test.", ConvertToCamelCase("Hi!! This is text. Time to test.")); - Assert.Equal("building", ConvertToCamelCase("BUILDING")); - Assert.Equal("building Property", ConvertToCamelCase("BUILDING Property")); - Assert.Equal("building Property", ConvertToCamelCase("Building Property")); - Assert.Equal("building PROPERTY", ConvertToCamelCase("BUILDING PROPERTY")); - } - - // Use a helper method since the method is not public. - private static string ConvertToCamelCase(string name) - { - JsonNamingPolicy policy = JsonNamingPolicy.CamelCase; - string value = policy.ConvertName(name); - return value; - } - } -} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs new file mode 100644 index 00000000000000..266af5d015c871 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs @@ -0,0 +1,199 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public static class NamingPolicyUnitTests + { + [Fact] + public static void ToCamelCaseTest() + { + // These test cases were copied from Json.NET. + Assert.Equal("urlValue", Convert("URLValue")); + Assert.Equal("url", Convert("URL")); + Assert.Equal("id", Convert("ID")); + Assert.Equal("i", Convert("I")); + Assert.Equal("", Convert("")); + Assert.Null(Convert(null)); + Assert.Equal("person", Convert("Person")); + Assert.Equal("iPhone", Convert("iPhone")); + Assert.Equal("iPhone", Convert("IPhone")); + Assert.Equal("i Phone", Convert("I Phone")); + Assert.Equal("i Phone", Convert("I Phone")); + Assert.Equal(" IPhone", Convert(" IPhone")); + Assert.Equal(" IPhone ", Convert(" IPhone ")); + Assert.Equal("isCIA", Convert("IsCIA")); + Assert.Equal("vmQ", Convert("VmQ")); + Assert.Equal("xml2Json", Convert("Xml2Json")); + Assert.Equal("snAkEcAsE", Convert("SnAkEcAsE")); + Assert.Equal("snA__kEcAsE", Convert("SnA__kEcAsE")); + Assert.Equal("snA__ kEcAsE", Convert("SnA__ kEcAsE")); + Assert.Equal("already_snake_case_ ", Convert("already_snake_case_ ")); + Assert.Equal("isJSONProperty", Convert("IsJSONProperty")); + Assert.Equal("shoutinG_CASE", Convert("SHOUTING_CASE")); + Assert.Equal("9999-12-31T23:59:59.9999999Z", Convert("9999-12-31T23:59:59.9999999Z")); + Assert.Equal("hi!! This is text. Time to test.", Convert("Hi!! This is text. Time to test.")); + Assert.Equal("building", Convert("BUILDING")); + Assert.Equal("building Property", Convert("BUILDING Property")); + Assert.Equal("building Property", Convert("Building Property")); + Assert.Equal("building PROPERTY", Convert("BUILDING PROPERTY")); + + static string Convert(string name) + { + JsonNamingPolicy policy = JsonNamingPolicy.CamelCase; + string value = policy.ConvertName(name); + return value; + } + } + + [Fact] + public static void ToSnakeLowerCase() + { + Assert.Equal("xml_http_request", Convert("XMLHttpRequest")); + Assert.Equal("camel_case", Convert("camelCase")); + Assert.Equal("camel_case", Convert("CamelCase")); + Assert.Equal("snake_case", Convert("snake_case")); + Assert.Equal("snake_case", Convert("SNAKE_CASE")); + Assert.Equal("kebab_case", Convert("kebab-case")); + Assert.Equal("kebab_case", Convert("KEBAB-CASE")); + Assert.Equal("double_space", Convert("double space")); + Assert.Equal("double_underscore", Convert("double__underscore")); + Assert.Equal("abc", Convert("abc")); + Assert.Equal("ab_c", Convert("abC")); + Assert.Equal("a_bc", Convert("aBc")); + Assert.Equal("a_bc", Convert("aBC")); + Assert.Equal("a_bc", Convert("ABc")); + Assert.Equal("abc", Convert("ABC")); + Assert.Equal("abc123def456", Convert("abc123def456")); + Assert.Equal("abc123_def456", Convert("abc123Def456")); + Assert.Equal("abc123_def456", Convert("abc123DEF456")); + Assert.Equal("abc123def456", Convert("ABC123DEF456")); + Assert.Equal("abc123def456", Convert("ABC123def456")); + Assert.Equal("abc123def456", Convert("Abc123def456")); + Assert.Equal("abc", Convert(" abc")); + Assert.Equal("abc", Convert("abc ")); + Assert.Equal("abc", Convert(" abc ")); + Assert.Equal("abc_def", Convert(" abc def ")); + + static string Convert(string name) + { + JsonNamingPolicy policy = JsonNamingPolicy.SnakeLowerCase; + string value = policy.ConvertName(name); + return value; + } + } + + [Fact] + public static void ToSnakeUpperCase() + { + Assert.Equal("XML_HTTP_REQUEST", Convert("XMLHttpRequest")); + Assert.Equal("CAMEL_CASE", Convert("camelCase")); + Assert.Equal("CAMEL_CASE", Convert("CamelCase")); + Assert.Equal("SNAKE_CASE", Convert("snake_case")); + Assert.Equal("SNAKE_CASE", Convert("SNAKE_CASE")); + Assert.Equal("KEBAB_CASE", Convert("kebab-case")); + Assert.Equal("KEBAB_CASE", Convert("KEBAB-CASE")); + Assert.Equal("DOUBLE_SPACE", Convert("double space")); + Assert.Equal("DOUBLE_UNDERSCORE", Convert("double__underscore")); + Assert.Equal("ABC", Convert("abc")); + Assert.Equal("AB_C", Convert("abC")); + Assert.Equal("A_BC", Convert("aBc")); + Assert.Equal("A_BC", Convert("aBC")); + Assert.Equal("A_BC", Convert("ABc")); + Assert.Equal("ABC", Convert("ABC")); + Assert.Equal("ABC123DEF456", Convert("abc123def456")); + Assert.Equal("ABC123_DEF456", Convert("abc123Def456")); + Assert.Equal("ABC123_DEF456", Convert("abc123DEF456")); + Assert.Equal("ABC123DEF456", Convert("ABC123DEF456")); + Assert.Equal("ABC123DEF456", Convert("ABC123def456")); + Assert.Equal("ABC123DEF456", Convert("Abc123def456")); + Assert.Equal("ABC", Convert(" ABC")); + Assert.Equal("ABC", Convert("ABC ")); + Assert.Equal("ABC", Convert(" ABC ")); + Assert.Equal("ABC_DEF", Convert(" ABC def ")); + + static string Convert(string name) + { + JsonNamingPolicy policy = JsonNamingPolicy.SnakeUpperCase; + string value = policy.ConvertName(name); + return value; + } + } + + [Fact] + public static void ToKebabLowerCase() + { + Assert.Equal("xml-http-request", Convert("XMLHttpRequest")); + Assert.Equal("camel-case", Convert("camelCase")); + Assert.Equal("camel-case", Convert("CamelCase")); + Assert.Equal("snake-case", Convert("snake_case")); + Assert.Equal("snake-case", Convert("SNAKE_CASE")); + Assert.Equal("kebab-case", Convert("kebab-case")); + Assert.Equal("kebab-case", Convert("KEBAB-CASE")); + Assert.Equal("double-space", Convert("double space")); + Assert.Equal("double-underscore", Convert("double__underscore")); + Assert.Equal("abc", Convert("abc")); + Assert.Equal("abC", Convert("ab-c")); + Assert.Equal("aBc", Convert("a-bc")); + Assert.Equal("aBC", Convert("a-bc")); + Assert.Equal("ABc", Convert("a-bc")); + Assert.Equal("ABC", Convert("abc")); + Assert.Equal("abc123def456", Convert("abc123def456")); + Assert.Equal("abc123-def456", Convert("abc123Def456")); + Assert.Equal("abc123-def456", Convert("abc123DEF456")); + Assert.Equal("abc123def456", Convert("ABC123DEF456")); + Assert.Equal("abc123def456", Convert("ABC123def456")); + Assert.Equal("abc123def456", Convert("Abc123def456")); + Assert.Equal("abc", Convert(" abc")); + Assert.Equal("abc", Convert("abc ")); + Assert.Equal("abc", Convert(" abc ")); + Assert.Equal("abc-def"," abc def "); + + static string Convert(string name) + { + JsonNamingPolicy policy = JsonNamingPolicy.KebabLowerCase; + string value = policy.ConvertName(name); + return value; + } + } + + [Fact] + public static void ToKebabUpperCase() + { + Assert.Equal("XML-HTTP-REQUEST", Convert("XMLHttpRequest")); + Assert.Equal("CAMEL-CASE", Convert("camelCase")); + Assert.Equal("CAMEL-CASE", Convert("CamelCase")); + Assert.Equal("SNAKE-CASE", Convert("snake_case")); + Assert.Equal("SNAKE-CASE", Convert("SNAKE_CASE")); + Assert.Equal("KEBAB-CASE", Convert("kebab-case")); + Assert.Equal("KEBAB-CASE", Convert("KEBAB-CASE")); + Assert.Equal("DOUBLE-SPACE", Convert("double space")); + Assert.Equal("DOUBLE-UNDERSCORE", Convert("double__underscore")); + Assert.Equal("ABC", Convert("abc")); + Assert.Equal("AB-C", Convert("abC")); + Assert.Equal("A-BC", Convert("aBc")); + Assert.Equal("A-BC", Convert("aBC")); + Assert.Equal("A-BC", Convert("ABc")); + Assert.Equal("ABC", Convert("ABC")); + Assert.Equal("ABC123DEF456", Convert("abc123def456")); + Assert.Equal("ABC123-DEF456", Convert("abc123Def456")); + Assert.Equal("ABC123-DEF456", Convert("abc123DEF456")); + Assert.Equal("ABC123DEF456", Convert("ABC123DEF456")); + Assert.Equal("ABC123DEF456", Convert("ABC123def456")); + Assert.Equal("ABC123DEF456", Convert("Abc123def456")); + Assert.Equal("ABC", Convert(" ABC")); + Assert.Equal("ABC", Convert("ABC ")); + Assert.Equal("ABC", Convert(" ABC ")); + Assert.Equal("ABC-DEF", Convert(" ABC def ")); + + static string Convert(string name) + { + JsonNamingPolicy policy = JsonNamingPolicy.KebabUpperCase; + string value = policy.ConvertName(name); + return value; + } + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 749f6364889bac..9c95305a2ea693 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -116,7 +116,6 @@ - @@ -175,6 +174,7 @@ + From 3b0ff1bb8c02629dc157e3013300433cddcd65f5 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Fri, 20 May 2022 20:28:11 +0300 Subject: [PATCH 02/18] Code styling issues --- src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs | 2 +- .../System.Text.Json/gen/JsonSourceGenerator.Parser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs index f41f19abf2b8c3..ff9cdbaaa1ef46 100644 --- a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs @@ -41,6 +41,6 @@ enum JsonKnownNamingPolicy /// /// Specifies that the built-in be used to convert JSON property names. /// - KebabUpperCase = 5, + KebabUpperCase = 5 } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index fd10cbf6c32e1a..1470200c492673 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -1525,7 +1525,7 @@ private static string DetermineRuntimePropName(string clrPropName, string? jsonP JsonKnownNamingPolicy.KebabUpperCase => JsonNamingPolicy.KebabUpperCase, _ => null, }; - + runtimePropName = instance?.ConvertName(clrPropName) ?? clrPropName; } From 748b94762d0342a77b6788af6b34d6ca2836d153 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Fri, 20 May 2022 22:43:47 +0300 Subject: [PATCH 03/18] Explicit types --- .../Common/JsonSimpleNamingPolicy.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs index 54682111e13add..bab895623aa66b 100644 --- a/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs @@ -15,12 +15,12 @@ internal JsonSimpleNamingPolicy(bool lowercase, char boundary) => public override string ConvertName(string name) { - var bufferLength = name.Length * 2; - var buffer = bufferLength > 512 + int bufferLength = name.Length * 2; + char[]? buffer = bufferLength > 512 ? ArrayPool.Shared.Rent(bufferLength) : null; - var resultLength = 0; + int resultLength = 0; Span result = buffer is null ? stackalloc char[512] : buffer; @@ -30,14 +30,14 @@ void WriteWord(ref Span result, ReadOnlySpan word) if (word.IsEmpty) return; - var required = result.IsEmpty + int required = result.IsEmpty ? word.Length : word.Length + 1; if (required >= result.Length) { - var bufferLength = result.Length * 2; - var bufferNew = ArrayPool.Shared.Rent(bufferLength); + int bufferLength = result.Length * 2; + char[] bufferNew = ArrayPool.Shared.Rent(bufferLength); result.CopyTo(bufferNew); @@ -53,7 +53,8 @@ void WriteWord(ref Span result, ReadOnlySpan word) resultLength += 1; } - var destination = result.Slice(resultLength); + Span destination = result.Slice(resultLength); + if (_lowercase) { word.ToLowerInvariant(destination); @@ -67,12 +68,14 @@ void WriteWord(ref Span result, ReadOnlySpan word) } int first = 0; - var chars = name.AsSpan(); - var previousCategory = CharCategory.Boundary; + ReadOnlySpan chars = name.AsSpan(); + CharCategory previousCategory = CharCategory.Boundary; + for (int index = 0; index < chars.Length; index++) { - var current = chars[index]; - var currentCategoryUnicode = char.GetUnicodeCategory(current); + char current = chars[index]; + UnicodeCategory currentCategoryUnicode = char.GetUnicodeCategory(current); + if (currentCategoryUnicode == UnicodeCategory.SpaceSeparator || currentCategoryUnicode >= UnicodeCategory.ConnectorPunctuation && currentCategoryUnicode <= UnicodeCategory.OtherPunctuation) @@ -87,8 +90,8 @@ void WriteWord(ref Span result, ReadOnlySpan word) if (index + 1 < chars.Length) { - var next = chars[index + 1]; - var currentCategory = currentCategoryUnicode switch + char next = chars[index + 1]; + CharCategory currentCategory = currentCategoryUnicode switch { UnicodeCategory.LowercaseLetter => CharCategory.Lowercase, UnicodeCategory.UppercaseLetter => CharCategory.Uppercase, From 762f81947e4b8a5c0431bba99b8db2e76360a864 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Fri, 20 May 2022 23:16:07 +0300 Subject: [PATCH 04/18] Fixed range slicing issue --- src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs index bab895623aa66b..c40b61922d12fc 100644 --- a/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs @@ -101,7 +101,7 @@ void WriteWord(ref Span result, ReadOnlySpan word) if (currentCategory == CharCategory.Lowercase && char.IsUpper(next) || next == '_') { - WriteWord(ref result, chars.Slice(first, index + 1)); + WriteWord(ref result, chars.Slice(first, index - first + 1)); previousCategory = CharCategory.Boundary; first = index + 1; From 191eace70ef891b253234ad3f21e6faf7f6d9ed4 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Mon, 23 May 2022 18:40:26 +0300 Subject: [PATCH 05/18] Fixed tests --- .../Serialization/NamingPolicyUnitTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs index 266af5d015c871..93549ac10ee8dc 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs @@ -135,11 +135,11 @@ public static void ToKebabLowerCase() Assert.Equal("double-space", Convert("double space")); Assert.Equal("double-underscore", Convert("double__underscore")); Assert.Equal("abc", Convert("abc")); - Assert.Equal("abC", Convert("ab-c")); - Assert.Equal("aBc", Convert("a-bc")); - Assert.Equal("aBC", Convert("a-bc")); - Assert.Equal("ABc", Convert("a-bc")); - Assert.Equal("ABC", Convert("abc")); + Assert.Equal("ab-c", Convert("abC")); + Assert.Equal("a-bc", Convert("aBc")); + Assert.Equal("a-bc", Convert("aBC")); + Assert.Equal("a-bc", Convert("ABc")); + Assert.Equal("abc", Convert("ABC")); Assert.Equal("abc123def456", Convert("abc123def456")); Assert.Equal("abc123-def456", Convert("abc123Def456")); Assert.Equal("abc123-def456", Convert("abc123DEF456")); From 9294107c31d31b84b65a3378df90526d80ba1035 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Mon, 23 May 2022 20:11:53 +0300 Subject: [PATCH 06/18] Forgotten conversion in tests --- .../Serialization/NamingPolicyUnitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs index 93549ac10ee8dc..054996ea1629c1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs @@ -149,7 +149,7 @@ public static void ToKebabLowerCase() Assert.Equal("abc", Convert(" abc")); Assert.Equal("abc", Convert("abc ")); Assert.Equal("abc", Convert(" abc ")); - Assert.Equal("abc-def"," abc def "); + Assert.Equal("abc-def", Convert(" abc def ")); static string Convert(string name) { From 076877cb466841c21d98c6f858d0c61eee52eab9 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Mon, 25 Jul 2022 11:23:21 +0300 Subject: [PATCH 07/18] Fixed docs Co-authored-by: Daniel Stockhammer --- src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs index ff9cdbaaa1ef46..0080b95eeb8016 100644 --- a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs @@ -29,7 +29,7 @@ enum JsonKnownNamingPolicy SnakeLowerCase = 2, /// - /// Specifies that the built-in be used to convert JSON property names. + /// Specifies that the built-in be used to convert JSON property names. /// SnakeUpperCase = 3, From 927fac1ec1782b8c0564755e963a8b2765157af9 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Sun, 2 Oct 2022 17:37:02 +0300 Subject: [PATCH 08/18] Used nameof instead of hardcoded names in source generator --- .../gen/JsonSourceGenerator.Emitter.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index f1e722e38f7ebb..7938bab6405ff0 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1169,11 +1169,11 @@ private string GetLogicForDefaultSerializerOptionsInit() string? namingPolicyName = options.PropertyNamingPolicy switch { - JsonKnownNamingPolicy.CamelCase => "CamelCase", - JsonKnownNamingPolicy.SnakeLowerCase => "SnakeLowerCase", - JsonKnownNamingPolicy.SnakeUpperCase => "SnakeUpperCase", - JsonKnownNamingPolicy.KebabLowerCase => "KebabLowerCase", - JsonKnownNamingPolicy.KebabUpperCase => "KebabUpperCase", + JsonKnownNamingPolicy.CamelCase => nameof(JsonNamingPolicy.CamelCase), + JsonKnownNamingPolicy.SnakeLowerCase => nameof(JsonNamingPolicy.SnakeLowerCase), + JsonKnownNamingPolicy.SnakeUpperCase => nameof(JsonNamingPolicy.SnakeUpperCase), + JsonKnownNamingPolicy.KebabLowerCase => nameof(JsonNamingPolicy.KebabLowerCase), + JsonKnownNamingPolicy.KebabUpperCase => nameof(JsonNamingPolicy.KebabUpperCase), _ => null, }; From 6afee1efe6be5c97f1dcf627a08434bbf00a9350 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Tue, 4 Oct 2022 15:40:16 +0300 Subject: [PATCH 09/18] Updated public API --- .../Common/JsonKebabCaseLowerNamingPolicy.cs | 13 +++++++++++++ .../Common/JsonKebabCaseUpperNamingPolicy.cs | 13 +++++++++++++ .../Common/JsonKnownNamingPolicy.cs | 16 ++++++++-------- .../System.Text.Json/Common/JsonNamingPolicy.cs | 11 ++++------- ...ingPolicy.cs => JsonSeparatorNamingPolicy.cs} | 11 ++++++----- .../Common/JsonSnakeCaseLowerNamingPolicy.cs | 13 +++++++++++++ .../Common/JsonSnakeCaseUpperNamingPolicy.cs | 13 +++++++++++++ .../gen/JsonSourceGenerator.Emitter.cs | 8 ++++---- .../gen/JsonSourceGenerator.Parser.cs | 8 ++++---- .../System.Text.Json.SourceGeneration.targets | 6 +++++- .../System.Text.Json/ref/System.Text.Json.cs | 16 ++++++++-------- .../System.Text.Json/src/System.Text.Json.csproj | 6 +++++- .../Serialization/NamingPolicyUnitTests.cs | 8 ++++---- 13 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 src/libraries/System.Text.Json/Common/JsonKebabCaseLowerNamingPolicy.cs create mode 100644 src/libraries/System.Text.Json/Common/JsonKebabCaseUpperNamingPolicy.cs rename src/libraries/System.Text.Json/Common/{JsonSimpleNamingPolicy.cs => JsonSeparatorNamingPolicy.cs} (93%) create mode 100644 src/libraries/System.Text.Json/Common/JsonSnakeCaseLowerNamingPolicy.cs create mode 100644 src/libraries/System.Text.Json/Common/JsonSnakeCaseUpperNamingPolicy.cs diff --git a/src/libraries/System.Text.Json/Common/JsonKebabCaseLowerNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKebabCaseLowerNamingPolicy.cs new file mode 100644 index 00000000000000..ebb380d34dd63d --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonKebabCaseLowerNamingPolicy.cs @@ -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: '-') + { + } + } +} diff --git a/src/libraries/System.Text.Json/Common/JsonKebabCaseUpperNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKebabCaseUpperNamingPolicy.cs new file mode 100644 index 00000000000000..1bd58a6e0b07fb --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonKebabCaseUpperNamingPolicy.cs @@ -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: '-') + { + } + } +} diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs index 0080b95eeb8016..4c414544759c4e 100644 --- a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs @@ -24,23 +24,23 @@ enum JsonKnownNamingPolicy CamelCase = 1, /// - /// Specifies that the built-in be used to convert JSON property names. + /// Specifies that the built-in be used to convert JSON property names. /// - SnakeLowerCase = 2, + SnakeCaseLower = 2, /// - /// Specifies that the built-in be used to convert JSON property names. + /// Specifies that the built-in be used to convert JSON property names. /// - SnakeUpperCase = 3, + SnakeCaseUpper = 3, /// - /// Specifies that the built-in be used to convert JSON property names. + /// Specifies that the built-in be used to convert JSON property names. /// - KebabLowerCase = 4, + KebabCaseLower = 4, /// - /// Specifies that the built-in be used to convert JSON property names. + /// Specifies that the built-in be used to convert JSON property names. /// - KebabUpperCase = 5 + KebabCaseUpper = 5 } } diff --git a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs index acffcf4f814b40..32e6542d1c3ee1 100644 --- a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs @@ -13,9 +13,6 @@ namespace System.Text.Json #endif abstract class JsonNamingPolicy { - private const char SnakeWordBoundary = '_'; - private const char KebabWordBoundary = '-'; - /// /// Initializes a new instance of . /// @@ -29,22 +26,22 @@ protected JsonNamingPolicy() { } /// /// Returns the naming policy for lower snake-casing. /// - public static JsonNamingPolicy SnakeLowerCase { get; } = new JsonSimpleNamingPolicy(lowercase: true, SnakeWordBoundary); + public static JsonNamingPolicy SnakeCaseLower { get; } = new JsonSnakeCaseLowerNamingPolicy(); /// /// Returns the naming policy for upper snake-casing. /// - public static JsonNamingPolicy SnakeUpperCase { get; } = new JsonSimpleNamingPolicy(lowercase: false, SnakeWordBoundary); + public static JsonNamingPolicy SnakeCaseUpper { get; } = new JsonSnakeCaseUpperNamingPolicy(); /// /// Returns the naming policy for lower kebab-casing. /// - public static JsonNamingPolicy KebabLowerCase { get; } = new JsonSimpleNamingPolicy(lowercase: true, KebabWordBoundary); + public static JsonNamingPolicy KebabCaseLower { get; } = new JsonSnakeCaseLowerNamingPolicy(); /// /// Returns the naming policy for upper kebab-casing. /// - public static JsonNamingPolicy KebabUpperCase { get; } = new JsonSimpleNamingPolicy(lowercase: false, KebabWordBoundary); + public static JsonNamingPolicy KebabCaseUpper { get; } = new JsonKebabCaseUpperNamingPolicy(); /// /// When overridden in a derived class, converts the specified name according to the policy. diff --git a/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs similarity index 93% rename from src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs rename to src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs index c40b61922d12fc..37cd1354059c0e 100644 --- a/src/libraries/System.Text.Json/Common/JsonSimpleNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -1,17 +1,18 @@ // 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 sealed class JsonSimpleNamingPolicy : JsonNamingPolicy + internal abstract class JsonSeparatorNamingPolicy : JsonNamingPolicy { private readonly bool _lowercase; - private readonly char _boundary; + private readonly char _separator; - internal JsonSimpleNamingPolicy(bool lowercase, char boundary) => - (_lowercase, _boundary) = (lowercase, boundary); + internal JsonSeparatorNamingPolicy(bool lowercase, char separator) => + (_lowercase, _separator) = (lowercase, separator); public override string ConvertName(string name) { @@ -49,7 +50,7 @@ void WriteWord(ref Span result, ReadOnlySpan word) if (resultLength != 0) { - result[resultLength] = _boundary; + result[resultLength] = _separator; resultLength += 1; } diff --git a/src/libraries/System.Text.Json/Common/JsonSnakeCaseLowerNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSnakeCaseLowerNamingPolicy.cs new file mode 100644 index 00000000000000..f46488143dae3e --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonSnakeCaseLowerNamingPolicy.cs @@ -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: '_') + { + } + } +} diff --git a/src/libraries/System.Text.Json/Common/JsonSnakeCaseUpperNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSnakeCaseUpperNamingPolicy.cs new file mode 100644 index 00000000000000..3f49d873f8960c --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonSnakeCaseUpperNamingPolicy.cs @@ -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: '_') + { + } + } +} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 7938bab6405ff0..4c3f4e897fea57 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1170,10 +1170,10 @@ private string GetLogicForDefaultSerializerOptionsInit() string? namingPolicyName = options.PropertyNamingPolicy switch { JsonKnownNamingPolicy.CamelCase => nameof(JsonNamingPolicy.CamelCase), - JsonKnownNamingPolicy.SnakeLowerCase => nameof(JsonNamingPolicy.SnakeLowerCase), - JsonKnownNamingPolicy.SnakeUpperCase => nameof(JsonNamingPolicy.SnakeUpperCase), - JsonKnownNamingPolicy.KebabLowerCase => nameof(JsonNamingPolicy.KebabLowerCase), - JsonKnownNamingPolicy.KebabUpperCase => nameof(JsonNamingPolicy.KebabUpperCase), + JsonKnownNamingPolicy.SnakeCaseLower => nameof(JsonNamingPolicy.SnakeCaseLower), + JsonKnownNamingPolicy.SnakeCaseUpper => nameof(JsonNamingPolicy.SnakeCaseUpper), + JsonKnownNamingPolicy.KebabCaseLower => nameof(JsonNamingPolicy.KebabCaseLower), + JsonKnownNamingPolicy.KebabCaseUpper => nameof(JsonNamingPolicy.KebabCaseUpper), _ => null, }; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 1470200c492673..0230408f89adec 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -1519,10 +1519,10 @@ private static string DetermineRuntimePropName(string clrPropName, string? jsonP JsonNamingPolicy? instance = namingPolicy switch { JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase, - JsonKnownNamingPolicy.SnakeLowerCase => JsonNamingPolicy.SnakeLowerCase, - JsonKnownNamingPolicy.SnakeUpperCase => JsonNamingPolicy.SnakeUpperCase, - JsonKnownNamingPolicy.KebabLowerCase => JsonNamingPolicy.KebabLowerCase, - JsonKnownNamingPolicy.KebabUpperCase => JsonNamingPolicy.KebabUpperCase, + JsonKnownNamingPolicy.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower, + JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper, + JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower, + JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper, _ => null, }; diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index 178d3ae5a7c28d..0b45537c27dfed 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -30,10 +30,14 @@ + + + - + + diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index fb9cb006520107..b3426cdf8c4425 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -157,10 +157,10 @@ public abstract partial class JsonNamingPolicy { protected JsonNamingPolicy() { } public static System.Text.Json.JsonNamingPolicy CamelCase { get { throw null; } } - public static System.Text.Json.JsonNamingPolicy SnakeLowerCase { get { throw null; } } - public static System.Text.Json.JsonNamingPolicy SnakeUpperCase { get { throw null; } } - public static System.Text.Json.JsonNamingPolicy KebabLowerCase { get { throw null; } } - public static System.Text.Json.JsonNamingPolicy KebabUpperCase { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy SnakeCaseLower { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy SnakeCaseUpper { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy KebabCaseLower { get { throw null; } } + public static System.Text.Json.JsonNamingPolicy KebabCaseUpper { get { throw null; } } public abstract string ConvertName(string name); } public readonly partial struct JsonProperty @@ -918,10 +918,10 @@ public enum JsonKnownNamingPolicy { Unspecified = 0, CamelCase = 1, - SnakeLowerCase = 2, - SnakeUpperCase = 3, - KebabLowerCase = 4, - KebabUpperCase = 5, + SnakeCaseLower = 2, + SnakeCaseUpper = 3, + KebabCaseLower = 4, + KebabCaseUpper = 5, } [System.FlagsAttribute] public enum JsonNumberHandling diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 560c539fcdfee2..635be981ff62ca 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -30,10 +30,14 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + + + - + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs index 054996ea1629c1..0f4de92a21bbb1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs @@ -79,7 +79,7 @@ public static void ToSnakeLowerCase() static string Convert(string name) { - JsonNamingPolicy policy = JsonNamingPolicy.SnakeLowerCase; + JsonNamingPolicy policy = JsonNamingPolicy.SnakeCaseLower; string value = policy.ConvertName(name); return value; } @@ -116,7 +116,7 @@ public static void ToSnakeUpperCase() static string Convert(string name) { - JsonNamingPolicy policy = JsonNamingPolicy.SnakeUpperCase; + JsonNamingPolicy policy = JsonNamingPolicy.SnakeCaseUpper; string value = policy.ConvertName(name); return value; } @@ -153,7 +153,7 @@ public static void ToKebabLowerCase() static string Convert(string name) { - JsonNamingPolicy policy = JsonNamingPolicy.KebabLowerCase; + JsonNamingPolicy policy = JsonNamingPolicy.KebabCaseLower; string value = policy.ConvertName(name); return value; } @@ -190,7 +190,7 @@ public static void ToKebabUpperCase() static string Convert(string name) { - JsonNamingPolicy policy = JsonNamingPolicy.KebabUpperCase; + JsonNamingPolicy policy = JsonNamingPolicy.KebabCaseUpper; string value = policy.ConvertName(name); return value; } From f39065becfde2a1d2c04a23c84fb6fb1d0355d5b Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Wed, 5 Oct 2022 21:40:27 +0300 Subject: [PATCH 10/18] Fixed kebab case lower policy --- src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs index 32e6542d1c3ee1..5abfa4621eb173 100644 --- a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs @@ -36,7 +36,7 @@ protected JsonNamingPolicy() { } /// /// Returns the naming policy for lower kebab-casing. /// - public static JsonNamingPolicy KebabCaseLower { get; } = new JsonSnakeCaseLowerNamingPolicy(); + public static JsonNamingPolicy KebabCaseLower { get; } = new JsonKebabCaseLowerNamingPolicy(); /// /// Returns the naming policy for upper kebab-casing. From 23f5d46a5a13e7071c87e1f053dd211053c7a0bf Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Tue, 11 Oct 2022 17:01:44 +0300 Subject: [PATCH 11/18] Added tests for long inputs --- .../Serialization/NamingPolicyUnitTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs index 0f4de92a21bbb1..6a62c2acdd1f9f 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs @@ -76,6 +76,15 @@ public static void ToSnakeLowerCase() Assert.Equal("abc", Convert("abc ")); Assert.Equal("abc", Convert(" abc ")); Assert.Equal("abc_def", Convert(" abc def ")); + Assert.Equal( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + Convert("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "a_haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + Convert("aHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "a_towel_it_says_is_about_the_most_massively_useful_thing_an_interstellar_hitchhiker_can_have_partly_it_has_great_practical_value_you_can_wrap_it_around_you_for_warmth_as_you_bound_across_the_cold_moons_of_jaglan_beta_you_can_lie_on_it_on_the_brilliant_marble_sanded_beaches_of_santraginus_v_inhaling_the_heady_sea_vapors_you_can_sleep_under_it_beneath_the_stars_which_shine_so_redly_on_the_desert_world_of_kakrafoon_use_it_to_sail_a_miniraft_down_the_slow_heavy_river_moth_wet_it_for_use_in_hand_to_hand_combat_wrap_it_round_your_head_to_ward_off_noxious_fumes_or_avoid_the_gaze_of_the_ravenous_bugblatter_beast_of_traal_a_mind_bogglingly_stupid_animal_it_assumes_that_if_you_cant_see_it_it_cant_see_you_daft_as_a_brush_but_very_very_ravenous_you_can_wave_your_towel_in_emergencies_as_a_distress_signal_and_of_course_dry_yourself_of_with_it_if_it_still_seems_to_be_clean_enough", + Convert("ATowelItSaysIsAboutTheMostMassivelyUsefulThingAnInterstellarHitchhikerCanHave_PartlyItHasGreatPracticalValue_YouCanWrapItAroundYouForWarmthAsYouBoundAcrossTheColdMoonsOfJaglanBeta_YouCanLieOnItOnTheBrilliantMarbleSandedBeachesOfSantraginusVInhalingTheHeadySeaVapors_YouCanSleepUnderItBeneathTheStarsWhichShineSoRedlyOnTheDesertWorldOfKakrafoon_UseItToSailAMiniraftDownTheSlowHeavyRiverMoth_WetItForUseInHandToHandCombat_WrapItRoundYourHeadToWardOffNoxiousFumesOrAvoidTheGazeOfTheRavenousBugblatterBeastOfTraalAMindBogglinglyStupidAnimal_ItAssumesThatIfYouCantSeeItItCantSeeYouDaftAsABrushButVeryVeryRavenous_YouCanWaveYourTowelInEmergenciesAsADistressSignalAndOfCourseDryYourselfOfWithItIfItStillSeemsToBeCleanEnough")); static string Convert(string name) { @@ -113,6 +122,15 @@ public static void ToSnakeUpperCase() Assert.Equal("ABC", Convert("ABC ")); Assert.Equal("ABC", Convert(" ABC ")); Assert.Equal("ABC_DEF", Convert(" ABC def ")); + Assert.Equal( + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + Convert("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "A_HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + Convert("aHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "A_TOWEL_IT_SAYS_IS_ABOUT_THE_MOST_MASSIVELY_USEFUL_THING_AN_INTERSTELLAR_HITCHHIKER_CAN_HAVE_PARTLY_IT_HAS_GREAT_PRACTICAL_VALUE_YOU_CAN_WRAP_IT_AROUND_YOU_FOR_WARMTH_AS_YOU_BOUND_ACROSS_THE_COLD_MOONS_OF_JAGLAN_BETA_YOU_CAN_LIE_ON_IT_ON_THE_BRILLIANT_MARBLE_SANDED_BEACHES_OF_SANTRAGINUS_V_INHALING_THE_HEADY_SEA_VAPORS_YOU_CAN_SLEEP_UNDER_IT_BENEATH_THE_STARS_WHICH_SHINE_SO_REDLY_ON_THE_DESERT_WORLD_OF_KAKRAFOON_USE_IT_TO_SAIL_A_MINIRAFT_DOWN_THE_SLOW_HEAVY_RIVER_MOTH_WET_IT_FOR_USE_IN_HAND_TO_HAND_COMBAT_WRAP_IT_ROUND_YOUR_HEAD_TO_WARD_OFF_NOXIOUS_FUMES_OR_AVOID_THE_GAZE_OF_THE_RAVENOUS_BUGBLATTER_BEAST_OF_TRAAL_A_MIND_BOGGLINGLY_STUPID_ANIMAL_IT_ASSUMES_THAT_IF_YOU_CANT_SEE_IT_IT_CANT_SEE_YOU_DAFT_AS_A_BRUSH_BUT_VERY_VERY_RAVENOUS_YOU_CAN_WAVE_YOUR_TOWEL_IN_EMERGENCIES_AS_A_DISTRESS_SIGNAL_AND_OF_COURSE_DRY_YOURSELF_OF_WITH_IT_IF_IT_STILL_SEEMS_TO_BE_CLEAN_ENOUGH", + Convert("ATowelItSaysIsAboutTheMostMassivelyUsefulThingAnInterstellarHitchhikerCanHave_PartlyItHasGreatPracticalValue_YouCanWrapItAroundYouForWarmthAsYouBoundAcrossTheColdMoonsOfJaglanBeta_YouCanLieOnItOnTheBrilliantMarbleSandedBeachesOfSantraginusVInhalingTheHeadySeaVapors_YouCanSleepUnderItBeneathTheStarsWhichShineSoRedlyOnTheDesertWorldOfKakrafoon_UseItToSailAMiniraftDownTheSlowHeavyRiverMoth_WetItForUseInHandToHandCombat_WrapItRoundYourHeadToWardOffNoxiousFumesOrAvoidTheGazeOfTheRavenousBugblatterBeastOfTraalAMindBogglinglyStupidAnimal_ItAssumesThatIfYouCantSeeItItCantSeeYouDaftAsABrushButVeryVeryRavenous_YouCanWaveYourTowelInEmergenciesAsADistressSignalAndOfCourseDryYourselfOfWithItIfItStillSeemsToBeCleanEnough")); static string Convert(string name) { @@ -150,6 +168,15 @@ public static void ToKebabLowerCase() Assert.Equal("abc", Convert("abc ")); Assert.Equal("abc", Convert(" abc ")); Assert.Equal("abc-def", Convert(" abc def ")); + Assert.Equal( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + Convert("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "a-haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + Convert("aHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "a-towel-it-says-is-about-the-most-massively-useful-thing-an-interstellar-hitchhiker-can-have-partly-it-has-great-practical-value-you-can-wrap-it-around-you-for-warmth-as-you-bound-across-the-cold-moons-of-jaglan-beta-you-can-lie-on-it-on-the-brilliant-marble-sanded-beaches-of-santraginus-v-inhaling-the-heady-sea-vapors-you-can-sleep-under-it-beneath-the-stars-which-shine-so-redly-on-the-desert-world-of-kakrafoon-use-it-to-sail-a-miniraft-down-the-slow-heavy-river-moth-wet-it-for-use-in-hand-to-hand-combat-wrap-it-round-your-head-to-ward-off-noxious-fumes-or-avoid-the-gaze-of-the-ravenous-bugblatter-beast-of-traal-a-mind-bogglingly-stupid-animal-it-assumes-that-if-you-cant-see-it-it-cant-see-you-daft-as-a-brush-but-very-very-ravenous-you-can-wave-your-towel-in-emergencies-as-a-distress-signal-and-of-course-dry-yourself-of-with-it-if-it-still-seems-to-be-clean-enough", + Convert("ATowelItSaysIsAboutTheMostMassivelyUsefulThingAnInterstellarHitchhikerCanHave_PartlyItHasGreatPracticalValue_YouCanWrapItAroundYouForWarmthAsYouBoundAcrossTheColdMoonsOfJaglanBeta_YouCanLieOnItOnTheBrilliantMarbleSandedBeachesOfSantraginusVInhalingTheHeadySeaVapors_YouCanSleepUnderItBeneathTheStarsWhichShineSoRedlyOnTheDesertWorldOfKakrafoon_UseItToSailAMiniraftDownTheSlowHeavyRiverMoth_WetItForUseInHandToHandCombat_WrapItRoundYourHeadToWardOffNoxiousFumesOrAvoidTheGazeOfTheRavenousBugblatterBeastOfTraalAMindBogglinglyStupidAnimal_ItAssumesThatIfYouCantSeeItItCantSeeYouDaftAsABrushButVeryVeryRavenous_YouCanWaveYourTowelInEmergenciesAsADistressSignalAndOfCourseDryYourselfOfWithItIfItStillSeemsToBeCleanEnough")); static string Convert(string name) { @@ -187,6 +214,15 @@ public static void ToKebabUpperCase() Assert.Equal("ABC", Convert("ABC ")); Assert.Equal("ABC", Convert(" ABC ")); Assert.Equal("ABC-DEF", Convert(" ABC def ")); + Assert.Equal( + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + Convert("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "A-HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + Convert("aHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.Equal( + "A-TOWEL-IT-SAYS-IS-ABOUT-THE-MOST-MASSIVELY-USEFUL-THING-AN-INTERSTELLAR-HITCHHIKER-CAN-HAVE-PARTLY-IT-HAS-GREAT-PRACTICAL-VALUE-YOU-CAN-WRAP-IT-AROUND-YOU-FOR-WARMTH-AS-YOU-BOUND-ACROSS-THE-COLD-MOONS-OF-JAGLAN-BETA-YOU-CAN-LIE-ON-IT-ON-THE-BRILLIANT-MARBLE-SANDED-BEACHES-OF-SANTRAGINUS-V-INHALING-THE-HEADY-SEA-VAPORS-YOU-CAN-SLEEP-UNDER-IT-BENEATH-THE-STARS-WHICH-SHINE-SO-REDLY-ON-THE-DESERT-WORLD-OF-KAKRAFOON-USE-IT-TO-SAIL-A-MINIRAFT-DOWN-THE-SLOW-HEAVY-RIVER-MOTH-WET-IT-FOR-USE-IN-HAND-TO-HAND-COMBAT-WRAP-IT-ROUND-YOUR-HEAD-TO-WARD-OFF-NOXIOUS-FUMES-OR-AVOID-THE-GAZE-OF-THE-RAVENOUS-BUGBLATTER-BEAST-OF-TRAAL-A-MIND-BOGGLINGLY-STUPID-ANIMAL-IT-ASSUMES-THAT-IF-YOU-CANT-SEE-IT-IT-CANT-SEE-YOU-DAFT-AS-A-BRUSH-BUT-VERY-VERY-RAVENOUS-YOU-CAN-WAVE-YOUR-TOWEL-IN-EMERGENCIES-AS-A-DISTRESS-SIGNAL-AND-OF-COURSE-DRY-YOURSELF-OF-WITH-IT-IF-IT-STILL-SEEMS-TO-BE-CLEAN-ENOUGH", + Convert("ATowelItSaysIsAboutTheMostMassivelyUsefulThingAnInterstellarHitchhikerCanHave_PartlyItHasGreatPracticalValue_YouCanWrapItAroundYouForWarmthAsYouBoundAcrossTheColdMoonsOfJaglanBeta_YouCanLieOnItOnTheBrilliantMarbleSandedBeachesOfSantraginusVInhalingTheHeadySeaVapors_YouCanSleepUnderItBeneathTheStarsWhichShineSoRedlyOnTheDesertWorldOfKakrafoon_UseItToSailAMiniraftDownTheSlowHeavyRiverMoth_WetItForUseInHandToHandCombat_WrapItRoundYourHeadToWardOffNoxiousFumesOrAvoidTheGazeOfTheRavenousBugblatterBeastOfTraalAMindBogglinglyStupidAnimal_ItAssumesThatIfYouCantSeeItItCantSeeYouDaftAsABrushButVeryVeryRavenous_YouCanWaveYourTowelInEmergenciesAsADistressSignalAndOfCourseDryYourselfOfWithItIfItStillSeemsToBeCleanEnough")); static string Convert(string name) { From b694e2232e1dbdd1ca4621e239e5fa0afa066de7 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Tue, 11 Oct 2022 17:09:33 +0300 Subject: [PATCH 12/18] Performance improvements --- .../System.Text.Json/Common/JsonConstants.cs | 3 + .../Common/JsonSeparatorNamingPolicy.cs | 79 +++++++++++-------- .../src/System/Text/Json/JsonConstants.cs | 3 - 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/libraries/System.Text.Json/Common/JsonConstants.cs b/src/libraries/System.Text.Json/Common/JsonConstants.cs index 63bf065d126872..4a7209f9a4aba2 100644 --- a/src/libraries/System.Text.Json/Common/JsonConstants.cs +++ b/src/libraries/System.Text.Json/Common/JsonConstants.cs @@ -8,5 +8,8 @@ internal static partial class JsonConstants // Standard format for double and single on non-inbox frameworks. public const string DoubleFormatString = "G17"; public const string SingleFormatString = "G9"; + + public const int StackallocByteThreshold = 256; + public const int StackallocCharThreshold = StackallocByteThreshold / 2; } } diff --git a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs index 37cd1354059c0e..867df219ee5b06 100644 --- a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -16,36 +16,56 @@ internal JsonSeparatorNamingPolicy(bool lowercase, char separator) => public override string ConvertName(string name) { - int bufferLength = name.Length * 2; - char[]? buffer = bufferLength > 512 + // Rented buffer 20% longer that the input. + int bufferLength = (12 * name.Length) / 10; + char[]? buffer = bufferLength > JsonConstants.StackallocCharThreshold ? ArrayPool.Shared.Rent(bufferLength) : null; int resultLength = 0; Span result = buffer is null - ? stackalloc char[512] + ? stackalloc char[JsonConstants.StackallocCharThreshold] : buffer; - void WriteWord(ref Span result, ReadOnlySpan word) + void ExpandBuffer(ref Span result) + { + var bufferNew = ArrayPool.Shared.Rent(bufferLength *= 2); + + result.CopyTo(bufferNew); + + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer, clearArray: true); + } + + buffer = bufferNew; + result = buffer; + } + + void WriteWord(ReadOnlySpan word, ref Span result) { if (word.IsEmpty) + { return; + } - int required = result.IsEmpty - ? word.Length - : word.Length + 1; + Span destination = result.Slice(resultLength != 0 + ? resultLength + 1 + : resultLength); - if (required >= result.Length) + int written; + while (true) { - int bufferLength = result.Length * 2; - char[] bufferNew = ArrayPool.Shared.Rent(bufferLength); - - result.CopyTo(bufferNew); + written = _lowercase + ? word.ToLowerInvariant(destination) + : word.ToUpperInvariant(destination); - if (buffer is not null) - ArrayPool.Shared.Return(buffer); + if (written > 0) + { + break; + } - buffer = bufferNew; + ExpandBuffer(ref result); } if (resultLength != 0) @@ -54,18 +74,7 @@ void WriteWord(ref Span result, ReadOnlySpan word) resultLength += 1; } - Span destination = result.Slice(resultLength); - - if (_lowercase) - { - word.ToLowerInvariant(destination); - } - else - { - word.ToUpperInvariant(destination); - } - - resultLength += word.Length; + resultLength += written; } int first = 0; @@ -81,7 +90,7 @@ void WriteWord(ref Span result, ReadOnlySpan word) currentCategoryUnicode >= UnicodeCategory.ConnectorPunctuation && currentCategoryUnicode <= UnicodeCategory.OtherPunctuation) { - WriteWord(ref result, chars.Slice(first, index - first)); + WriteWord(chars.Slice(first, index - first), ref result); previousCategory = CharCategory.Boundary; first = index + 1; @@ -102,7 +111,7 @@ void WriteWord(ref Span result, ReadOnlySpan word) if (currentCategory == CharCategory.Lowercase && char.IsUpper(next) || next == '_') { - WriteWord(ref result, chars.Slice(first, index - first + 1)); + WriteWord(chars.Slice(first, index - first + 1), ref result); previousCategory = CharCategory.Boundary; first = index + 1; @@ -114,7 +123,7 @@ void WriteWord(ref Span result, ReadOnlySpan word) currentCategoryUnicode == UnicodeCategory.UppercaseLetter && char.IsLower(next)) { - WriteWord(ref result, chars.Slice(first, index - first)); + WriteWord(chars.Slice(first, index - first), ref result); previousCategory = CharCategory.Boundary; first = index; @@ -126,14 +135,14 @@ void WriteWord(ref Span result, ReadOnlySpan word) } } - WriteWord(ref result, chars.Slice(first)); + WriteWord(chars.Slice(first), ref result); - name = result - .Slice(0, resultLength) - .ToString(); + name = result.Slice(0, resultLength).ToString(); if (buffer is not null) - ArrayPool.Shared.Return(buffer); + { + ArrayPool.Shared.Return(buffer, clearArray: true); + } return name; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs index 421b71e80f0494..97746bc0ad6b00 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs @@ -50,9 +50,6 @@ internal static partial class JsonConstants public const int SpacesPerIndent = 2; public const int RemoveFlagsBitMask = 0x7FFFFFFF; - public const int StackallocByteThreshold = 256; - public const int StackallocCharThreshold = StackallocByteThreshold / 2; - // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. // For example: '+' becomes '\u0043' // Escaping surrogate pairs (represented by 3 or 4 utf-8 bytes) would expand to 12 bytes (which is still <= 6x). From 393ddb53342e0882a861521f7d432d4a3f30fbb8 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Wed, 12 Oct 2022 16:55:48 +0300 Subject: [PATCH 13/18] Made ConvertName sealed Co-authored-by: Eirik Tsarpalis --- .../System.Text.Json/Common/JsonSeparatorNamingPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs index 867df219ee5b06..69f1d5024b7646 100644 --- a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -14,7 +14,7 @@ internal abstract class JsonSeparatorNamingPolicy : JsonNamingPolicy internal JsonSeparatorNamingPolicy(bool lowercase, char separator) => (_lowercase, _separator) = (lowercase, separator); - public override string ConvertName(string name) + public sealed override string ConvertName(string name) { // Rented buffer 20% longer that the input. int bufferLength = (12 * name.Length) / 10; From ef7524d840c9672670922cbf336d155b18c79acb Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Fri, 14 Oct 2022 23:35:26 +0300 Subject: [PATCH 14/18] Explicit variable type --- .../System.Text.Json/Common/JsonSeparatorNamingPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs index 69f1d5024b7646..75cda4b4373500 100644 --- a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -29,7 +29,7 @@ public sealed override string ConvertName(string name) void ExpandBuffer(ref Span result) { - var bufferNew = ArrayPool.Shared.Rent(bufferLength *= 2); + char[] bufferNew = ArrayPool.Shared.Rent(result.Length * 2); result.CopyTo(bufferNew); From ead9f874e3772ac1e813e26ba88ca0d265cb622e Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Mon, 17 Oct 2022 16:26:34 +0300 Subject: [PATCH 15/18] Clear only a dirty part of the buffer --- .../Common/JsonSeparatorNamingPolicy.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs index 75cda4b4373500..f6cb53a0e15155 100644 --- a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -17,29 +17,30 @@ internal JsonSeparatorNamingPolicy(bool lowercase, char separator) => public sealed override string ConvertName(string name) { // Rented buffer 20% longer that the input. - int bufferLength = (12 * name.Length) / 10; - char[]? buffer = bufferLength > JsonConstants.StackallocCharThreshold - ? ArrayPool.Shared.Rent(bufferLength) + int rentedBufferLength = (12 * name.Length) / 10; + char[]? rentedBuffer = rentedBufferLength > JsonConstants.StackallocCharThreshold + ? ArrayPool.Shared.Rent(rentedBufferLength) : null; int resultLength = 0; - Span result = buffer is null + Span result = rentedBuffer is null ? stackalloc char[JsonConstants.StackallocCharThreshold] - : buffer; + : rentedBuffer; void ExpandBuffer(ref Span result) { - char[] bufferNew = ArrayPool.Shared.Rent(result.Length * 2); + char[] newBuffer = ArrayPool.Shared.Rent(result.Length * 2); - result.CopyTo(bufferNew); + result.CopyTo(newBuffer); - if (buffer is not null) + if (rentedBuffer is not null) { - ArrayPool.Shared.Return(buffer, clearArray: true); + result.Slice(0, resultLength).Clear(); + ArrayPool.Shared.Return(rentedBuffer); } - buffer = bufferNew; - result = buffer; + rentedBuffer = newBuffer; + result = rentedBuffer; } void WriteWord(ReadOnlySpan word, ref Span result) @@ -139,9 +140,10 @@ void WriteWord(ReadOnlySpan word, ref Span result) name = result.Slice(0, resultLength).ToString(); - if (buffer is not null) + if (rentedBuffer is not null) { - ArrayPool.Shared.Return(buffer, clearArray: true); + result.Slice(0, resultLength).Clear(); + ArrayPool.Shared.Return(rentedBuffer); } return name; From c11d048bac9cd0185b7fa27f35615b5a5d212796 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Wed, 19 Oct 2022 11:17:31 +0300 Subject: [PATCH 16/18] Fixed exception on slicing more that exists --- .../Common/JsonSeparatorNamingPolicy.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs index f6cb53a0e15155..9908045cc7a80c 100644 --- a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -50,20 +50,25 @@ void WriteWord(ReadOnlySpan word, ref Span result) return; } - Span destination = result.Slice(resultLength != 0 - ? resultLength + 1 - : resultLength); - int written; while (true) { - written = _lowercase - ? word.ToLowerInvariant(destination) - : word.ToUpperInvariant(destination); + var destinationOffset = resultLength != 0 + ? resultLength + 1 + : resultLength; - if (written > 0) + if (destinationOffset < result.Length) { - break; + Span destination = result.Slice(destinationOffset); + + written = _lowercase + ? word.ToLowerInvariant(destination) + : word.ToUpperInvariant(destination); + + if (written > 0) + { + break; + } } ExpandBuffer(ref result); From f9d3bed3938cdef15b09a152cb5b50a7ffc05010 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Wed, 19 Oct 2022 11:19:57 +0300 Subject: [PATCH 17/18] Better variable name --- .../Common/JsonSeparatorNamingPolicy.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs index 9908045cc7a80c..9e54127c65ad95 100644 --- a/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -22,7 +22,7 @@ public sealed override string ConvertName(string name) ? ArrayPool.Shared.Rent(rentedBufferLength) : null; - int resultLength = 0; + int resultUsedLength = 0; Span result = rentedBuffer is null ? stackalloc char[JsonConstants.StackallocCharThreshold] : rentedBuffer; @@ -35,7 +35,7 @@ void ExpandBuffer(ref Span result) if (rentedBuffer is not null) { - result.Slice(0, resultLength).Clear(); + result.Slice(0, resultUsedLength).Clear(); ArrayPool.Shared.Return(rentedBuffer); } @@ -53,9 +53,9 @@ void WriteWord(ReadOnlySpan word, ref Span result) int written; while (true) { - var destinationOffset = resultLength != 0 - ? resultLength + 1 - : resultLength; + var destinationOffset = resultUsedLength != 0 + ? resultUsedLength + 1 + : resultUsedLength; if (destinationOffset < result.Length) { @@ -74,13 +74,13 @@ void WriteWord(ReadOnlySpan word, ref Span result) ExpandBuffer(ref result); } - if (resultLength != 0) + if (resultUsedLength != 0) { - result[resultLength] = _separator; - resultLength += 1; + result[resultUsedLength] = _separator; + resultUsedLength += 1; } - resultLength += written; + resultUsedLength += written; } int first = 0; @@ -143,11 +143,11 @@ void WriteWord(ReadOnlySpan word, ref Span result) WriteWord(chars.Slice(first), ref result); - name = result.Slice(0, resultLength).ToString(); + name = result.Slice(0, resultUsedLength).ToString(); if (rentedBuffer is not null) { - result.Slice(0, resultLength).Clear(); + result.Slice(0, resultUsedLength).Clear(); ArrayPool.Shared.Return(rentedBuffer); } From febb3c4409249af7ec7e50804ccfd366575129be Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Wed, 19 Oct 2022 12:30:06 +0300 Subject: [PATCH 18/18] End-to-end serialization tests --- .../tests/Common/PropertyNameTests.cs | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs index 568357a5088b1d..e20233a678f6c0 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs @@ -15,40 +15,54 @@ public abstract partial class PropertyNameTests : SerializerTests public PropertyNameTests(JsonSerializerWrapper serializerWrapper) : base(serializerWrapper) { } [Fact] - public async Task CamelCaseDeserializeNoMatch() + public async Task BuiltInPolicyDeserializeNoMatch() { - var options = new JsonSerializerOptions(); - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - - SimpleTestClass obj = await Serializer.DeserializeWrapper(@"{""MyInt16"":1}", options); - - // This is 0 (default value) because the data does not match the property "MyInt16" that is assuming camel-casing of "myInt16". - Assert.Equal(0, obj.MyInt16); + // This is 0 (default value) because the data does not match the property "MyInt16" using the specified policy. + await DeserializeAndAssert(JsonNamingPolicy.CamelCase, @"{""MyInt16"":1}", 0); + await DeserializeAndAssert(JsonNamingPolicy.SnakeCaseLower, @"{""MyInt16"":1}", 0); + await DeserializeAndAssert(JsonNamingPolicy.SnakeCaseUpper, @"{""MyInt16"":1}", 0); + await DeserializeAndAssert(JsonNamingPolicy.KebabCaseLower, @"{""MyInt16"":1}", 0); + await DeserializeAndAssert(JsonNamingPolicy.KebabCaseUpper, @"{""MyInt16"":1}", 0); } [Fact] - public async Task CamelCaseDeserializeMatch() + public async Task BuiltInPolicyDeserializeMatch() { - var options = new JsonSerializerOptions(); - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + // This is 1 because the data matches the property "MyInt16" using the specified policy. + await DeserializeAndAssert(JsonNamingPolicy.CamelCase, @"{""myInt16"":1}", 1); + await DeserializeAndAssert(JsonNamingPolicy.SnakeCaseLower, @"{""my_int16"":1}", 1); + await DeserializeAndAssert(JsonNamingPolicy.SnakeCaseUpper, @"{""MY_INT16"":1}", 1); + await DeserializeAndAssert(JsonNamingPolicy.KebabCaseLower, @"{""my-int16"":1}", 1); + await DeserializeAndAssert(JsonNamingPolicy.KebabCaseUpper, @"{""MY-INT16"":1}", 1); + } - SimpleTestClass obj = await Serializer.DeserializeWrapper(@"{""myInt16"":1}", options); + private async Task DeserializeAndAssert(JsonNamingPolicy policy, string json, short expected) + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = policy }; + var obj = await Serializer.DeserializeWrapper(json, options); - // This is 1 because the data matches the property "MyInt16" that is assuming camel-casing of "myInt16". - Assert.Equal(1, obj.MyInt16); + Assert.Equal(expected, obj.MyInt16); } [Fact] - public async Task CamelCaseSerialize() + public async Task BuiltInPolicySerialize() { - var options = new JsonSerializerOptions(); - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + await SerializeAndAssert(JsonNamingPolicy.CamelCase, @"""myInt16"":0", @"""myInt32"":0"); + await SerializeAndAssert(JsonNamingPolicy.SnakeCaseLower, @"""my_int16"":0", @"""my_int32"":0"); + await SerializeAndAssert(JsonNamingPolicy.SnakeCaseUpper, @"""MY_INT16"":0", @"""MY_INT32"":0"); + await SerializeAndAssert(JsonNamingPolicy.KebabCaseLower, @"""my-int16"":0", @"""my-int32"":0"); + await SerializeAndAssert(JsonNamingPolicy.KebabCaseUpper, @"""MY-INT16"":0", @"""MY-INT32"":0"); - SimpleTestClass obj = await Serializer.DeserializeWrapper(@"{}", options); + async Task SerializeAndAssert(JsonNamingPolicy policy, string myInt16, string myInt32) + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = policy }; + var obj = await Serializer.DeserializeWrapper(@"{}", options); - string json = await Serializer.SerializeWrapper(obj, options); - Assert.Contains(@"""myInt16"":0", json); - Assert.Contains(@"""myInt32"":0", json); + string json = await Serializer.SerializeWrapper(obj, options); + + Assert.Contains(myInt16, json); + Assert.Contains(myInt32, json); + } } [Fact]