From 24813dc41dd3d573458e710292a109f8ffa4f415 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 20 Oct 2022 15:26:54 +0300 Subject: [PATCH] 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 * 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 * 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 Co-authored-by: Eirik Tsarpalis --- .../System.Text.Json/Common/JsonConstants.cs | 3 + .../Common/JsonKebabCaseLowerNamingPolicy.cs | 13 + .../Common/JsonKebabCaseUpperNamingPolicy.cs | 13 + .../Common/JsonKnownNamingPolicy.cs | 22 +- .../Common/JsonNamingPolicy.cs | 20 ++ .../Common/JsonSeparatorNamingPolicy.cs | 164 ++++++++++++ .../Common/JsonSnakeCaseLowerNamingPolicy.cs | 13 + .../Common/JsonSnakeCaseUpperNamingPolicy.cs | 13 + .../gen/JsonSourceGenerator.Emitter.cs | 14 +- .../gen/JsonSourceGenerator.Parser.cs | 16 +- .../System.Text.Json.SourceGeneration.targets | 5 + .../System.Text.Json/ref/System.Text.Json.cs | 8 + .../src/System.Text.Json.csproj | 5 + .../src/System/Text/Json/JsonConstants.cs | 3 - .../tests/Common/PropertyNameTests.cs | 56 +++-- .../Serialization/CamelCaseUnitTests.cs | 52 ---- .../Serialization/NamingPolicyUnitTests.cs | 235 ++++++++++++++++++ .../System.Text.Json.Tests.csproj | 2 +- 18 files changed, 572 insertions(+), 85 deletions(-) create mode 100644 src/libraries/System.Text.Json/Common/JsonKebabCaseLowerNamingPolicy.cs create mode 100644 src/libraries/System.Text.Json/Common/JsonKebabCaseUpperNamingPolicy.cs create mode 100644 src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs create mode 100644 src/libraries/System.Text.Json/Common/JsonSnakeCaseLowerNamingPolicy.cs create mode 100644 src/libraries/System.Text.Json/Common/JsonSnakeCaseUpperNamingPolicy.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/JsonConstants.cs b/src/libraries/System.Text.Json/Common/JsonConstants.cs index 63bf065d12687..4a7209f9a4aba 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/JsonKebabCaseLowerNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKebabCaseLowerNamingPolicy.cs new file mode 100644 index 0000000000000..ebb380d34dd63 --- /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 0000000000000..1bd58a6e0b07f --- /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 73868fcd7c935..4c414544759c4 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. + /// + SnakeCaseLower = 2, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + SnakeCaseUpper = 3, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + KebabCaseLower = 4, + + /// + /// Specifies that the built-in be used to convert JSON property names. + /// + KebabCaseUpper = 5 } } diff --git a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs index ee0a86c2321bc..5abfa4621eb17 100644 --- a/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs @@ -23,6 +23,26 @@ protected JsonNamingPolicy() { } /// public static JsonNamingPolicy CamelCase { get; } = new JsonCamelCaseNamingPolicy(); + /// + /// Returns the naming policy for lower snake-casing. + /// + public static JsonNamingPolicy SnakeCaseLower { get; } = new JsonSnakeCaseLowerNamingPolicy(); + + /// + /// Returns the naming policy for upper snake-casing. + /// + public static JsonNamingPolicy SnakeCaseUpper { get; } = new JsonSnakeCaseUpperNamingPolicy(); + + /// + /// Returns the naming policy for lower kebab-casing. + /// + public static JsonNamingPolicy KebabCaseLower { get; } = new JsonKebabCaseLowerNamingPolicy(); + + /// + /// Returns the naming policy for upper kebab-casing. + /// + 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/JsonSeparatorNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs new file mode 100644 index 0000000000000..9e54127c65ad9 --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs @@ -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.Shared.Rent(rentedBufferLength) + : null; + + int resultUsedLength = 0; + Span result = rentedBuffer is null + ? stackalloc char[JsonConstants.StackallocCharThreshold] + : rentedBuffer; + + void ExpandBuffer(ref Span result) + { + char[] newBuffer = ArrayPool.Shared.Rent(result.Length * 2); + + result.CopyTo(newBuffer); + + if (rentedBuffer is not null) + { + result.Slice(0, resultUsedLength).Clear(); + ArrayPool.Shared.Return(rentedBuffer); + } + + rentedBuffer = newBuffer; + result = rentedBuffer; + } + + void WriteWord(ReadOnlySpan word, ref Span result) + { + if (word.IsEmpty) + { + return; + } + + int written; + while (true) + { + var destinationOffset = resultUsedLength != 0 + ? resultUsedLength + 1 + : resultUsedLength; + + if (destinationOffset < result.Length) + { + Span 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 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.Shared.Return(rentedBuffer); + } + + return name; + } + + private enum CharCategory + { + Boundary, + Lowercase, + Uppercase, + } + } +} 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 0000000000000..f46488143dae3 --- /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 0000000000000..3f49d873f8960 --- /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 c5d949f29283a..fc3d569ff1fb3 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1170,9 +1170,19 @@ private string GetLogicForDefaultSerializerOptionsInit() { JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions; - string? namingPolicyInit = options.PropertyNamingPolicy == JsonKnownNamingPolicy.CamelCase + string? namingPolicyName = options.PropertyNamingPolicy switch + { + JsonKnownNamingPolicy.CamelCase => nameof(JsonNamingPolicy.CamelCase), + JsonKnownNamingPolicy.SnakeCaseLower => nameof(JsonNamingPolicy.SnakeCaseLower), + JsonKnownNamingPolicy.SnakeCaseUpper => nameof(JsonNamingPolicy.SnakeCaseUpper), + JsonKnownNamingPolicy.KebabCaseLower => nameof(JsonNamingPolicy.KebabCaseLower), + JsonKnownNamingPolicy.KebabCaseUpper => nameof(JsonNamingPolicy.KebabCaseUpper), + _ => 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 60a0be1265182..0230408f89ade 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.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower, + JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper, + JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower, + JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper, + _ => 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 596ce0c49163c..0b45537c27dfe 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,9 +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 39512b64fd74a..1eec8124a3976 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 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 @@ -914,6 +918,10 @@ public enum JsonKnownNamingPolicy { Unspecified = 0, CamelCase = 1, + 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 f0341a109304e..635be981ff62c 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -30,9 +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/src/System/Text/Json/JsonConstants.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs index 421b71e80f049..97746bc0ad6b0 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). diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs index 568357a5088b1..e20233a678f6c 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] 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 aebce3dcce17a..0000000000000 --- 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 0000000000000..6a62c2acdd1f9 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NamingPolicyUnitTests.cs @@ -0,0 +1,235 @@ +// 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 ")); + 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) + { + JsonNamingPolicy policy = JsonNamingPolicy.SnakeCaseLower; + 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 ")); + 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) + { + JsonNamingPolicy policy = JsonNamingPolicy.SnakeCaseUpper; + 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("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 ")); + 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) + { + JsonNamingPolicy policy = JsonNamingPolicy.KebabCaseLower; + 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 ")); + 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) + { + JsonNamingPolicy policy = JsonNamingPolicy.KebabCaseUpper; + 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 749f6364889ba..9c95305a2ea69 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 @@ +