using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+using System.Text.Json.Serialization;
namespace System.Text.Json
{
}
#endif
+ internal static bool RequiresSpecialNumberHandlingOnWrite(JsonNumberHandling? handling)
+ {
+ return handling != null
+ ? (handling.Value & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) != 0
+ : false;
+ }
+
/// <summary>
/// Provides an in-place, stable sorting implementation for List.
/// </summary>
private const string PreferredPropertyObjectCreationHandlingPropName = "PreferredPropertyObjectCreationHandling";
private const string ObjectCreatorPropName = "ObjectCreator";
private const string OptionsInstanceVariableName = "Options";
- private const string JsonTypeInfoReturnValueLocalVariableName = "jsonTypeInfo";
+ private const string JsonTypeInfoLocalVariableName = "jsonTypeInfo";
private const string PropInitMethodNameSuffix = "PropInit";
private const string TryGetTypeInfoForRuntimeCustomConverterMethodName = "TryGetTypeInfoForRuntimeCustomConverter";
private const string ExpandConverterMethodName = "ExpandConverter";
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
writer.WriteLine($"""
- {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.{typeInfoPropertyName}Converter);
+ {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.{typeInfoPropertyName}Converter);
""");
GenerateTypeInfoFactoryFooter(writer);
writer.WriteLine($"""
{JsonConverterTypeRef} converter = {ExpandConverterMethodName}(typeof({typeFQN}), new {converterFQN}(), {OptionsLocalVariableName});
- {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)} ({OptionsLocalVariableName}, converter);
+ {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)} ({OptionsLocalVariableName}, converter);
""");
GenerateTypeInfoFactoryFooter(writer);
writer.WriteLine($$"""
{{JsonConverterTypeRef}} converter = {{JsonMetadataServicesTypeRef}}.GetNullableConverter<{{underlyingTypeFQN}}>({{OptionsLocalVariableName}});
- {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}}({{OptionsLocalVariableName}}, converter);
+ {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}}({{OptionsLocalVariableName}}, converter);
""");
GenerateTypeInfoFactoryFooter(writer);
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
writer.WriteLine($"""
- {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeFQN}>());
+ {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeFQN}>());
""");
GenerateTypeInfoFactoryFooter(writer);
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
writer.WriteLine($"""
- {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeFQN}>({OptionsLocalVariableName}));
+ {JsonTypeInfoLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeFQN}>({OptionsLocalVariableName}));
""");
GenerateTypeInfoFactoryFooter(writer);
var {{InfoVarName}} = new {{JsonCollectionInfoValuesTypeRef}}<{{typeFQN}}>
{
{{ObjectCreatorPropName}} = {{FormatDefaultConstructorExpr(typeGenerationSpec)}},
- {{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}},
{{SerializeHandlerPropName}} = {{serializeMethodName ?? "null"}}
};
- {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{createCollectionMethodExpr}};
+ {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{createCollectionMethodExpr}};
+ {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}};
""");
GenerateTypeInfoFactoryFooter(writer);
ObjectWithParameterizedConstructorCreator = {{parameterizedCreatorInvocation}},
PropertyMetadataInitializer = {{propInitAdapterFunc ?? "null"}},
ConstructorParameterMetadataInitializer = {{ctorParamMetadataInitMethodName ?? "null"}},
- {{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}},
{{SerializeHandlerPropName}} = {{serializeMethodName ?? "null"}}
};
- {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}});
+ {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}});
+ {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}};
""");
if (typeMetadata is { UnmappedMemberHandling: not null } or { PreferredPropertyObjectCreationHandling: not null })
if (typeMetadata.UnmappedMemberHandling != null)
{
- writer.WriteLine($"{JsonTypeInfoReturnValueLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};");
+ writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};");
}
if (typeMetadata.PreferredPropertyObjectCreationHandling != null)
{
- writer.WriteLine($"{JsonTypeInfoReturnValueLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};");
+ writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};");
}
}
private {{typeInfoFQN}} {{CreateTypeInfoMethodName(typeMetadata)}}({{JsonSerializerOptionsTypeRef}} {{OptionsLocalVariableName}})
{
- if (!{{TryGetTypeInfoForRuntimeCustomConverterMethodName}}<{{typeFQN}}>({{OptionsLocalVariableName}}, out {{typeInfoFQN}} {{JsonTypeInfoReturnValueLocalVariableName}}))
+ if (!{{TryGetTypeInfoForRuntimeCustomConverterMethodName}}<{{typeFQN}}>({{OptionsLocalVariableName}}, out {{typeInfoFQN}} {{JsonTypeInfoLocalVariableName}}))
{
""");
writer.WriteLine($$"""
}
- {{JsonTypeInfoReturnValueLocalVariableName}}.{{OriginatingResolverPropertyName}} = this;
- return {{JsonTypeInfoReturnValueLocalVariableName}};
+ {{JsonTypeInfoLocalVariableName}}.{{OriginatingResolverPropertyName}} = this;
+ return {{JsonTypeInfoLocalVariableName}};
}
""");
}
private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) =>
numberHandling switch
{
- null => "default",
+ null => "null",
>= 0 => $"({JsonNumberHandlingTypeRef}){(int)numberHandling.Value}",
< 0 => $"({JsonNumberHandlingTypeRef})({(int)numberHandling.Value})"
};
return false;
}
+ if (JsonHelpers.RequiresSpecialNumberHandlingOnWrite(NumberHandling))
+ {
+ return false;
+ }
+
switch (ClassType)
{
case ClassType.Object:
foreach (PropertyGenerationSpec property in PropertyGenSpecs)
{
if (property.PropertyType.SpecialType is SpecialType.System_Object ||
- property.NumberHandling is JsonNumberHandling.AllowNamedFloatingPointLiterals
- or JsonNumberHandling.WriteAsString ||
+ property.NumberHandling != null ||
property.ConverterType != null)
{
return false;
if (!state.SupportContinuation &&
jsonTypeInfo.CanUseSerializeHandler &&
+ !JsonHelpers.RequiresSpecialNumberHandlingOnWrite(state.Current.NumberHandling) &&
!state.CurrentContainsMetadata) // Do not use the fast path if state needs to write metadata.
{
((JsonTypeInfo<T>)jsonTypeInfo).SerializeHandler!(writer, value);
options.Encoder == null &&
// Disallow custom number handling we'd need to honor when writing.
// AllowReadingFromString and Strict are fine since there's no action to take when writing.
- (options.NumberHandling & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals)) == 0 &&
+ !JsonHelpers.RequiresSpecialNumberHandlingOnWrite(options.NumberHandling) &&
options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.None &&
#pragma warning disable SYSLIB0020
!options.IgnoreNullValues && // This property is obsolete.
{
internal static partial class JsonTestHelper
{
+#if NETCOREAPP
+ public const string DoubleFormatString = null;
+ public const string SingleFormatString = null;
+#else
+ public const string DoubleFormatString = "G17";
+ public const string SingleFormatString = "G9";
+#endif
+
+ public static float NextFloat(Random random)
+ {
+ double mantissa = (random.NextDouble() * 2.0) - 1.0;
+ double exponent = Math.Pow(2.0, random.Next(-126, 128));
+ float value = (float)(mantissa * exponent);
+ return value;
+ }
+
+ public static double NextDouble(Random random, double minValue, double maxValue)
+ {
+ double value = random.NextDouble() * (maxValue - minValue) + minValue;
+ return value;
+ }
+
+ public static decimal NextDecimal(Random random, double minValue, double maxValue)
+ {
+ double value = random.NextDouble() * (maxValue - minValue) + minValue;
+ return (decimal)value;
+ }
+
public static void AssertJsonEqual(string expected, string actual)
{
using JsonDocument expectedDom = JsonDocument.Parse(expected);
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Linq;
+using System.Text.Encodings.Web;
+using System.Text.Json.Tests;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+ public abstract class NumberHandlingTests : SerializerTests
+ {
+ public NumberHandlingTests(JsonSerializerWrapper serializerWrapper) : base(serializerWrapper) { }
+
+ private static readonly JsonSerializerOptions s_optionReadFromStr = new JsonSerializerOptions
+ {
+ NumberHandling = JsonNumberHandling.AllowReadingFromString
+ };
+
+ private static readonly JsonSerializerOptions s_optionWriteAsStr = new JsonSerializerOptions
+ {
+ NumberHandling = JsonNumberHandling.WriteAsString
+ };
+
+ private static readonly JsonSerializerOptions s_optionReadAndWriteFromStr = new JsonSerializerOptions
+ {
+ NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString
+ };
+
+ private static readonly JsonSerializerOptions s_optionsAllowFloatConstants = new JsonSerializerOptions
+ {
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
+ };
+
+ private static readonly JsonSerializerOptions s_optionReadFromStrAllowFloatConstants = new JsonSerializerOptions
+ {
+ NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals
+ };
+
+ private static readonly JsonSerializerOptions s_optionWriteAsStrAllowFloatConstants = new JsonSerializerOptions
+ {
+ NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals
+ };
+
+ [Fact]
+ public async Task Number_AsRootType_RoundTrip()
+ {
+ await RunAsRootTypeTest(JsonNumberTestData.Bytes);
+ await RunAsRootTypeTest(JsonNumberTestData.SBytes);
+ await RunAsRootTypeTest(JsonNumberTestData.Shorts);
+ await RunAsRootTypeTest(JsonNumberTestData.Ints);
+ await RunAsRootTypeTest(JsonNumberTestData.Longs);
+ await RunAsRootTypeTest(JsonNumberTestData.UShorts);
+ await RunAsRootTypeTest(JsonNumberTestData.UInts);
+ await RunAsRootTypeTest(JsonNumberTestData.ULongs);
+ await RunAsRootTypeTest(JsonNumberTestData.Floats);
+ await RunAsRootTypeTest(JsonNumberTestData.Doubles);
+ await RunAsRootTypeTest(JsonNumberTestData.Decimals);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableBytes);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableSBytes);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableShorts);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableInts);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableLongs);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableUShorts);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableUInts);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableULongs);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableFloats);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableDoubles);
+ await RunAsRootTypeTest(JsonNumberTestData.NullableDecimals);
+ }
+
+ private async Task RunAsRootTypeTest<T>(List<T> numbers)
+ {
+ foreach (T number in numbers)
+ {
+ string numberAsString = GetNumberAsString(number);
+ string json = $"{numberAsString}";
+ string jsonWithNumberAsString = @$"""{numberAsString}""";
+ await PerformAsRootTypeSerialization(number, json, jsonWithNumberAsString);
+ }
+ }
+
+ private static string GetNumberAsString<T>(T number)
+ {
+ return number switch
+ {
+ double @double => @double.ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture),
+ float @float => @float.ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture),
+ decimal @decimal => @decimal.ToString(CultureInfo.InvariantCulture),
+ _ => number.ToString()
+ };
+ }
+
+ private async Task PerformAsRootTypeSerialization<T>(T number, string jsonWithNumberAsNumber, string jsonWithNumberAsString)
+ {
+ // Option: read from string
+
+ // Deserialize
+ Assert.Equal(number, await Serializer.DeserializeWrapper<T>(jsonWithNumberAsNumber, s_optionReadFromStr));
+ Assert.Equal(number, await Serializer.DeserializeWrapper<T>(jsonWithNumberAsString, s_optionReadFromStr));
+
+ // Serialize
+ Assert.Equal(jsonWithNumberAsNumber, await Serializer.SerializeWrapper(number, s_optionReadFromStr));
+
+ // Option: write as string
+
+ // Deserialize
+ Assert.Equal(number, await Serializer.DeserializeWrapper<T>(jsonWithNumberAsNumber, s_optionWriteAsStr));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<T>(jsonWithNumberAsString, s_optionWriteAsStr));
+
+ // Serialize
+ Assert.Equal(jsonWithNumberAsString, await Serializer.SerializeWrapper(number, s_optionWriteAsStr));
+
+ // Option: read and write from/to string
+
+ // Deserialize
+ Assert.Equal(number, await Serializer.DeserializeWrapper<T>(jsonWithNumberAsNumber, s_optionReadAndWriteFromStr));
+ Assert.Equal(number, await Serializer.DeserializeWrapper<T>(jsonWithNumberAsString, s_optionReadAndWriteFromStr));
+
+ // Serialize
+ Assert.Equal(jsonWithNumberAsString, await Serializer.SerializeWrapper(number, s_optionReadAndWriteFromStr));
+ }
+
+ [Fact]
+ public async Task Number_AsBoxed_RootType()
+ {
+ string numberAsString = @"""2""";
+
+ int @int = 2;
+ float @float = 2;
+ int? nullableInt = 2;
+ float? nullableFloat = 2;
+
+ Assert.Equal(numberAsString, await Serializer.SerializeWrapper((object)@int, s_optionReadAndWriteFromStr));
+ Assert.Equal(numberAsString, await Serializer.SerializeWrapper((object)@float, s_optionReadAndWriteFromStr));
+ Assert.Equal(numberAsString, await Serializer.SerializeWrapper((object)nullableInt, s_optionReadAndWriteFromStr));
+ Assert.Equal(numberAsString, await Serializer.SerializeWrapper((object)nullableFloat, s_optionReadAndWriteFromStr));
+
+ Assert.Equal(2, (int)await Serializer.DeserializeWrapper(numberAsString, typeof(int), s_optionReadAndWriteFromStr));
+ Assert.Equal(2, (float)await Serializer.DeserializeWrapper(numberAsString, typeof(float), s_optionReadAndWriteFromStr));
+ Assert.Equal(2, (int?)await Serializer.DeserializeWrapper(numberAsString, typeof(int?), s_optionReadAndWriteFromStr));
+ Assert.Equal(2, (float?)await Serializer.DeserializeWrapper(numberAsString, typeof(float?), s_optionReadAndWriteFromStr));
+ }
+
+ [Fact]
+ public async Task Number_AsBoxed_Property()
+ {
+ int @int = 1;
+ float? nullableFloat = 2;
+
+ string expected = @"{""MyInt"":""1"",""MyNullableFloat"":""2""}";
+
+ var obj = new Class_With_BoxedNumbers
+ {
+ MyInt = @int,
+ MyNullableFloat = nullableFloat
+ };
+
+ string serialized = await Serializer.SerializeWrapper(obj);
+ JsonTestHelper.AssertJsonEqual(expected, serialized);
+
+ obj = await Serializer.DeserializeWrapper<Class_With_BoxedNumbers>(serialized);
+
+ JsonElement el = Assert.IsType<JsonElement>(obj.MyInt);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal("1", el.GetString());
+
+ el = Assert.IsType<JsonElement>(obj.MyNullableFloat);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal("2", el.GetString());
+ }
+
+ public class Class_With_BoxedNumbers
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public object MyInt { get; set; }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public object MyNullableFloat { get; set; }
+ }
+
+ [Fact]
+ public async Task Number_AsBoxed_CollectionRootType_Element()
+ {
+ int @int = 1;
+ float? nullableFloat = 2;
+
+ string expected = @"[""1""]";
+
+ var obj = new List<object> { @int };
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+ Assert.Equal(expected, serialized);
+
+ obj = await Serializer.DeserializeWrapper<List<object>>(serialized, s_optionReadAndWriteFromStr);
+
+ JsonElement el = Assert.IsType<JsonElement>(obj[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal("1", el.GetString());
+
+ expected = @"[""2""]";
+
+ IList obj2 = new object[] { nullableFloat };
+ serialized = await Serializer.SerializeWrapper(obj2, s_optionReadAndWriteFromStr);
+ Assert.Equal(expected, serialized);
+
+ obj2 = await Serializer.DeserializeWrapper<IList>(serialized, s_optionReadAndWriteFromStr);
+
+ el = Assert.IsType<JsonElement>(obj2[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal("2", el.GetString());
+ }
+
+ [Fact]
+ public async Task Number_AsBoxed_CollectionProperty_Element()
+ {
+ int @int = 2;
+ float? nullableFloat = 2;
+
+ string expected = @"{""MyInts"":[""2""],""MyNullableFloats"":[""2""]}";
+
+ var obj = new Class_With_ListsOfBoxedNumbers
+ {
+ MyInts = new List<object> { @int },
+ MyNullableFloats = new object[] { nullableFloat }
+ };
+
+ string serialized = await Serializer.SerializeWrapper(obj);
+ JsonTestHelper.AssertJsonEqual(expected, serialized);
+
+ obj = await Serializer.DeserializeWrapper<Class_With_ListsOfBoxedNumbers>(serialized);
+
+ JsonElement el = Assert.IsType<JsonElement>(obj.MyInts[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal("2", el.GetString());
+
+ el = Assert.IsType<JsonElement>(obj.MyNullableFloats[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal("2", el.GetString());
+ }
+
+ public class Class_With_ListsOfBoxedNumbers
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public List<object> MyInts { get; set; }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public IList MyNullableFloats { get; set; }
+ }
+
+ [Fact]
+ public async Task NonNumber_AsBoxed_Property()
+ {
+ DateTime dateTime = DateTimeTestHelpers.FixedDateTimeValue;
+ Guid? nullableGuid = Guid.NewGuid();
+
+ string expected = @$"{{""MyDateTime"":{await Serializer.SerializeWrapper(dateTime)},""MyNullableGuid"":{await Serializer.SerializeWrapper(nullableGuid)}}}";
+
+ var obj = new Class_With_BoxedNonNumbers
+ {
+ MyDateTime = dateTime,
+ MyNullableGuid = nullableGuid
+ };
+
+ string serialized = await Serializer.SerializeWrapper(obj);
+ JsonTestHelper.AssertJsonEqual(expected, serialized);
+
+ obj = await Serializer.DeserializeWrapper<Class_With_BoxedNonNumbers>(serialized);
+
+ JsonElement el = Assert.IsType<JsonElement>(obj.MyDateTime);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal(dateTime, el.GetDateTime());
+
+ el = Assert.IsType<JsonElement>(obj.MyNullableGuid);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal(nullableGuid.Value, el.GetGuid());
+ }
+
+ public class Class_With_BoxedNonNumbers
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public object MyDateTime { get; set; }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public object MyNullableGuid { get; set; }
+ }
+
+ [Fact]
+ public async Task NonNumber_AsBoxed_CollectionRootType_Element()
+ {
+ DateTime dateTime = DateTimeTestHelpers.FixedDateTimeValue;
+ Guid? nullableGuid = Guid.NewGuid();
+
+ string expected = @$"[{await Serializer.SerializeWrapper(dateTime)}]";
+
+ var obj = new List<object> { dateTime };
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+ Assert.Equal(expected, serialized);
+
+ obj = await Serializer.DeserializeWrapper<List<object>>(serialized, s_optionReadAndWriteFromStr);
+
+ JsonElement el = Assert.IsType<JsonElement>(obj[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal(dateTime, el.GetDateTime());
+
+ expected = @$"[{await Serializer.SerializeWrapper(nullableGuid)}]";
+
+ IList obj2 = new object[] { nullableGuid };
+ serialized = await Serializer.SerializeWrapper(obj2, s_optionReadAndWriteFromStr);
+ Assert.Equal(expected, serialized);
+
+ obj2 = await Serializer.DeserializeWrapper<IList>(serialized, s_optionReadAndWriteFromStr);
+
+ el = Assert.IsType<JsonElement>(obj2[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal(nullableGuid.Value, el.GetGuid());
+ }
+
+ [Fact]
+ public async Task NonNumber_AsBoxed_CollectionProperty_Element()
+ {
+ DateTime dateTime = DateTimeTestHelpers.FixedDateTimeValue;
+ Guid? nullableGuid = Guid.NewGuid();
+
+ string expected = @$"{{""MyDateTimes"":[{await Serializer.SerializeWrapper(dateTime)}],""MyNullableGuids"":[{await Serializer.SerializeWrapper(nullableGuid)}]}}";
+
+ var obj = new Class_With_ListsOfBoxedNonNumbers
+ {
+ MyDateTimes = new List<object> { dateTime },
+ MyNullableGuids = new object[] { nullableGuid }
+ };
+
+ string serialized = await Serializer.SerializeWrapper(obj);
+ JsonTestHelper.AssertJsonEqual(expected, serialized);
+
+ obj = await Serializer.DeserializeWrapper<Class_With_ListsOfBoxedNonNumbers>(serialized);
+
+ JsonElement el = Assert.IsType<JsonElement>(obj.MyDateTimes[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal(dateTime, el.GetDateTime());
+
+ el = Assert.IsType<JsonElement>(obj.MyNullableGuids[0]);
+ Assert.Equal(JsonValueKind.String, el.ValueKind);
+ Assert.Equal(nullableGuid, el.GetGuid());
+ }
+
+ public class Class_With_ListsOfBoxedNonNumbers
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public List<object> MyDateTimes { get; set; }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public IList MyNullableGuids { get; set; }
+ }
+
+ [Fact]
+ public async Task Number_AsCollectionElement_RoundTrip()
+ {
+ await RunAsCollectionElementTest(JsonNumberTestData.Bytes);
+ await RunAsCollectionElementTest(JsonNumberTestData.SBytes);
+ await RunAsCollectionElementTest(JsonNumberTestData.Shorts);
+ await RunAsCollectionElementTest(JsonNumberTestData.Ints);
+ await RunAsCollectionElementTest(JsonNumberTestData.Longs);
+ await RunAsCollectionElementTest(JsonNumberTestData.UShorts);
+ await RunAsCollectionElementTest(JsonNumberTestData.UInts);
+ await RunAsCollectionElementTest(JsonNumberTestData.ULongs);
+ await RunAsCollectionElementTest(JsonNumberTestData.Floats);
+ await RunAsCollectionElementTest(JsonNumberTestData.Doubles);
+ await RunAsCollectionElementTest(JsonNumberTestData.Decimals);
+
+ // https://github.com/dotnet/runtime/issues/66220
+ if (!PlatformDetection.IsAppleMobile)
+ {
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableBytes);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableSBytes);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableShorts);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableInts);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableLongs);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableUShorts);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableUInts);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableULongs);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableFloats);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableDoubles);
+ await RunAsCollectionElementTest(JsonNumberTestData.NullableDecimals);
+ }
+ }
+
+ private async Task RunAsCollectionElementTest<T>(List<T> numbers)
+ {
+ StringBuilder jsonBuilder_NumbersAsNumbers = new StringBuilder();
+ StringBuilder jsonBuilder_NumbersAsStrings = new StringBuilder();
+ StringBuilder jsonBuilder_NumbersAsNumbersAndStrings = new StringBuilder();
+ StringBuilder jsonBuilder_NumbersAsNumbersAndStrings_Alternate = new StringBuilder();
+ bool asNumber = false;
+
+ jsonBuilder_NumbersAsNumbers.Append("[");
+ jsonBuilder_NumbersAsStrings.Append("[");
+ jsonBuilder_NumbersAsNumbersAndStrings.Append("[");
+ jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Append("[");
+
+ foreach (T number in numbers)
+ {
+ string numberAsString = GetNumberAsString(number);
+
+ string jsonWithNumberAsString = @$"""{numberAsString}""";
+
+ jsonBuilder_NumbersAsNumbers.Append($"{numberAsString},");
+ jsonBuilder_NumbersAsStrings.Append($"{jsonWithNumberAsString},");
+ jsonBuilder_NumbersAsNumbersAndStrings.Append(asNumber
+ ? $"{numberAsString},"
+ : $"{jsonWithNumberAsString},");
+ jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Append(!asNumber
+ ? $"{numberAsString},"
+ : $"{jsonWithNumberAsString},");
+
+ asNumber = !asNumber;
+ }
+
+ jsonBuilder_NumbersAsNumbers.Remove(jsonBuilder_NumbersAsNumbers.Length - 1, 1);
+ jsonBuilder_NumbersAsStrings.Remove(jsonBuilder_NumbersAsStrings.Length - 1, 1);
+ jsonBuilder_NumbersAsNumbersAndStrings.Remove(jsonBuilder_NumbersAsNumbersAndStrings.Length - 1, 1);
+ jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Remove(jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Length - 1, 1);
+
+ jsonBuilder_NumbersAsNumbers.Append("]");
+ jsonBuilder_NumbersAsStrings.Append("]");
+ jsonBuilder_NumbersAsNumbersAndStrings.Append("]");
+ jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Append("]");
+
+ string jsonNumbersAsStrings = jsonBuilder_NumbersAsStrings.ToString();
+
+ await PerformAsCollectionElementSerialization(
+ numbers,
+ jsonBuilder_NumbersAsNumbers.ToString(),
+ jsonNumbersAsStrings,
+ jsonBuilder_NumbersAsNumbersAndStrings.ToString(),
+ jsonBuilder_NumbersAsNumbersAndStrings_Alternate.ToString());
+
+ // Reflection based tests for every collection type.
+ await RunAllCollectionsRoundTripTest<T>(jsonNumbersAsStrings);
+ }
+
+ private async Task PerformAsCollectionElementSerialization<T>(
+ List<T> numbers,
+ string json_NumbersAsNumbers,
+ string json_NumbersAsStrings,
+ string json_NumbersAsNumbersAndStrings,
+ string json_NumbersAsNumbersAndStrings_Alternate)
+ {
+ List<T> deserialized;
+
+ // Option: read from string
+
+ // Deserialize
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbers, s_optionReadFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsStrings, s_optionReadFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbersAndStrings, s_optionReadFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbersAndStrings_Alternate, s_optionReadFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ // Serialize
+ Assert.Equal(json_NumbersAsNumbers, await Serializer.SerializeWrapper(numbers, s_optionReadFromStr));
+
+ // Option: write as string
+
+ // Deserialize
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbers, s_optionWriteAsStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsStrings, s_optionWriteAsStr));
+
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbersAndStrings, s_optionWriteAsStr));
+
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbersAndStrings_Alternate, s_optionWriteAsStr));
+
+ // Serialize
+ Assert.Equal(json_NumbersAsStrings, await Serializer.SerializeWrapper(numbers, s_optionWriteAsStr));
+
+ // Option: read and write from/to string
+
+ // Deserialize
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbers, s_optionReadAndWriteFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsStrings, s_optionReadAndWriteFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbersAndStrings, s_optionReadAndWriteFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ deserialized = await Serializer.DeserializeWrapper<List<T>>(json_NumbersAsNumbersAndStrings_Alternate, s_optionReadAndWriteFromStr);
+ AssertIEnumerableEqual(numbers, deserialized);
+
+ // Serialize
+ Assert.Equal(json_NumbersAsStrings, await Serializer.SerializeWrapper(numbers, s_optionReadAndWriteFromStr));
+ }
+
+ private void AssertIEnumerableEqual<T>(IEnumerable<T> list1, IEnumerable<T> list2)
+ {
+ IEnumerator<T> enumerator1 = list1.GetEnumerator();
+ IEnumerator<T> enumerator2 = list2.GetEnumerator();
+
+ while (enumerator1.MoveNext())
+ {
+ enumerator2.MoveNext();
+ Assert.Equal(enumerator1.Current, enumerator2.Current);
+ }
+
+ Assert.False(enumerator2.MoveNext());
+ }
+
+ private async Task RunAllCollectionsRoundTripTest<T>(string json)
+ {
+ foreach (Type type in CollectionTestTypes.DeserializableGenericEnumerableTypes<T>())
+ {
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
+ {
+ HashSet<T> obj1 = (HashSet<T>)await Serializer.DeserializeWrapper(json, type, s_optionReadAndWriteFromStr);
+ string serialized = await Serializer.SerializeWrapper(obj1, s_optionReadAndWriteFromStr);
+
+ HashSet<T> obj2 = (HashSet<T>)await Serializer.DeserializeWrapper(serialized, type, s_optionReadAndWriteFromStr);
+
+ Assert.Equal(obj1.Count, obj2.Count);
+ foreach (T element in obj1)
+ {
+ Assert.True(obj2.Contains(element));
+ }
+ }
+ else if (type != typeof(byte[]))
+ {
+ object obj = await Serializer.DeserializeWrapper(json, type, s_optionReadAndWriteFromStr);
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+ Assert.Equal(json, serialized);
+ }
+ }
+
+ foreach (Type type in CollectionTestTypes.DeserializableNonGenericEnumerableTypes())
+ {
+ // Deserialized as collection of JsonElements.
+ object obj = await Serializer.DeserializeWrapper(json, type, s_optionReadAndWriteFromStr);
+ // Serialized as strings with escaping.
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+
+ // Ensure escaped values were serialized accurately
+ List<T> list = await Serializer.DeserializeWrapper<List<T>>(serialized, s_optionReadAndWriteFromStr);
+ serialized = await Serializer.SerializeWrapper(list, s_optionReadAndWriteFromStr);
+ Assert.Equal(json, serialized);
+
+ // Serialize instance which is a collection of numbers (not JsonElements).
+ obj = Activator.CreateInstance(type, new[] { list });
+ serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+ Assert.Equal(json, serialized);
+ }
+ }
+
+ [Fact]
+ public async Task Number_AsDictionaryElement_RoundTrip()
+ {
+ var dict = new Dictionary<int, float>();
+ for (int i = 0; i < 10; i++)
+ {
+ dict[JsonNumberTestData.Ints[i]] = JsonNumberTestData.Floats[i];
+ }
+
+ // Serialize
+ string serialized = await Serializer.SerializeWrapper(dict, s_optionReadAndWriteFromStr);
+ AssertDictionaryElements_StringValues(serialized);
+
+ // Deserialize
+ dict = await Serializer.DeserializeWrapper<Dictionary<int, float>>(serialized, s_optionReadAndWriteFromStr);
+
+ // Test roundtrip
+ JsonTestHelper.AssertJsonEqual(serialized, await Serializer.SerializeWrapper(dict, s_optionReadAndWriteFromStr));
+ }
+
+ private void AssertDictionaryElements_StringValues(string serialized)
+ {
+ Utf8JsonReader reader = new(Encoding.UTF8.GetBytes(serialized));
+ reader.Read();
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+ else if (reader.TokenType == JsonTokenType.String)
+ {
+ Assert.True(reader.ValueSpan.IndexOf((byte)'\\') == -1);
+ }
+ else
+ {
+ Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
+ }
+ }
+ }
+
+ [Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/39674", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))]
+ [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", ~RuntimeConfiguration.Release)]
+ public async Task DictionariesRoundTrip()
+ {
+ await RunAllDictionariessRoundTripTest(JsonNumberTestData.ULongs);
+ await RunAllDictionariessRoundTripTest(JsonNumberTestData.Floats);
+ await RunAllDictionariessRoundTripTest(JsonNumberTestData.Doubles);
+ }
+
+ private async Task RunAllDictionariessRoundTripTest<T>(List<T> numbers)
+ {
+ StringBuilder jsonBuilder_NumbersAsStrings = new StringBuilder();
+
+ jsonBuilder_NumbersAsStrings.Append("{");
+
+ foreach (T number in numbers)
+ {
+ string numberAsString = GetNumberAsString(number);
+ string jsonWithNumberAsString = @$"""{numberAsString}""";
+
+ jsonBuilder_NumbersAsStrings.Append($"{jsonWithNumberAsString}:");
+ jsonBuilder_NumbersAsStrings.Append($"{jsonWithNumberAsString},");
+ }
+
+ jsonBuilder_NumbersAsStrings.Remove(jsonBuilder_NumbersAsStrings.Length - 1, 1);
+ jsonBuilder_NumbersAsStrings.Append("}");
+
+ string jsonNumbersAsStrings = jsonBuilder_NumbersAsStrings.ToString();
+
+ foreach (Type type in CollectionTestTypes.DeserializableDictionaryTypes<string, T>())
+ {
+ object obj = await Serializer.DeserializeWrapper(jsonNumbersAsStrings, type, s_optionReadAndWriteFromStr);
+ JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr));
+ }
+
+ foreach (Type type in CollectionTestTypes.DeserializableNonGenericDictionaryTypes())
+ {
+ Dictionary<T, T> dict = await Serializer.DeserializeWrapper<Dictionary<T, T>>(jsonNumbersAsStrings, s_optionReadAndWriteFromStr);
+
+ // Serialize instance which is a dictionary of numbers (not JsonElements).
+ object obj = Activator.CreateInstance(type, new[] { dict });
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+ JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, serialized);
+ }
+ }
+
+ [Fact]
+ public async Task Number_AsPropertyValue_RoundTrip()
+ {
+ var obj = new Class_With_NullableUInt64_And_Float()
+ {
+ NullableUInt64Number = JsonNumberTestData.NullableULongs.LastOrDefault(),
+ FloatNumbers = JsonNumberTestData.Floats
+ };
+
+ // Serialize
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+
+ // Deserialize
+ obj = await Serializer.DeserializeWrapper<Class_With_NullableUInt64_And_Float>(serialized, s_optionReadAndWriteFromStr);
+
+ // Test roundtrip
+ JsonTestHelper.AssertJsonEqual(serialized, await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr));
+ }
+
+ public class Class_With_NullableUInt64_And_Float
+ {
+ public ulong? NullableUInt64Number { get; set; }
+ [JsonInclude]
+ public List<float> FloatNumbers;
+ }
+
+ [Fact]
+ public async Task Number_AsKeyValuePairValue_RoundTrip()
+ {
+ var obj = new KeyValuePair<ulong?, List<float>>(JsonNumberTestData.NullableULongs.LastOrDefault(), JsonNumberTestData.Floats);
+
+ // Serialize
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+
+ // Deserialize
+ obj = await Serializer.DeserializeWrapper<KeyValuePair<ulong?, List<float>>>(serialized, s_optionReadAndWriteFromStr);
+
+ // Test roundtrip
+ JsonTestHelper.AssertJsonEqual(serialized, await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr));
+ }
+
+ [Fact]
+ public async Task Number_AsObjectWithParameterizedCtor_RoundTrip()
+ {
+ var obj = new MyClassWithNumbers(JsonNumberTestData.NullableULongs.LastOrDefault(), JsonNumberTestData.Floats);
+
+ // Serialize
+ string serialized = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+
+ // Deserialize
+ obj = await Serializer.DeserializeWrapper<MyClassWithNumbers>(serialized, s_optionReadAndWriteFromStr);
+
+ // Test roundtrip
+ JsonTestHelper.AssertJsonEqual(serialized, await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr));
+ }
+
+ public class MyClassWithNumbers
+ {
+ public ulong? Ulong { get; }
+ public List<float> ListOfFloats { get; }
+
+ public MyClassWithNumbers(ulong? @ulong, List<float> listOfFloats)
+ {
+ Ulong = @ulong;
+ ListOfFloats = listOfFloats;
+ }
+ }
+
+ [Fact]
+ public async Task Number_AsObjectWithParameterizedCtor_PropHasAttribute()
+ {
+ string json = @"{""ListOfFloats"":[""1""]}";
+ // Strict handling on property overrides loose global policy.
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<MyClassWithNumbers_PropsHasAttribute>(json, s_optionReadFromStr));
+
+ // Serialize
+ json = @"{""ListOfFloats"":[1]}";
+ MyClassWithNumbers_PropsHasAttribute obj = await Serializer.DeserializeWrapper<MyClassWithNumbers_PropsHasAttribute>(json);
+
+ // Number serialized as JSON number due to strict handling on property which overrides loose global policy.
+ Assert.Equal(json, await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr));
+ }
+
+ public class MyClassWithNumbers_PropsHasAttribute
+ {
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ public List<float> ListOfFloats { get; }
+
+ public MyClassWithNumbers_PropsHasAttribute(List<float> listOfFloats)
+ {
+ ListOfFloats = listOfFloats;
+ }
+ }
+
+ [Fact]
+ public async Task FloatingPointConstants_Pass()
+ {
+ // Valid values
+ await PerformFloatingPointSerialization("NaN");
+ await PerformFloatingPointSerialization("Infinity");
+ await PerformFloatingPointSerialization("-Infinity");
+
+ await PerformFloatingPointSerialization("\u004EaN"); // NaN
+ await PerformFloatingPointSerialization("Inf\u0069ni\u0074y"); // Infinity
+ await PerformFloatingPointSerialization("\u002DInf\u0069nity"); // -Infinity
+
+ async Task PerformFloatingPointSerialization(string testString)
+ {
+ string testStringAsJson = $@"""{testString}""";
+ string testJson = @$"{{""FloatNumber"":{testStringAsJson},""DoubleNumber"":{testStringAsJson}}}";
+
+ StructWithNumbers obj;
+ switch (testString)
+ {
+ case "NaN":
+ obj = await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionsAllowFloatConstants);
+ Assert.Equal(float.NaN, obj.FloatNumber);
+ Assert.Equal(double.NaN, obj.DoubleNumber);
+
+ obj = await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionReadFromStr);
+ Assert.Equal(float.NaN, obj.FloatNumber);
+ Assert.Equal(double.NaN, obj.DoubleNumber);
+ break;
+ case "Infinity":
+ obj = await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionsAllowFloatConstants);
+ Assert.Equal(float.PositiveInfinity, obj.FloatNumber);
+ Assert.Equal(double.PositiveInfinity, obj.DoubleNumber);
+
+ obj = await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionReadFromStr);
+ Assert.Equal(float.PositiveInfinity, obj.FloatNumber);
+ Assert.Equal(double.PositiveInfinity, obj.DoubleNumber);
+ break;
+ case "-Infinity":
+ obj = await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionsAllowFloatConstants);
+ Assert.Equal(float.NegativeInfinity, obj.FloatNumber);
+ Assert.Equal(double.NegativeInfinity, obj.DoubleNumber);
+
+ obj = await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionReadFromStr);
+ Assert.Equal(float.NegativeInfinity, obj.FloatNumber);
+ Assert.Equal(double.NegativeInfinity, obj.DoubleNumber);
+ break;
+ default:
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionsAllowFloatConstants));
+ return;
+ }
+
+ JsonTestHelper.AssertJsonEqual(testJson, await Serializer.SerializeWrapper(obj, s_optionsAllowFloatConstants));
+ JsonTestHelper.AssertJsonEqual(testJson, await Serializer.SerializeWrapper(obj, s_optionWriteAsStr));
+ }
+ }
+
+ [Theory]
+ [InlineData("naN")]
+ [InlineData("Nan")]
+ [InlineData("NAN")]
+ [InlineData("+Infinity")]
+ [InlineData("+infinity")]
+ [InlineData("infinity")]
+ [InlineData("infinitY")]
+ [InlineData("INFINITY")]
+ [InlineData("+INFINITY")]
+ [InlineData("-infinity")]
+ [InlineData("-infinitY")]
+ [InlineData("-INFINITY")]
+ [InlineData(" NaN")]
+ [InlineData("NaN ")]
+ [InlineData(" Infinity")]
+ [InlineData(" -Infinity")]
+ [InlineData("Infinity ")]
+ [InlineData("-Infinity ")]
+ [InlineData("a-Infinity")]
+ [InlineData("NaNa")]
+ [InlineData("Infinitya")]
+ [InlineData("-Infinitya")]
+#pragma warning disable xUnit1025 // Theory method 'FloatingPointConstants_Fail' on test class 'NumberHandlingTests' has InlineData duplicate(s)
+ [InlineData("\u006EaN")] // "naN"
+ [InlineData("\u0020Inf\u0069ni\u0074y")] // " Infinity"
+ [InlineData("\u002BInf\u0069nity")] // "+Infinity"
+#pragma warning restore xUnit1025
+ public async Task FloatingPointConstants_Fail(string testString)
+ {
+ string testStringAsJson = $@"""{testString}""";
+
+ string testJson = @$"{{""FloatNumber"":{testStringAsJson}}}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionReadFromStr));
+
+ testJson = @$"{{""DoubleNumber"":{testStringAsJson}}}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<StructWithNumbers>(testJson, s_optionReadFromStr));
+ }
+
+ [Fact]
+ public async Task AllowFloatingPointConstants_WriteAsNumber_IfNotConstant()
+ {
+ float @float = 1;
+ // Not written as "1"
+ Assert.Equal("1", await Serializer.SerializeWrapper(@float, s_optionsAllowFloatConstants));
+
+ double @double = 1;
+ // Not written as "1"
+ Assert.Equal("1", await Serializer.SerializeWrapper(@double, s_optionsAllowFloatConstants));
+ }
+
+ [Theory]
+ [InlineData("NaN")]
+ [InlineData("Infinity")]
+ [InlineData("-Infinity")]
+ public async Task Unquoted_FloatingPointConstants_Read_Fail(string testString)
+ {
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<float>(testString, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<double?>(testString, s_optionReadFromStr));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<double>(testString, s_optionReadFromStrAllowFloatConstants));
+ }
+
+ public struct StructWithNumbers
+ {
+ public float FloatNumber { get; set; }
+ public double DoubleNumber { get; set; }
+ }
+
+ [Fact]
+ public async Task ReadFromString_AllowFloatingPoint()
+ {
+ string json = @"{""IntNumber"":""1"",""FloatNumber"":""NaN""}";
+ ClassWithNumbers obj = await Serializer.DeserializeWrapper<ClassWithNumbers>(json, s_optionReadFromStrAllowFloatConstants);
+
+ Assert.Equal(1, obj.IntNumber);
+ Assert.Equal(float.NaN, obj.FloatNumber);
+
+ JsonTestHelper.AssertJsonEqual(@"{""IntNumber"":1,""FloatNumber"":""NaN""}", await Serializer.SerializeWrapper(obj, s_optionReadFromStrAllowFloatConstants));
+ }
+
+ [Fact]
+ public async Task WriteAsString_AllowFloatingPoint()
+ {
+ string json = @"{""IntNumber"":""1"",""FloatNumber"":""NaN""}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWithNumbers>(json, s_optionWriteAsStrAllowFloatConstants));
+
+ var obj = new ClassWithNumbers
+ {
+ IntNumber = 1,
+ FloatNumber = float.NaN
+ };
+
+ JsonTestHelper.AssertJsonEqual(json, await Serializer.SerializeWrapper(obj, s_optionWriteAsStrAllowFloatConstants));
+ }
+
+ public class ClassWithNumbers
+ {
+ public int IntNumber { get; set; }
+ public float FloatNumber { get; set; }
+ }
+
+ [Fact]
+ public async Task FloatingPointConstants_IncompatibleNumber()
+ {
+ await AssertFloatingPointIncompatible_Fails<byte>();
+ await AssertFloatingPointIncompatible_Fails<sbyte>();
+ await AssertFloatingPointIncompatible_Fails<short>();
+ await AssertFloatingPointIncompatible_Fails<int>();
+ await AssertFloatingPointIncompatible_Fails<long>();
+ await AssertFloatingPointIncompatible_Fails<ushort>();
+ await AssertFloatingPointIncompatible_Fails<uint>();
+ await AssertFloatingPointIncompatible_Fails<ulong>();
+ await AssertFloatingPointIncompatible_Fails<decimal>();
+ await AssertFloatingPointIncompatible_Fails<byte?>();
+ await AssertFloatingPointIncompatible_Fails<sbyte?>();
+ await AssertFloatingPointIncompatible_Fails<short?>();
+ await AssertFloatingPointIncompatible_Fails<int?>();
+ await AssertFloatingPointIncompatible_Fails<long?>();
+ await AssertFloatingPointIncompatible_Fails<ushort?>();
+ await AssertFloatingPointIncompatible_Fails<uint?>();
+ await AssertFloatingPointIncompatible_Fails<ulong?>();
+ await AssertFloatingPointIncompatible_Fails<decimal?>();
+ }
+
+ private async Task AssertFloatingPointIncompatible_Fails<T>()
+ {
+ string[] testCases = new[]
+ {
+ @"""NaN""",
+ @"""Infinity""",
+ @"""-Infinity""",
+ };
+
+ foreach (string test in testCases)
+ {
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<T>(test, s_optionReadFromStrAllowFloatConstants));
+ }
+ }
+
+ [Fact]
+ public async Task UnsupportedFormats()
+ {
+ await AssertUnsupportedFormatThrows<byte>();
+ await AssertUnsupportedFormatThrows<sbyte>();
+ await AssertUnsupportedFormatThrows<short>();
+ await AssertUnsupportedFormatThrows<int>();
+ await AssertUnsupportedFormatThrows<long>();
+ await AssertUnsupportedFormatThrows<ushort>();
+ await AssertUnsupportedFormatThrows<uint>();
+ await AssertUnsupportedFormatThrows<ulong>();
+ await AssertUnsupportedFormatThrows<float>();
+ await AssertUnsupportedFormatThrows<decimal>();
+ await AssertUnsupportedFormatThrows<byte?>();
+ await AssertUnsupportedFormatThrows<sbyte?>();
+ await AssertUnsupportedFormatThrows<short?>();
+ await AssertUnsupportedFormatThrows<int?>();
+ await AssertUnsupportedFormatThrows<long?>();
+ await AssertUnsupportedFormatThrows<ushort?>();
+ await AssertUnsupportedFormatThrows<uint?>();
+ await AssertUnsupportedFormatThrows<ulong?>();
+ await AssertUnsupportedFormatThrows<float?>();
+ await AssertUnsupportedFormatThrows<decimal?>();
+ }
+
+ private async Task AssertUnsupportedFormatThrows<T>()
+ {
+ string[] testCases = new[]
+ {
+ "$123.46", // Currency
+ "100.00 %", // Percent
+ "1234,57", // Fixed point
+ "00FF", // Hexadecimal
+ };
+
+ foreach (string test in testCases)
+ {
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<T>(test, s_optionReadFromStr));
+ }
+ }
+
+ [Fact]
+ public async Task EscapingTest()
+ {
+ // Cause all characters to be escaped.
+ var encoderSettings = new TextEncoderSettings();
+ encoderSettings.ForbidCharacters('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '-', 'e', 'E');
+
+ JavaScriptEncoder encoder = JavaScriptEncoder.Create(encoderSettings);
+ var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
+ {
+ Encoder = encoder
+ };
+
+ await PerformEscapingTest(JsonNumberTestData.Bytes, options);
+ await PerformEscapingTest(JsonNumberTestData.SBytes, options);
+ await PerformEscapingTest(JsonNumberTestData.Shorts, options);
+ await PerformEscapingTest(JsonNumberTestData.Ints, options);
+ await PerformEscapingTest(JsonNumberTestData.Longs, options);
+ await PerformEscapingTest(JsonNumberTestData.UShorts, options);
+ await PerformEscapingTest(JsonNumberTestData.UInts, options);
+ await PerformEscapingTest(JsonNumberTestData.ULongs, options);
+ await PerformEscapingTest(JsonNumberTestData.Floats, options);
+ await PerformEscapingTest(JsonNumberTestData.Doubles, options);
+ await PerformEscapingTest(JsonNumberTestData.Decimals, options);
+ }
+
+ private async Task PerformEscapingTest<T>(List<T> numbers, JsonSerializerOptions options)
+ {
+ // All input characters are escaped
+ IEnumerable<string> numbersAsStrings = numbers.Select(num => GetNumberAsString(num));
+ string input = await Serializer.SerializeWrapper(numbersAsStrings, options);
+ AssertListNumbersEscaped(input);
+
+ // Unescaping works
+ List<T> deserialized = await Serializer.DeserializeWrapper<List<T>>(input, options);
+ Assert.Equal(numbers.Count, deserialized.Count);
+ for (int i = 0; i < numbers.Count; i++)
+ {
+ Assert.Equal(numbers[i], deserialized[i]);
+ }
+
+ // Every number is written as a string, and custom escaping is not honored.
+ string serialized = await Serializer.SerializeWrapper(deserialized, options);
+ AssertListNumbersUnescaped(serialized);
+ }
+
+ private void AssertListNumbersEscaped(string json)
+ {
+ Utf8JsonReader reader = new(Encoding.UTF8.GetBytes(json));
+ reader.Read();
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndArray)
+ {
+ break;
+ }
+ else
+ {
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ Assert.True(reader.ValueSpan.IndexOf((byte)'\\') != -1);
+ }
+ }
+ }
+
+ private void AssertListNumbersUnescaped(string json)
+ {
+ Utf8JsonReader reader = new(Encoding.UTF8.GetBytes(json));
+ reader.Read();
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndArray)
+ {
+ break;
+ }
+ else
+ {
+ Assert.Equal(JsonTokenType.String, reader.TokenType);
+ Assert.True(reader.ValueSpan.IndexOf((byte)'\\') == -1);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task Number_RoundtripNull()
+ {
+ await Perform_Number_RoundTripNull_Test<byte>();
+ await Perform_Number_RoundTripNull_Test<sbyte>();
+ await Perform_Number_RoundTripNull_Test<short>();
+ await Perform_Number_RoundTripNull_Test<int>();
+ await Perform_Number_RoundTripNull_Test<long>();
+ await Perform_Number_RoundTripNull_Test<ushort>();
+ await Perform_Number_RoundTripNull_Test<uint>();
+ await Perform_Number_RoundTripNull_Test<ulong>();
+ await Perform_Number_RoundTripNull_Test<float>();
+ await Perform_Number_RoundTripNull_Test<decimal>();
+ }
+
+ private async Task Perform_Number_RoundTripNull_Test<T>()
+ {
+ string nullAsJson = "null";
+ string nullAsQuotedJson = $@"""{nullAsJson}""";
+
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<T>(nullAsJson, s_optionReadAndWriteFromStr));
+ Assert.Equal("0", await Serializer.SerializeWrapper(default(T)));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<T>(nullAsQuotedJson, s_optionReadAndWriteFromStr));
+ }
+
+ [Fact]
+ public async Task NullableNumber_RoundtripNull()
+ {
+ await Perform_NullableNumber_RoundTripNull_Test<byte?>();
+ await Perform_NullableNumber_RoundTripNull_Test<sbyte?>();
+ await Perform_NullableNumber_RoundTripNull_Test<short?>();
+ await Perform_NullableNumber_RoundTripNull_Test<int?>();
+ await Perform_NullableNumber_RoundTripNull_Test<long?>();
+ await Perform_NullableNumber_RoundTripNull_Test<ushort?>();
+ await Perform_NullableNumber_RoundTripNull_Test<uint?>();
+ await Perform_NullableNumber_RoundTripNull_Test<ulong?>();
+ await Perform_NullableNumber_RoundTripNull_Test<float?>();
+ await Perform_NullableNumber_RoundTripNull_Test<decimal?>();
+ }
+
+ private async Task Perform_NullableNumber_RoundTripNull_Test<T>()
+ {
+ string nullAsJson = "null";
+ string nullAsQuotedJson = $@"""{nullAsJson}""";
+
+ Assert.Null(await Serializer.DeserializeWrapper<T>(nullAsJson, s_optionReadAndWriteFromStr));
+ Assert.Equal(nullAsJson, await Serializer.SerializeWrapper(default(T)));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<T>(nullAsQuotedJson, s_optionReadAndWriteFromStr));
+ }
+
+ [Fact]
+ public async Task Disallow_ArbritaryStrings_On_AllowFloatingPointConstants()
+ {
+ string json = @"""12345""";
+
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<byte>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<sbyte>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<short>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<int>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<long>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ushort>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<uint>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ulong>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<float>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<double>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<decimal>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<byte?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<sbyte?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<short?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<int?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<long?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ushort?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<uint?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ulong?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<float?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<double?>(json, s_optionsAllowFloatConstants));
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<decimal?>(json, s_optionsAllowFloatConstants));
+ }
+
+ [Fact]
+ public async Task Attributes_OnMembers_Work()
+ {
+ // Bad JSON because Int should not be string.
+ string intIsString = @"{""Float"":""1234.5"",""Int"":""12345""}";
+
+ // Good JSON because Float can be string.
+ string floatIsString = @"{""Float"":""1234.5"",""Int"":12345}";
+
+ // Good JSON because Float can be number.
+ string floatIsNumber = @"{""Float"":1234.5,""Int"":12345}";
+
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWith_Attribute_OnNumber>(intIsString));
+
+ ClassWith_Attribute_OnNumber obj = await Serializer.DeserializeWrapper<ClassWith_Attribute_OnNumber>(floatIsString);
+ Assert.Equal(1234.5, obj.Float);
+ Assert.Equal(12345, obj.Int);
+
+ obj = await Serializer.DeserializeWrapper<ClassWith_Attribute_OnNumber>(floatIsNumber);
+ Assert.Equal(1234.5, obj.Float);
+ Assert.Equal(12345, obj.Int);
+
+ // Per options, float should be written as string.
+ JsonTestHelper.AssertJsonEqual(floatIsString, await Serializer.SerializeWrapper(obj));
+ }
+
+ public class ClassWith_Attribute_OnNumber
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public float Float { get; set; }
+
+ public int Int { get; set; }
+ }
+
+ [Fact]
+ public async Task Attribute_OnRootType_Works()
+ {
+ // Not allowed
+ string floatIsString = @"{""Float"":""1234"",""Int"":123}";
+
+ // Allowed
+ string floatIsNan = @"{""Float"":""NaN"",""Int"":123}";
+
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<Type_AllowFloatConstants>(floatIsString));
+
+ Type_AllowFloatConstants obj = await Serializer.DeserializeWrapper<Type_AllowFloatConstants>(floatIsNan);
+ Assert.Equal(float.NaN, obj.Float);
+ Assert.Equal(123, obj.Int);
+
+ JsonTestHelper.AssertJsonEqual(floatIsNan, await Serializer.SerializeWrapper(obj));
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)]
+ public class Type_AllowFloatConstants
+ {
+ public float Float { get; set; }
+
+ public int Int { get; set; }
+ }
+
+ [Fact]
+ public async Task AttributeOnType_WinsOver_GlobalOption()
+ {
+ // Global options strict, type options loose
+ string json = @"{""Float"":""12345""}";
+ var obj1 = await Serializer.DeserializeWrapper<ClassWith_LooseAttribute>(json);
+
+ Assert.Equal(@"{""Float"":""12345""}", await Serializer.SerializeWrapper(obj1));
+
+ // Global options loose, type options strict
+ json = @"{""Float"":""12345""}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWith_StrictAttribute>(json, s_optionReadAndWriteFromStr));
+
+ var obj2 = new ClassWith_StrictAttribute() { Float = 12345 };
+ Assert.Equal(@"{""Float"":12345}", await Serializer.SerializeWrapper(obj2, s_optionReadAndWriteFromStr));
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ public class ClassWith_StrictAttribute
+ {
+ public float Float { get; set; }
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public class ClassWith_LooseAttribute
+ {
+ public float Float { get; set; }
+ }
+
+ [Fact]
+ public async Task AttributeOnMember_WinsOver_AttributeOnType()
+ {
+ string json = @"{""Double"":""NaN""}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWith_Attribute_On_TypeAndMember>(json));
+
+ var obj = new ClassWith_Attribute_On_TypeAndMember { Double = float.NaN };
+ await Assert.ThrowsAsync<ArgumentException>(async () => await Serializer.SerializeWrapper(obj));
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)]
+ public class ClassWith_Attribute_On_TypeAndMember
+ {
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ public double Double { get; set; }
+ }
+
+ [Fact]
+ public async Task Attribute_OnNestedType_Works()
+ {
+ string jsonWithShortProperty = @"{""Short"":""1""}";
+ ClassWith_ReadAsStringAttribute obj = await Serializer.DeserializeWrapper<ClassWith_ReadAsStringAttribute>(jsonWithShortProperty);
+ Assert.Equal(1, obj.Short);
+
+ string jsonWithMyObjectProperty = @"{""MyObject"":{""Float"":""1""}}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWith_ReadAsStringAttribute>(jsonWithMyObjectProperty));
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
+ public class ClassWith_ReadAsStringAttribute
+ {
+ public short Short { get; set; }
+
+ public ClassWith_StrictAttribute MyObject { get; set; }
+ }
+
+ [Fact]
+ public async Task MemberAttributeAppliesToCollection_SimpleElements()
+ {
+ await RunTest<int[]>();
+ await RunTest<ConcurrentQueue<int>>();
+ await RunTest<GenericICollectionWrapper<int>>();
+ await RunTest<IEnumerable<int>>();
+ await RunTest<Collection<int>>();
+ await RunTest<ImmutableList<int>>();
+ await RunTest<HashSet<int>>();
+ await RunTest<List<int>>();
+ await RunTest<IList<int>>();
+ await RunTest<IList>();
+ await RunTest<Queue<int>>();
+
+ async Task RunTest<T>()
+ {
+ string json = @"{""MyList"":[""1"",""2""]}";
+ ClassWithSimpleCollectionProperty<T> obj = await Serializer.DeserializeWrapper<ClassWithSimpleCollectionProperty<T>>(json);
+ Assert.Equal(json, await Serializer.SerializeWrapper(obj));
+ }
+ }
+
+ public class ClassWithSimpleCollectionProperty<T>
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public T MyList { get; set; }
+ }
+
+ [Fact]
+ public async Task NestedCollectionElementTypeHandling_Overrides_GlobalOption()
+ {
+ // Strict policy on the collection element type overrides read-as-string on the collection property
+ string json = @"{""MyList"":[{""Float"":""1""}]}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWithComplexListProperty>(json, s_optionReadAndWriteFromStr));
+
+ // Strict policy on the collection element type overrides write-as-string on the collection property
+ var obj = new ClassWithComplexListProperty
+ {
+ MyList = new List<ClassWith_StrictAttribute> { new ClassWith_StrictAttribute { Float = 1 } }
+ };
+ Assert.Equal(@"{""MyList"":[{""Float"":1}]}", await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr));
+ }
+
+ public class ClassWithComplexListProperty
+ {
+ public List<ClassWith_StrictAttribute> MyList { get; set; }
+ }
+
+ [Fact]
+ public async Task NumberHandlingAttribute_NotAllowedOn_CollectionOfNonNumbers()
+ {
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.DeserializeWrapper<ClassWith_AttributeOnComplexListProperty>(""));
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.SerializeWrapper(new ClassWith_AttributeOnComplexListProperty()));
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.DeserializeWrapper<ClassWith_AttributeOnComplexDictionaryProperty>(""));
+ await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.SerializeWrapper(new ClassWith_AttributeOnComplexDictionaryProperty()));
+ }
+
+ public class ClassWith_AttributeOnComplexListProperty
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public List<ClassWith_StrictAttribute> MyList { get; set; }
+ }
+
+ public class ClassWith_AttributeOnComplexDictionaryProperty
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
+ public Dictionary<string, ClassWith_StrictAttribute> MyDictionary { get; set; }
+ }
+
+ [Fact]
+ public async Task MemberAttributeAppliesToDictionary_SimpleElements()
+ {
+ string json = @"{""First"":""1"",""Second"":""2""}";
+ ClassWithSimpleDictionaryProperty obj = await Serializer.DeserializeWrapper<ClassWithSimpleDictionaryProperty>(json);
+ }
+
+ public class ClassWithSimpleDictionaryProperty
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public Dictionary<string, int> MyDictionary { get; set; }
+ }
+
+ [Fact]
+ public async Task NestedDictionaryElementTypeHandling_Overrides_GlobalOption()
+ {
+ // Strict policy on the dictionary element type overrides read-as-string on the collection property.
+ string json = @"{""MyDictionary"":{""Key"":{""Float"":""1""}}}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWithComplexDictionaryProperty>(json, s_optionReadFromStr));
+
+ // Strict policy on the collection element type overrides write-as-string on the collection property
+ var obj = new ClassWithComplexDictionaryProperty
+ {
+ MyDictionary = new Dictionary<string, ClassWith_StrictAttribute> { ["Key"] = new ClassWith_StrictAttribute { Float = 1 } }
+ };
+ Assert.Equal(@"{""MyDictionary"":{""Key"":{""Float"":1}}}", await Serializer.SerializeWrapper(obj, s_optionReadFromStr));
+ }
+
+ public class ClassWithComplexDictionaryProperty
+ {
+ public Dictionary<string, ClassWith_StrictAttribute> MyDictionary { get; set; }
+ }
+
+ [Fact]
+ public async Task TypeAttributeAppliesTo_CustomCollectionElements()
+ {
+ string json = @"[""1""]";
+ MyCustomList obj = await Serializer.DeserializeWrapper<MyCustomList>(json);
+ Assert.Equal(json, await Serializer.SerializeWrapper(obj));
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public class MyCustomList : List<int> { }
+
+ [Fact]
+ public async Task TypeAttributeAppliesTo_CustomCollectionElements_HonoredWhenProperty()
+ {
+ string json = @"{""List"":[""1""]}";
+ ClassWithCustomList obj = await Serializer.DeserializeWrapper<ClassWithCustomList>(json);
+ Assert.Equal(json, await Serializer.SerializeWrapper(obj));
+ }
+
+ public class ClassWithCustomList
+ {
+ public MyCustomList List { get; set; }
+ }
+
+ [Fact]
+ public async Task TypeAttributeAppliesTo_CustomDictionaryElements()
+ {
+ string json = @"{""Key"":""1""}";
+ MyCustomDictionary obj = await Serializer.DeserializeWrapper<MyCustomDictionary>(json);
+ Assert.Equal(json, await Serializer.SerializeWrapper(obj));
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public class MyCustomDictionary : Dictionary<string, int> { }
+
+ [Fact]
+ public async Task TypeAttributeAppliesTo_CustomDictionaryElements_HonoredWhenProperty()
+ {
+ string json = @"{""Dictionary"":{""Key"":""1""}}";
+ ClassWithCustomDictionary obj = await Serializer.DeserializeWrapper<ClassWithCustomDictionary>(json);
+ Assert.Equal(json, await Serializer.SerializeWrapper(obj));
+ }
+
+ public class ClassWithCustomDictionary
+ {
+ public MyCustomDictionary Dictionary { get; set; }
+ }
+
+ [Fact]
+ public async Task Attribute_OnType_NotRecursive()
+ {
+ // Recursive behavior, where number handling setting on a property is applied to subsequent
+ // properties in its type closure, would allow a string number. This is not supported.
+ string json = @"{""NestedClass"":{""MyInt"":""1""}}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<AttributeAppliedToFirstLevelProp>(json));
+
+ var obj = new AttributeAppliedToFirstLevelProp
+ {
+ NestedClass = new NonNumberType { MyInt = 1 }
+ };
+ Assert.Equal(@"{""NestedClass"":{""MyInt"":1}}", await Serializer.SerializeWrapper(obj));
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public class AttributeAppliedToFirstLevelProp
+ {
+ public NonNumberType NestedClass { get; set; }
+ }
+
+ public class NonNumberType
+ {
+ public int MyInt { get; set; }
+ }
+
+ [Fact]
+ public async Task HandlingOnMemberOverridesHandlingOnType_Enumerable()
+ {
+ string json = @"{""List"":[""1""]}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<MyCustomListWrapper>(json));
+
+ var obj = new MyCustomListWrapper
+ {
+ List = new MyCustomList { 1 }
+ };
+ Assert.Equal(@"{""List"":[1]}", await Serializer.SerializeWrapper(obj));
+ }
+
+ public class MyCustomListWrapper
+ {
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ public MyCustomList List { get; set; }
+ }
+
+ [Fact]
+ public async Task HandlingOnMemberOverridesHandlingOnType_Dictionary()
+ {
+ string json = @"{""Dictionary"":{""Key"":""1""}}";
+ await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<MyCustomDictionaryWrapper>(json));
+
+ var obj1 = new MyCustomDictionaryWrapper
+ {
+ Dictionary = new MyCustomDictionary { ["Key"] = 1 }
+ };
+ Assert.Equal(@"{""Dictionary"":{""Key"":1}}", await Serializer.SerializeWrapper(obj1));
+ }
+
+ public class MyCustomDictionaryWrapper
+ {
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ public MyCustomDictionary Dictionary { get; set; }
+ }
+
+ [Fact]
+ public async Task Attribute_Allowed_On_NonNumber_NonCollection_Property()
+ {
+ const string Json = @"{""MyProp"":{""MyInt"":1}}";
+
+ ClassWith_NumberHandlingOn_ObjectProperty obj = await Serializer.DeserializeWrapper<ClassWith_NumberHandlingOn_ObjectProperty>(Json);
+ Assert.Equal(1, obj.MyProp.MyInt);
+
+ string json = await Serializer.SerializeWrapper(obj);
+ Assert.Equal(Json, json);
+ }
+
+ public class ClassWith_NumberHandlingOn_ObjectProperty
+ {
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ public NonNumberType MyProp { get; set; }
+ }
+
+ [Fact]
+ public async Task Attribute_Allowed_On_Property_WithCustomConverter()
+ {
+ string json = @"{""Prop"":1}";
+
+ // Converter returns 25 regardless of input.
+ var obj = await Serializer.DeserializeWrapper<ClassWith_NumberHandlingOn_Property_WithCustomConverter>(json);
+ Assert.Equal(25, obj.Prop);
+
+ // Converter throws this exception regardless of input.
+ NotImplementedException ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(obj));
+ Assert.Equal("Converter was called", ex.Message);
+ }
+
+ public class ClassWith_NumberHandlingOn_Property_WithCustomConverter
+ {
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ [JsonConverter(typeof(ConverterForInt32))]
+ public int Prop { get; set; }
+ }
+
+ [Fact]
+ public async Task Attribute_Allowed_On_Type_WithCustomConverter()
+ {
+ string json = @"{}";
+ NotImplementedException ex;
+
+ // Assert regular Read/Write methods on custom converter are called.
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.DeserializeWrapper<ClassWith_NumberHandlingOn_Type_WithCustomConverter>(json));
+ Assert.Equal("Converter was called", ex.Message);
+
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(new ClassWith_NumberHandlingOn_Type_WithCustomConverter()));
+ Assert.Equal("Converter was called", ex.Message);
+ }
+
+ [JsonNumberHandling(JsonNumberHandling.Strict)]
+ [JsonConverter(typeof(ConverterForMyType))]
+ public class ClassWith_NumberHandlingOn_Type_WithCustomConverter
+ {
+ }
+
+ public class ConverterForMyType : JsonConverter<ClassWith_NumberHandlingOn_Type_WithCustomConverter>
+ {
+ public override ClassWith_NumberHandlingOn_Type_WithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException("Converter was called");
+ }
+
+ public override void Write(Utf8JsonWriter writer, ClassWith_NumberHandlingOn_Type_WithCustomConverter value, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException("Converter was called");
+ }
+ }
+
+ [Fact]
+ public async Task CustomConverterOverridesBuiltInLogic()
+ {
+ var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
+ {
+ Converters = { new ConverterForInt32(), new ConverterForFloat() }
+ };
+
+ string json = @"""32""";
+
+ // Converter returns 25 regardless of input.
+ Assert.Equal(25, await Serializer.DeserializeWrapper<int>(json, options));
+
+ // Converter throws this exception regardless of input.
+ NotImplementedException ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(4, options));
+ Assert.Equal("Converter was called", ex.Message);
+
+ json = @"""NaN""";
+
+ // Converter returns 25 if NaN.
+ Assert.Equal(25, await Serializer.DeserializeWrapper<float?>(json, options));
+
+ // Converter writes 25 if NaN.
+ Assert.Equal("25", await Serializer.SerializeWrapper((float?)float.NaN, options));
+ }
+
+ public class ConverterForFloat : JsonConverter<float?>
+ {
+ public override float? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String && reader.GetString() == "NaN")
+ {
+ return 25;
+ }
+
+ throw new NotSupportedException();
+ }
+
+ public override void Write(Utf8JsonWriter writer, float? value, JsonSerializerOptions options)
+ {
+ if (float.IsNaN(value.Value))
+ {
+ writer.WriteNumberValue(25);
+ return;
+ }
+
+ throw new NotSupportedException();
+ }
+ }
+
+ [Fact]
+ public static void JsonNumberHandling_ArgOutOfRangeFail()
+ {
+ // Global options
+ ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
+ () => new JsonSerializerOptions { NumberHandling = (JsonNumberHandling)(-1) });
+ Assert.Contains("value", ex.ToString());
+ Assert.Throws<ArgumentOutOfRangeException>(
+ () => new JsonSerializerOptions { NumberHandling = (JsonNumberHandling)(8) });
+
+ ex = Assert.Throws<ArgumentOutOfRangeException>(
+ () => new JsonNumberHandlingAttribute((JsonNumberHandling)(-1)));
+ Assert.Contains("handling", ex.ToString());
+ Assert.Throws<ArgumentOutOfRangeException>(
+ () => new JsonNumberHandlingAttribute((JsonNumberHandling)(8)));
+ }
+
+ [Fact]
+ public async Task InternalCollectionConverter_CustomNumberConverter_GlobalOption()
+ {
+ NotImplementedException ex;
+
+ var list = new List<int> { 1 };
+ var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
+ {
+ Converters = { new ConverterForInt32() }
+ };
+
+ // Assert converter methods are called and not Read/WriteWithNumberHandling (which would throw InvalidOperationException).
+ // Converter returns 25 regardless of input.
+ Assert.Equal(25, (await Serializer.DeserializeWrapper<List<int>>(@"[""1""]", options))[0]);
+ // Converter throws this exception regardless of input.
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(list, options));
+ Assert.Equal("Converter was called", ex.Message);
+
+ var list2 = new List<int?> { 1 };
+ Assert.Equal(25, (await Serializer.DeserializeWrapper<List<int?>>(@"[""1""]", options))[0]);
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(list2, options));
+ Assert.Equal("Converter was called", ex.Message);
+
+ // Okay to set number handling for number collection property when number is handled with custom converter;
+ // converter Read/Write methods called.
+ ClassWithListPropAndAttribute obj1 = await Serializer.DeserializeWrapper<ClassWithListPropAndAttribute>(@"{""Prop"":[""1""]}", options);
+ Assert.Equal(25, obj1.Prop[0]);
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(obj1, options));
+ Assert.Equal("Converter was called", ex.Message);
+
+ ClassWithDictPropAndAttribute obj2 = await Serializer.DeserializeWrapper<ClassWithDictPropAndAttribute>(@"{""Prop"":{""1"":""1""}}", options);
+ Assert.Equal(25, obj2.Prop[1]);
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(obj2, options));
+ Assert.Equal("Converter was called", ex.Message);
+ }
+
+ public class ClassWithListPropAndAttribute
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public List<int> Prop { get; set; }
+ }
+
+ public class ClassWithDictPropAndAttribute
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public Dictionary<int, int?> Prop { get; set; }
+ }
+
+ [Fact]
+ public async Task InternalCollectionConverter_CustomNumberConverter_OnProperty()
+ {
+ // Invalid to set number handling for number collection property when number is handled with custom converter.
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.DeserializeWrapper<ClassWithListPropAndAttribute_ConverterOnProp>(""));
+ Assert.Contains(nameof(ClassWithListPropAndAttribute_ConverterOnProp), ex.ToString());
+ Assert.Contains("IntProp", ex.ToString());
+
+ ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.SerializeWrapper(new ClassWithListPropAndAttribute_ConverterOnProp()));
+ Assert.Contains(nameof(ClassWithListPropAndAttribute_ConverterOnProp), ex.ToString());
+ Assert.Contains("IntProp", ex.ToString());
+
+#if !BUILDING_SOURCE_GENERATOR_TESTS
+ // Source-gen isn't currently validating that the converter on the test prop
+ // is invalid so JsonException is being thrown instead due to invalid JSON.
+ // [ActiveIssue("https://github.com/dotnet/runtime/issues/73714"]
+ ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.DeserializeWrapper<ClassWithDictPropAndAttribute_ConverterOnProp>(""));
+ Assert.Contains(nameof(ClassWithDictPropAndAttribute_ConverterOnProp), ex.ToString());
+ Assert.Contains("IntProp", ex.ToString());
+
+ ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await Serializer.SerializeWrapper(new ClassWithDictPropAndAttribute_ConverterOnProp()));
+ Assert.Contains(nameof(ClassWithDictPropAndAttribute_ConverterOnProp), ex.ToString());
+ Assert.Contains("IntProp", ex.ToString());
+#endif
+ }
+
+ public class ClassWithListPropAndAttribute_ConverterOnProp
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ [JsonConverter(typeof(ListOfIntConverter))]
+ public List<int> IntProp { get; set; }
+ }
+
+ public class ClassWithDictPropAndAttribute_ConverterOnProp
+ {
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ [JsonConverter(typeof(ClassWithDictPropAndAttribute_ConverterOnProp))]
+ public Dictionary<int, int?> IntProp { get; set; }
+ }
+
+ public class ListOfIntConverter : JsonConverter<List<int>>
+ {
+ public override List<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
+ public override void Write(Utf8JsonWriter writer, List<int> value, JsonSerializerOptions options) => throw new NotImplementedException();
+ }
+
+ [Fact]
+ public async Task InternalCollectionConverter_CustomNullableNumberConverter()
+ {
+ NotImplementedException ex;
+
+ var dict = new Dictionary<int, int?> { [1] = 1 };
+ var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
+ {
+ Converters = { new ConverterForNullableInt32() }
+ };
+
+ // Assert converter methods are called and not Read/WriteWithNumberHandling (which would throw InvalidOperationException).
+ // Converter returns 25 regardless of input.
+ Assert.Equal(25, (await Serializer.DeserializeWrapper<Dictionary<int, int?>>(@"{""1"":""1""}", options))[1]);
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(dict, options));
+ Assert.Equal("Converter was called", ex.Message);
+
+ var obj = await Serializer.DeserializeWrapper<ClassWithDictPropAndAttribute>(@"{""Prop"":{""1"":""1""}}", options);
+ Assert.Equal(25, obj.Prop[1]);
+ ex = await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(obj, options));
+ await Assert.ThrowsAsync<NotImplementedException>(async () => await Serializer.SerializeWrapper(dict, options));
+ Assert.Equal("Converter was called", ex.Message);
+ }
+
+ public class ConverterForNullableInt32 : JsonConverter<int?>
+ {
+ public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return 25;
+ }
+
+ public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException("Converter was called");
+ }
+ }
+
+ /// <summary>
+ /// Example of a custom converter that uses the options to determine behavior.
+ /// </summary>
+ [Fact]
+ public async Task AdaptableCustomConverter()
+ {
+ // Baseline without custom converter
+ PlainClassWithList obj = new() { Prop = new List<int>() { 1 } };
+ string json = await Serializer.SerializeWrapper(obj, s_optionReadAndWriteFromStr);
+ Assert.Equal("{\"Prop\":[\"1\"]}", json);
+
+ obj = await Serializer.DeserializeWrapper<PlainClassWithList>(json, s_optionReadAndWriteFromStr);
+ Assert.Equal(1, obj.Prop[0]);
+
+ // First with numbers
+ JsonSerializerOptions options = new()
+ {
+ Converters = { new AdaptableInt32Converter() }
+ };
+
+ obj = new PlainClassWithList() { Prop = new List<int>() { 1 } };
+ json = await Serializer.SerializeWrapper(obj, options);
+ Assert.Equal("{\"Prop\":[101]}", json);
+
+ obj = await Serializer.DeserializeWrapper<PlainClassWithList>(json, options);
+ Assert.Equal(1, obj.Prop[0]);
+
+ // Then with strings
+ options = new JsonSerializerOptions()
+ {
+ NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString,
+ Converters = { new AdaptableInt32Converter() }
+ };
+
+ obj = new PlainClassWithList() { Prop = new List<int>() { 1 } };
+ json = await Serializer.SerializeWrapper(obj, options);
+ Assert.Equal("{\"Prop\":[\"101\"]}", json);
+
+ obj = await Serializer.DeserializeWrapper<PlainClassWithList>(json, options);
+ Assert.Equal(1, obj.Prop[0]);
+ }
+
+ public class PlainClassWithList
+ {
+ public List<int> Prop { get; set; }
+ }
+
+ public class AdaptableInt32Converter : JsonConverter<int>
+ {
+ public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if ((JsonNumberHandling.AllowReadingFromString & options.NumberHandling) != 0)
+ {
+ // Assume it's a string; don't use TryParse().
+ return int.Parse(reader.GetString(), CultureInfo.InvariantCulture) - 100;
+ }
+ else
+ {
+ return reader.GetInt32() - 100;
+ }
+ }
+
+ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
+ {
+ if ((JsonNumberHandling.WriteAsString & options.NumberHandling) != 0)
+ {
+ writer.WriteStringValue((value + 100).ToString(CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ writer.WriteNumberValue(value + 100);
+ }
+ }
+ }
+ }
+}
{
throw new NotImplementedException("Converter was called");
}
+
+ // In source-gen, internal converters are not used as fallbacks when custom converters don't provide an implementation.
+#if BUILDING_SOURCE_GENERATOR_TESTS
+ public override int ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => int.Parse(reader.GetString());
+
+ public override void WriteAsPropertyName(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
+ => writer.WritePropertyName(value.ToString());
+#endif
}
public class SimpleSnakeCasePolicy : JsonNamingPolicy
public JsonTypeInfo<TypeWithValidationAttributes> TypeWithValidationAttributes { get; }
public JsonTypeInfo<TypeWithDerivedAttribute> TypeWithDerivedAttribute { get; }
public JsonTypeInfo<PolymorphicClass> PolymorphicClass { get; }
+ public JsonTypeInfo<PocoWithNumberHandlingAttr> PocoWithNumberHandlingAttr { get; }
}
internal partial class JsonContext : JsonSerializerContext
[JsonSerializable(typeof(TypeWithValidationAttributes))]
[JsonSerializable(typeof(TypeWithDerivedAttribute))]
[JsonSerializable(typeof(PolymorphicClass))]
+ [JsonSerializable(typeof(PocoWithNumberHandlingAttr))]
internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Default;
[JsonSerializable(typeof(TypeWithValidationAttributes), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Metadata)]
+ [JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Metadata)]
internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
[JsonSerializable(typeof(TypeWithValidationAttributes))]
[JsonSerializable(typeof(TypeWithDerivedAttribute))]
[JsonSerializable(typeof(PolymorphicClass))]
+ [JsonSerializable(typeof(PocoWithNumberHandlingAttr))]
internal partial class MetadataContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
[JsonSerializable(typeof(TypeWithValidationAttributes), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
internal partial class MixedModeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization;
Assert.True(derivedResult.Boolean);
}
}
+
+ [Fact]
+ public void NumberHandlingHonoredOnPoco()
+ {
+ if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
+ {
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new PocoWithNumberHandlingAttr(), DefaultContext.PocoWithNumberHandlingAttr));
+ }
+ else
+ {
+ JsonTestHelper.AssertJsonEqual(@"{""Id"":""0""}", JsonSerializer.Serialize(new PocoWithNumberHandlingAttr(), DefaultContext.PocoWithNumberHandlingAttr));
+ }
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Collections.ObjectModel;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Tests;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+ public sealed partial class NumberHandlingTests_Metadata : NumberHandlingTests
+ {
+ public NumberHandlingTests_Metadata()
+ : base(new StringSerializerWrapper(NumberHandlingTestsContext_Metadata.Default))
+ {
+ }
+
+ [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
+ [JsonSerializable(typeof(Class_With_BoxedNumbers))]
+ [JsonSerializable(typeof(Class_With_ListsOfBoxedNumbers))]
+ [JsonSerializable(typeof(Class_With_BoxedNonNumbers))]
+ [JsonSerializable(typeof(Class_With_ListsOfBoxedNonNumbers))]
+ [JsonSerializable(typeof(ClassWithNumbers))]
+ [JsonSerializable(typeof(ClassWith_StrictAttribute))]
+ [JsonSerializable(typeof(ClassWith_ReadAsStringAttribute))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<int[]>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<ConcurrentQueue<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<GenericICollectionWrapper<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<IEnumerable<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<Collection<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<ImmutableList<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<HashSet<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<List<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<IList<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<IList>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<Queue<int>>))]
+ [JsonSerializable(typeof(ClassWithComplexListProperty))]
+ [JsonSerializable(typeof(ClassWith_AttributeOnComplexListProperty))]
+ [JsonSerializable(typeof(ClassWith_AttributeOnComplexDictionaryProperty))]
+ [JsonSerializable(typeof(ClassWithSimpleDictionaryProperty))]
+ [JsonSerializable(typeof(ClassWithComplexDictionaryProperty))]
+ [JsonSerializable(typeof(MyCustomList))]
+ [JsonSerializable(typeof(ClassWithCustomList))]
+ [JsonSerializable(typeof(MyCustomDictionary))]
+ [JsonSerializable(typeof(ClassWithCustomDictionary))]
+ [JsonSerializable(typeof(AttributeAppliedToFirstLevelProp))]
+ [JsonSerializable(typeof(NonNumberType))]
+ [JsonSerializable(typeof(MyCustomListWrapper))]
+ [JsonSerializable(typeof(MyCustomDictionaryWrapper))]
+ [JsonSerializable(typeof(ClassWith_NumberHandlingOn_ObjectProperty))]
+ [JsonSerializable(typeof(ClassWith_NumberHandlingOn_Property_WithCustomConverter))]
+ [JsonSerializable(typeof(ClassWith_NumberHandlingOn_Type_WithCustomConverter))]
+ [JsonSerializable(typeof(Class_With_NullableUInt64_And_Float))]
+ [JsonSerializable(typeof(MyClassWithNumbers))]
+ [JsonSerializable(typeof(MyClassWithNumbers_PropsHasAttribute))]
+ [JsonSerializable(typeof(ClassWith_Attribute_OnNumber))]
+ [JsonSerializable(typeof(Type_AllowFloatConstants))]
+ [JsonSerializable(typeof(ClassWith_LooseAttribute))]
+ [JsonSerializable(typeof(ClassWith_Attribute_On_TypeAndMember))]
+ [JsonSerializable(typeof(ClassWithListPropAndAttribute))]
+ [JsonSerializable(typeof(ClassWithDictPropAndAttribute))]
+ [JsonSerializable(typeof(ClassWithListPropAndAttribute_ConverterOnProp))]
+ [JsonSerializable(typeof(ClassWithDictPropAndAttribute_ConverterOnProp))]
+ [JsonSerializable(typeof(PlainClassWithList))]
+ [JsonSerializable(typeof(StructWithNumbers))]
+ [JsonSerializable(typeof(DateTime))]
+ [JsonSerializable(typeof(Guid?))]
+ [JsonSerializable(typeof(byte))]
+ [JsonSerializable(typeof(sbyte))]
+ [JsonSerializable(typeof(short))]
+ [JsonSerializable(typeof(int))]
+ [JsonSerializable(typeof(long))]
+ [JsonSerializable(typeof(ushort))]
+ [JsonSerializable(typeof(uint))]
+ [JsonSerializable(typeof(ulong))]
+ [JsonSerializable(typeof(float))]
+ [JsonSerializable(typeof(double))]
+ [JsonSerializable(typeof(decimal))]
+ [JsonSerializable(typeof(byte?))]
+ [JsonSerializable(typeof(sbyte?))]
+ [JsonSerializable(typeof(short?))]
+ [JsonSerializable(typeof(int?))]
+ [JsonSerializable(typeof(long?))]
+ [JsonSerializable(typeof(ushort?))]
+ [JsonSerializable(typeof(uint?))]
+ [JsonSerializable(typeof(ulong?))]
+ [JsonSerializable(typeof(float?))]
+ [JsonSerializable(typeof(double?))]
+ [JsonSerializable(typeof(decimal?))]
+ [JsonSerializable(typeof(IEnumerable<string>))]
+ [JsonSerializable(typeof(List<float>))]
+ [JsonSerializable(typeof(List<double>))]
+ [JsonSerializable(typeof(List<decimal>))]
+ [JsonSerializable(typeof(KeyValuePair<ulong?, List<float>>))]
+ [JsonSerializable(typeof(Dictionary<int, float>))]
+ [JsonSerializable(typeof(Dictionary<string, ulong>))]
+ [JsonSerializable(typeof(JsonElement))]
+ [JsonSerializable(typeof(Queue))]
+ [JsonSerializable(typeof(WrapperForIList))]
+ [JsonSerializable(typeof(byte[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<byte>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<byte>))]
+ [JsonSerializable(typeof(IEnumerable<byte>))]
+ [JsonSerializable(typeof(Collection<byte>))]
+ [JsonSerializable(typeof(HashSet<byte>))]
+ [JsonSerializable(typeof(List<byte>))]
+ [JsonSerializable(typeof(Queue<byte>))]
+ [JsonSerializable(typeof(ImmutableList<byte>))]
+ [JsonSerializable(typeof(sbyte[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<sbyte>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<sbyte>))]
+ [JsonSerializable(typeof(IEnumerable<sbyte>))]
+ [JsonSerializable(typeof(Collection<sbyte>))]
+ [JsonSerializable(typeof(HashSet<sbyte>))]
+ [JsonSerializable(typeof(List<sbyte>))]
+ [JsonSerializable(typeof(Queue<sbyte>))]
+ [JsonSerializable(typeof(ImmutableList<sbyte>))]
+ [JsonSerializable(typeof(short[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<short>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<short>))]
+ [JsonSerializable(typeof(IEnumerable<short>))]
+ [JsonSerializable(typeof(Collection<short>))]
+ [JsonSerializable(typeof(HashSet<short>))]
+ [JsonSerializable(typeof(List<short>))]
+ [JsonSerializable(typeof(Queue<short>))]
+ [JsonSerializable(typeof(ImmutableList<short>))]
+ [JsonSerializable(typeof(int[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<int>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<int>))]
+ [JsonSerializable(typeof(IEnumerable<int>))]
+ [JsonSerializable(typeof(Collection<int>))]
+ [JsonSerializable(typeof(HashSet<int>))]
+ [JsonSerializable(typeof(List<int>))]
+ [JsonSerializable(typeof(Queue<int>))]
+ [JsonSerializable(typeof(ImmutableList<int>))]
+ [JsonSerializable(typeof(long[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<long>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<long>))]
+ [JsonSerializable(typeof(IEnumerable<long>))]
+ [JsonSerializable(typeof(Collection<long>))]
+ [JsonSerializable(typeof(HashSet<long>))]
+ [JsonSerializable(typeof(List<long>))]
+ [JsonSerializable(typeof(Queue<long>))]
+ [JsonSerializable(typeof(ImmutableList<long>))]
+ [JsonSerializable(typeof(ushort[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ushort>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ushort>))]
+ [JsonSerializable(typeof(IEnumerable<ushort>))]
+ [JsonSerializable(typeof(Collection<ushort>))]
+ [JsonSerializable(typeof(HashSet<ushort>))]
+ [JsonSerializable(typeof(List<ushort>))]
+ [JsonSerializable(typeof(Queue<ushort>))]
+ [JsonSerializable(typeof(ImmutableList<ushort>))]
+ [JsonSerializable(typeof(uint[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<uint>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<uint>))]
+ [JsonSerializable(typeof(IEnumerable<uint>))]
+ [JsonSerializable(typeof(Collection<uint>))]
+ [JsonSerializable(typeof(HashSet<uint>))]
+ [JsonSerializable(typeof(List<uint>))]
+ [JsonSerializable(typeof(Queue<uint>))]
+ [JsonSerializable(typeof(ImmutableList<uint>))]
+ [JsonSerializable(typeof(short?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<short?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<short?>))]
+ [JsonSerializable(typeof(IEnumerable<short?>))]
+ [JsonSerializable(typeof(Collection<short?>))]
+ [JsonSerializable(typeof(HashSet<short?>))]
+ [JsonSerializable(typeof(List<short?>))]
+ [JsonSerializable(typeof(Queue<short?>))]
+ [JsonSerializable(typeof(ImmutableList<short?>))]
+ [JsonSerializable(typeof(int?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<int?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<int?>))]
+ [JsonSerializable(typeof(IEnumerable<int?>))]
+ [JsonSerializable(typeof(Collection<int?>))]
+ [JsonSerializable(typeof(HashSet<int?>))]
+ [JsonSerializable(typeof(List<int?>))]
+ [JsonSerializable(typeof(Queue<int?>))]
+ [JsonSerializable(typeof(ImmutableList<int?>))]
+ [JsonSerializable(typeof(long?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<long?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<long?>))]
+ [JsonSerializable(typeof(IEnumerable<long?>))]
+ [JsonSerializable(typeof(Collection<long?>))]
+ [JsonSerializable(typeof(HashSet<long?>))]
+ [JsonSerializable(typeof(List<long?>))]
+ [JsonSerializable(typeof(Queue<long?>))]
+ [JsonSerializable(typeof(ImmutableList<long?>))]
+ [JsonSerializable(typeof(ushort?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ushort?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ushort?>))]
+ [JsonSerializable(typeof(IEnumerable<ushort?>))]
+ [JsonSerializable(typeof(Collection<ushort?>))]
+ [JsonSerializable(typeof(HashSet<ushort?>))]
+ [JsonSerializable(typeof(List<ushort?>))]
+ [JsonSerializable(typeof(Queue<ushort?>))]
+ [JsonSerializable(typeof(ImmutableList<ushort?>))]
+ [JsonSerializable(typeof(uint?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<uint?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<uint?>))]
+ [JsonSerializable(typeof(IEnumerable<uint?>))]
+ [JsonSerializable(typeof(Collection<uint?>))]
+ [JsonSerializable(typeof(HashSet<uint?>))]
+ [JsonSerializable(typeof(List<uint?>))]
+ [JsonSerializable(typeof(Queue<uint?>))]
+ [JsonSerializable(typeof(ImmutableList<uint?>))]
+ [JsonSerializable(typeof(ulong[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ulong>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ulong>))]
+ [JsonSerializable(typeof(IEnumerable<ulong>))]
+ [JsonSerializable(typeof(Collection<ulong>))]
+ [JsonSerializable(typeof(HashSet<ulong>))]
+ [JsonSerializable(typeof(List<ulong>))]
+ [JsonSerializable(typeof(Queue<ulong>))]
+ [JsonSerializable(typeof(ImmutableList<ulong>))]
+ [JsonSerializable(typeof(float[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<float>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<float>))]
+ [JsonSerializable(typeof(IEnumerable<float>))]
+ [JsonSerializable(typeof(Collection<float>))]
+ [JsonSerializable(typeof(HashSet<float>))]
+ [JsonSerializable(typeof(List<float>))]
+ [JsonSerializable(typeof(Queue<float>))]
+ [JsonSerializable(typeof(ImmutableList<float>))]
+ [JsonSerializable(typeof(double[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<double>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<double>))]
+ [JsonSerializable(typeof(IEnumerable<double>))]
+ [JsonSerializable(typeof(Collection<double>))]
+ [JsonSerializable(typeof(HashSet<double>))]
+ [JsonSerializable(typeof(List<double>))]
+ [JsonSerializable(typeof(Queue<double>))]
+ [JsonSerializable(typeof(ImmutableList<double>))]
+ [JsonSerializable(typeof(decimal[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<decimal>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<decimal>))]
+ [JsonSerializable(typeof(IEnumerable<decimal>))]
+ [JsonSerializable(typeof(Collection<decimal>))]
+ [JsonSerializable(typeof(HashSet<decimal>))]
+ [JsonSerializable(typeof(List<decimal>))]
+ [JsonSerializable(typeof(Queue<decimal>))]
+ [JsonSerializable(typeof(ImmutableList<decimal>))]
+ [JsonSerializable(typeof(List<byte>))]
+ [JsonSerializable(typeof(List<sbyte>))]
+ [JsonSerializable(typeof(List<short>))]
+ [JsonSerializable(typeof(List<int>))]
+ [JsonSerializable(typeof(List<long>))]
+ [JsonSerializable(typeof(List<ushort>))]
+ [JsonSerializable(typeof(List<uint>))]
+ [JsonSerializable(typeof(List<ulong>))]
+ [JsonSerializable(typeof(List<float>))]
+ [JsonSerializable(typeof(List<double>))]
+ [JsonSerializable(typeof(List<decimal>))]
+ [JsonSerializable(typeof(List<byte?>))]
+ [JsonSerializable(typeof(List<sbyte?>))]
+ [JsonSerializable(typeof(List<short?>))]
+ [JsonSerializable(typeof(List<int?>))]
+ [JsonSerializable(typeof(List<long?>))]
+ [JsonSerializable(typeof(List<ushort?>))]
+ [JsonSerializable(typeof(List<uint?>))]
+ [JsonSerializable(typeof(List<ulong?>))]
+ [JsonSerializable(typeof(List<float?>))]
+ [JsonSerializable(typeof(List<double?>))]
+ [JsonSerializable(typeof(List<decimal?>))]
+ [JsonSerializable(typeof(Hashtable))]
+ [JsonSerializable(typeof(byte?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<byte?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<byte?>))]
+ [JsonSerializable(typeof(IEnumerable<byte?>))]
+ [JsonSerializable(typeof(Collection<byte?>))]
+ [JsonSerializable(typeof(HashSet<byte?>))]
+ [JsonSerializable(typeof(List<byte?>))]
+ [JsonSerializable(typeof(Queue<byte?>))]
+ [JsonSerializable(typeof(ImmutableList<byte?>))]
+ [JsonSerializable(typeof(sbyte?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<sbyte?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<sbyte?>))]
+ [JsonSerializable(typeof(IEnumerable<sbyte?>))]
+ [JsonSerializable(typeof(Collection<sbyte?>))]
+ [JsonSerializable(typeof(HashSet<sbyte?>))]
+ [JsonSerializable(typeof(List<sbyte?>))]
+ [JsonSerializable(typeof(Queue<sbyte?>))]
+ [JsonSerializable(typeof(ImmutableList<sbyte?>))]
+ [JsonSerializable(typeof(short?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<short?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<short?>))]
+ [JsonSerializable(typeof(IEnumerable<short?>))]
+ [JsonSerializable(typeof(Collection<short?>))]
+ [JsonSerializable(typeof(HashSet<short?>))]
+ [JsonSerializable(typeof(List<short?>))]
+ [JsonSerializable(typeof(Queue<short?>))]
+ [JsonSerializable(typeof(ImmutableList<short?>))]
+ [JsonSerializable(typeof(int?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<int?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<int?>))]
+ [JsonSerializable(typeof(IEnumerable<int?>))]
+ [JsonSerializable(typeof(Collection<int?>))]
+ [JsonSerializable(typeof(HashSet<int?>))]
+ [JsonSerializable(typeof(List<int?>))]
+ [JsonSerializable(typeof(Queue<int?>))]
+ [JsonSerializable(typeof(ImmutableList<int?>))]
+ [JsonSerializable(typeof(long?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<long?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<long?>))]
+ [JsonSerializable(typeof(IEnumerable<long?>))]
+ [JsonSerializable(typeof(Collection<long?>))]
+ [JsonSerializable(typeof(HashSet<long?>))]
+ [JsonSerializable(typeof(List<long?>))]
+ [JsonSerializable(typeof(Queue<long?>))]
+ [JsonSerializable(typeof(ImmutableList<long?>))]
+ [JsonSerializable(typeof(ushort?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ushort?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ushort?>))]
+ [JsonSerializable(typeof(IEnumerable<ushort?>))]
+ [JsonSerializable(typeof(Collection<ushort?>))]
+ [JsonSerializable(typeof(HashSet<ushort?>))]
+ [JsonSerializable(typeof(List<ushort?>))]
+ [JsonSerializable(typeof(Queue<ushort?>))]
+ [JsonSerializable(typeof(ImmutableList<ushort?>))]
+ [JsonSerializable(typeof(uint?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<uint?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<uint?>))]
+ [JsonSerializable(typeof(IEnumerable<uint?>))]
+ [JsonSerializable(typeof(Collection<uint?>))]
+ [JsonSerializable(typeof(HashSet<uint?>))]
+ [JsonSerializable(typeof(List<uint?>))]
+ [JsonSerializable(typeof(Queue<uint?>))]
+ [JsonSerializable(typeof(ImmutableList<uint?>))]
+ [JsonSerializable(typeof(ulong?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ulong?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ulong?>))]
+ [JsonSerializable(typeof(IEnumerable<ulong?>))]
+ [JsonSerializable(typeof(Collection<ulong?>))]
+ [JsonSerializable(typeof(HashSet<ulong?>))]
+ [JsonSerializable(typeof(List<ulong?>))]
+ [JsonSerializable(typeof(Queue<ulong?>))]
+ [JsonSerializable(typeof(ImmutableList<ulong?>))]
+ [JsonSerializable(typeof(float?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<float?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<float?>))]
+ [JsonSerializable(typeof(IEnumerable<float?>))]
+ [JsonSerializable(typeof(Collection<float?>))]
+ [JsonSerializable(typeof(HashSet<float?>))]
+ [JsonSerializable(typeof(List<float?>))]
+ [JsonSerializable(typeof(Queue<float?>))]
+ [JsonSerializable(typeof(ImmutableList<float?>))]
+ [JsonSerializable(typeof(double?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<double?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<double?>))]
+ [JsonSerializable(typeof(IEnumerable<double?>))]
+ [JsonSerializable(typeof(Collection<double?>))]
+ [JsonSerializable(typeof(HashSet<double?>))]
+ [JsonSerializable(typeof(List<double?>))]
+ [JsonSerializable(typeof(Queue<double?>))]
+ [JsonSerializable(typeof(ImmutableList<double?>))]
+ [JsonSerializable(typeof(decimal?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<decimal?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<decimal?>))]
+ [JsonSerializable(typeof(IEnumerable<decimal?>))]
+ [JsonSerializable(typeof(Collection<decimal?>))]
+ [JsonSerializable(typeof(HashSet<decimal?>))]
+ [JsonSerializable(typeof(List<decimal?>))]
+ [JsonSerializable(typeof(Queue<decimal?>))]
+ [JsonSerializable(typeof(ImmutableList<decimal?>))]
+ [JsonSerializable(typeof(IDictionary))]
+ [JsonSerializable(typeof(Dictionary<string, object>))]
+ [JsonSerializable(typeof(Dictionary<string, ulong>))]
+ [JsonSerializable(typeof(ConcurrentDictionary<string, ulong>))]
+ [JsonSerializable(typeof(IDictionary<string, ulong>))]
+ [JsonSerializable(typeof(GenericIDictionaryWrapper<string, ulong>))]
+ [JsonSerializable(typeof(ImmutableDictionary<string, ulong>))]
+ [JsonSerializable(typeof(IReadOnlyDictionary<string, ulong>))]
+ [JsonSerializable(typeof(Dictionary<string, float>))]
+ [JsonSerializable(typeof(ConcurrentDictionary<string, float>))]
+ [JsonSerializable(typeof(IDictionary<string, float>))]
+ [JsonSerializable(typeof(GenericIDictionaryWrapper<string, float>))]
+ [JsonSerializable(typeof(ImmutableDictionary<string, float>))]
+ [JsonSerializable(typeof(IReadOnlyDictionary<string, float>))]
+ [JsonSerializable(typeof(Dictionary<string, double>))]
+ [JsonSerializable(typeof(ConcurrentDictionary<string, double>))]
+ [JsonSerializable(typeof(IDictionary<string, double>))]
+ [JsonSerializable(typeof(GenericIDictionaryWrapper<string, double>))]
+ [JsonSerializable(typeof(ImmutableDictionary<string, double>))]
+ [JsonSerializable(typeof(IReadOnlyDictionary<string, double>))]
+ [JsonSerializable(typeof(Dictionary<ulong, ulong>))]
+ [JsonSerializable(typeof(Dictionary<float, float>))]
+ [JsonSerializable(typeof(Dictionary<double, double>))]
+ [JsonSerializable(typeof(SortedList))]
+ internal sealed partial class NumberHandlingTestsContext_Metadata : JsonSerializerContext
+ {
+ }
+ }
+
+ public sealed partial class NumberHandlingTests_Default : NumberHandlingTests
+ {
+ public NumberHandlingTests_Default()
+ : base(new StringSerializerWrapper(NumberHandlingTestsContext_Default.Default))
+ {
+ }
+
+ [JsonSerializable(typeof(Class_With_BoxedNumbers))]
+ [JsonSerializable(typeof(Class_With_ListsOfBoxedNumbers))]
+ [JsonSerializable(typeof(Class_With_BoxedNonNumbers))]
+ [JsonSerializable(typeof(Class_With_ListsOfBoxedNonNumbers))]
+ [JsonSerializable(typeof(ClassWithNumbers))]
+ [JsonSerializable(typeof(ClassWith_StrictAttribute))]
+ [JsonSerializable(typeof(ClassWith_ReadAsStringAttribute))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<int[]>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<ConcurrentQueue<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<GenericICollectionWrapper<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<IEnumerable<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<Collection<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<ImmutableList<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<HashSet<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<List<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<IList<int>>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<IList>))]
+ [JsonSerializable(typeof(ClassWithSimpleCollectionProperty<Queue<int>>))]
+ [JsonSerializable(typeof(ClassWithComplexListProperty))]
+ [JsonSerializable(typeof(ClassWith_AttributeOnComplexListProperty))]
+ [JsonSerializable(typeof(ClassWith_AttributeOnComplexDictionaryProperty))]
+ [JsonSerializable(typeof(ClassWithSimpleDictionaryProperty))]
+ [JsonSerializable(typeof(ClassWithComplexDictionaryProperty))]
+ [JsonSerializable(typeof(MyCustomList))]
+ [JsonSerializable(typeof(ClassWithCustomList))]
+ [JsonSerializable(typeof(MyCustomDictionary))]
+ [JsonSerializable(typeof(ClassWithCustomDictionary))]
+ [JsonSerializable(typeof(AttributeAppliedToFirstLevelProp))]
+ [JsonSerializable(typeof(NonNumberType))]
+ [JsonSerializable(typeof(MyCustomListWrapper))]
+ [JsonSerializable(typeof(MyCustomDictionaryWrapper))]
+ [JsonSerializable(typeof(ClassWith_NumberHandlingOn_ObjectProperty))]
+ [JsonSerializable(typeof(ClassWith_NumberHandlingOn_Property_WithCustomConverter))]
+ [JsonSerializable(typeof(ClassWith_NumberHandlingOn_Type_WithCustomConverter))]
+ [JsonSerializable(typeof(Class_With_NullableUInt64_And_Float))]
+ [JsonSerializable(typeof(MyClassWithNumbers))]
+ [JsonSerializable(typeof(MyClassWithNumbers_PropsHasAttribute))]
+ [JsonSerializable(typeof(ClassWith_Attribute_OnNumber))]
+ [JsonSerializable(typeof(Type_AllowFloatConstants))]
+ [JsonSerializable(typeof(ClassWith_LooseAttribute))]
+ [JsonSerializable(typeof(ClassWith_Attribute_On_TypeAndMember))]
+ [JsonSerializable(typeof(ClassWithListPropAndAttribute))]
+ [JsonSerializable(typeof(ClassWithDictPropAndAttribute))]
+ [JsonSerializable(typeof(ClassWithListPropAndAttribute_ConverterOnProp))]
+ [JsonSerializable(typeof(ClassWithDictPropAndAttribute_ConverterOnProp))]
+ [JsonSerializable(typeof(PlainClassWithList))]
+ [JsonSerializable(typeof(StructWithNumbers))]
+ [JsonSerializable(typeof(DateTime))]
+ [JsonSerializable(typeof(Guid?))]
+ [JsonSerializable(typeof(byte))]
+ [JsonSerializable(typeof(sbyte))]
+ [JsonSerializable(typeof(short))]
+ [JsonSerializable(typeof(int))]
+ [JsonSerializable(typeof(long))]
+ [JsonSerializable(typeof(ushort))]
+ [JsonSerializable(typeof(uint))]
+ [JsonSerializable(typeof(ulong))]
+ [JsonSerializable(typeof(float))]
+ [JsonSerializable(typeof(double))]
+ [JsonSerializable(typeof(decimal))]
+ [JsonSerializable(typeof(byte?))]
+ [JsonSerializable(typeof(sbyte?))]
+ [JsonSerializable(typeof(short?))]
+ [JsonSerializable(typeof(int?))]
+ [JsonSerializable(typeof(long?))]
+ [JsonSerializable(typeof(ushort?))]
+ [JsonSerializable(typeof(uint?))]
+ [JsonSerializable(typeof(ulong?))]
+ [JsonSerializable(typeof(float?))]
+ [JsonSerializable(typeof(double?))]
+ [JsonSerializable(typeof(decimal?))]
+ [JsonSerializable(typeof(IEnumerable<string>))]
+ [JsonSerializable(typeof(List<float>))]
+ [JsonSerializable(typeof(List<double>))]
+ [JsonSerializable(typeof(List<decimal>))]
+ [JsonSerializable(typeof(KeyValuePair<ulong?, List<float>>))]
+ [JsonSerializable(typeof(Dictionary<int, float>))]
+ [JsonSerializable(typeof(Dictionary<string, ulong>))]
+ [JsonSerializable(typeof(JsonElement))]
+ [JsonSerializable(typeof(Queue))]
+ [JsonSerializable(typeof(WrapperForIList))]
+ [JsonSerializable(typeof(byte[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<byte>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<byte>))]
+ [JsonSerializable(typeof(IEnumerable<byte>))]
+ [JsonSerializable(typeof(Collection<byte>))]
+ [JsonSerializable(typeof(HashSet<byte>))]
+ [JsonSerializable(typeof(List<byte>))]
+ [JsonSerializable(typeof(Queue<byte>))]
+ [JsonSerializable(typeof(ImmutableList<byte>))]
+ [JsonSerializable(typeof(sbyte[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<sbyte>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<sbyte>))]
+ [JsonSerializable(typeof(IEnumerable<sbyte>))]
+ [JsonSerializable(typeof(Collection<sbyte>))]
+ [JsonSerializable(typeof(HashSet<sbyte>))]
+ [JsonSerializable(typeof(List<sbyte>))]
+ [JsonSerializable(typeof(Queue<sbyte>))]
+ [JsonSerializable(typeof(ImmutableList<sbyte>))]
+ [JsonSerializable(typeof(short[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<short>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<short>))]
+ [JsonSerializable(typeof(IEnumerable<short>))]
+ [JsonSerializable(typeof(Collection<short>))]
+ [JsonSerializable(typeof(HashSet<short>))]
+ [JsonSerializable(typeof(List<short>))]
+ [JsonSerializable(typeof(Queue<short>))]
+ [JsonSerializable(typeof(ImmutableList<short>))]
+ [JsonSerializable(typeof(int[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<int>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<int>))]
+ [JsonSerializable(typeof(IEnumerable<int>))]
+ [JsonSerializable(typeof(Collection<int>))]
+ [JsonSerializable(typeof(HashSet<int>))]
+ [JsonSerializable(typeof(List<int>))]
+ [JsonSerializable(typeof(Queue<int>))]
+ [JsonSerializable(typeof(ImmutableList<int>))]
+ [JsonSerializable(typeof(long[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<long>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<long>))]
+ [JsonSerializable(typeof(IEnumerable<long>))]
+ [JsonSerializable(typeof(Collection<long>))]
+ [JsonSerializable(typeof(HashSet<long>))]
+ [JsonSerializable(typeof(List<long>))]
+ [JsonSerializable(typeof(Queue<long>))]
+ [JsonSerializable(typeof(ImmutableList<long>))]
+ [JsonSerializable(typeof(ushort[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ushort>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ushort>))]
+ [JsonSerializable(typeof(IEnumerable<ushort>))]
+ [JsonSerializable(typeof(Collection<ushort>))]
+ [JsonSerializable(typeof(HashSet<ushort>))]
+ [JsonSerializable(typeof(List<ushort>))]
+ [JsonSerializable(typeof(Queue<ushort>))]
+ [JsonSerializable(typeof(ImmutableList<ushort>))]
+ [JsonSerializable(typeof(uint[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<uint>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<uint>))]
+ [JsonSerializable(typeof(IEnumerable<uint>))]
+ [JsonSerializable(typeof(Collection<uint>))]
+ [JsonSerializable(typeof(HashSet<uint>))]
+ [JsonSerializable(typeof(List<uint>))]
+ [JsonSerializable(typeof(Queue<uint>))]
+ [JsonSerializable(typeof(ImmutableList<uint>))]
+ [JsonSerializable(typeof(short?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<short?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<short?>))]
+ [JsonSerializable(typeof(IEnumerable<short?>))]
+ [JsonSerializable(typeof(Collection<short?>))]
+ [JsonSerializable(typeof(HashSet<short?>))]
+ [JsonSerializable(typeof(List<short?>))]
+ [JsonSerializable(typeof(Queue<short?>))]
+ [JsonSerializable(typeof(ImmutableList<short?>))]
+ [JsonSerializable(typeof(int?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<int?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<int?>))]
+ [JsonSerializable(typeof(IEnumerable<int?>))]
+ [JsonSerializable(typeof(Collection<int?>))]
+ [JsonSerializable(typeof(HashSet<int?>))]
+ [JsonSerializable(typeof(List<int?>))]
+ [JsonSerializable(typeof(Queue<int?>))]
+ [JsonSerializable(typeof(ImmutableList<int?>))]
+ [JsonSerializable(typeof(long?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<long?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<long?>))]
+ [JsonSerializable(typeof(IEnumerable<long?>))]
+ [JsonSerializable(typeof(Collection<long?>))]
+ [JsonSerializable(typeof(HashSet<long?>))]
+ [JsonSerializable(typeof(List<long?>))]
+ [JsonSerializable(typeof(Queue<long?>))]
+ [JsonSerializable(typeof(ImmutableList<long?>))]
+ [JsonSerializable(typeof(ushort?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ushort?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ushort?>))]
+ [JsonSerializable(typeof(IEnumerable<ushort?>))]
+ [JsonSerializable(typeof(Collection<ushort?>))]
+ [JsonSerializable(typeof(HashSet<ushort?>))]
+ [JsonSerializable(typeof(List<ushort?>))]
+ [JsonSerializable(typeof(Queue<ushort?>))]
+ [JsonSerializable(typeof(ImmutableList<ushort?>))]
+ [JsonSerializable(typeof(uint?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<uint?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<uint?>))]
+ [JsonSerializable(typeof(IEnumerable<uint?>))]
+ [JsonSerializable(typeof(Collection<uint?>))]
+ [JsonSerializable(typeof(HashSet<uint?>))]
+ [JsonSerializable(typeof(List<uint?>))]
+ [JsonSerializable(typeof(Queue<uint?>))]
+ [JsonSerializable(typeof(ImmutableList<uint?>))]
+ [JsonSerializable(typeof(ulong[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ulong>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ulong>))]
+ [JsonSerializable(typeof(IEnumerable<ulong>))]
+ [JsonSerializable(typeof(Collection<ulong>))]
+ [JsonSerializable(typeof(HashSet<ulong>))]
+ [JsonSerializable(typeof(List<ulong>))]
+ [JsonSerializable(typeof(Queue<ulong>))]
+ [JsonSerializable(typeof(ImmutableList<ulong>))]
+ [JsonSerializable(typeof(float[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<float>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<float>))]
+ [JsonSerializable(typeof(IEnumerable<float>))]
+ [JsonSerializable(typeof(Collection<float>))]
+ [JsonSerializable(typeof(HashSet<float>))]
+ [JsonSerializable(typeof(List<float>))]
+ [JsonSerializable(typeof(Queue<float>))]
+ [JsonSerializable(typeof(ImmutableList<float>))]
+ [JsonSerializable(typeof(double[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<double>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<double>))]
+ [JsonSerializable(typeof(IEnumerable<double>))]
+ [JsonSerializable(typeof(Collection<double>))]
+ [JsonSerializable(typeof(HashSet<double>))]
+ [JsonSerializable(typeof(List<double>))]
+ [JsonSerializable(typeof(Queue<double>))]
+ [JsonSerializable(typeof(ImmutableList<double>))]
+ [JsonSerializable(typeof(decimal[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<decimal>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<decimal>))]
+ [JsonSerializable(typeof(IEnumerable<decimal>))]
+ [JsonSerializable(typeof(Collection<decimal>))]
+ [JsonSerializable(typeof(HashSet<decimal>))]
+ [JsonSerializable(typeof(List<decimal>))]
+ [JsonSerializable(typeof(Queue<decimal>))]
+ [JsonSerializable(typeof(ImmutableList<decimal>))]
+ [JsonSerializable(typeof(List<byte>))]
+ [JsonSerializable(typeof(List<sbyte>))]
+ [JsonSerializable(typeof(List<short>))]
+ [JsonSerializable(typeof(List<int>))]
+ [JsonSerializable(typeof(List<long>))]
+ [JsonSerializable(typeof(List<ushort>))]
+ [JsonSerializable(typeof(List<uint>))]
+ [JsonSerializable(typeof(List<ulong>))]
+ [JsonSerializable(typeof(List<float>))]
+ [JsonSerializable(typeof(List<double>))]
+ [JsonSerializable(typeof(List<decimal>))]
+ [JsonSerializable(typeof(List<byte?>))]
+ [JsonSerializable(typeof(List<sbyte?>))]
+ [JsonSerializable(typeof(List<short?>))]
+ [JsonSerializable(typeof(List<int?>))]
+ [JsonSerializable(typeof(List<long?>))]
+ [JsonSerializable(typeof(List<ushort?>))]
+ [JsonSerializable(typeof(List<uint?>))]
+ [JsonSerializable(typeof(List<ulong?>))]
+ [JsonSerializable(typeof(List<float?>))]
+ [JsonSerializable(typeof(List<double?>))]
+ [JsonSerializable(typeof(List<decimal?>))]
+ [JsonSerializable(typeof(Hashtable))]
+ [JsonSerializable(typeof(byte?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<byte?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<byte?>))]
+ [JsonSerializable(typeof(IEnumerable<byte?>))]
+ [JsonSerializable(typeof(Collection<byte?>))]
+ [JsonSerializable(typeof(HashSet<byte?>))]
+ [JsonSerializable(typeof(List<byte?>))]
+ [JsonSerializable(typeof(Queue<byte?>))]
+ [JsonSerializable(typeof(ImmutableList<byte?>))]
+ [JsonSerializable(typeof(sbyte?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<sbyte?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<sbyte?>))]
+ [JsonSerializable(typeof(IEnumerable<sbyte?>))]
+ [JsonSerializable(typeof(Collection<sbyte?>))]
+ [JsonSerializable(typeof(HashSet<sbyte?>))]
+ [JsonSerializable(typeof(List<sbyte?>))]
+ [JsonSerializable(typeof(Queue<sbyte?>))]
+ [JsonSerializable(typeof(ImmutableList<sbyte?>))]
+ [JsonSerializable(typeof(short?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<short?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<short?>))]
+ [JsonSerializable(typeof(IEnumerable<short?>))]
+ [JsonSerializable(typeof(Collection<short?>))]
+ [JsonSerializable(typeof(HashSet<short?>))]
+ [JsonSerializable(typeof(List<short?>))]
+ [JsonSerializable(typeof(Queue<short?>))]
+ [JsonSerializable(typeof(ImmutableList<short?>))]
+ [JsonSerializable(typeof(int?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<int?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<int?>))]
+ [JsonSerializable(typeof(IEnumerable<int?>))]
+ [JsonSerializable(typeof(Collection<int?>))]
+ [JsonSerializable(typeof(HashSet<int?>))]
+ [JsonSerializable(typeof(List<int?>))]
+ [JsonSerializable(typeof(Queue<int?>))]
+ [JsonSerializable(typeof(ImmutableList<int?>))]
+ [JsonSerializable(typeof(long?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<long?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<long?>))]
+ [JsonSerializable(typeof(IEnumerable<long?>))]
+ [JsonSerializable(typeof(Collection<long?>))]
+ [JsonSerializable(typeof(HashSet<long?>))]
+ [JsonSerializable(typeof(List<long?>))]
+ [JsonSerializable(typeof(Queue<long?>))]
+ [JsonSerializable(typeof(ImmutableList<long?>))]
+ [JsonSerializable(typeof(ushort?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ushort?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ushort?>))]
+ [JsonSerializable(typeof(IEnumerable<ushort?>))]
+ [JsonSerializable(typeof(Collection<ushort?>))]
+ [JsonSerializable(typeof(HashSet<ushort?>))]
+ [JsonSerializable(typeof(List<ushort?>))]
+ [JsonSerializable(typeof(Queue<ushort?>))]
+ [JsonSerializable(typeof(ImmutableList<ushort?>))]
+ [JsonSerializable(typeof(uint?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<uint?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<uint?>))]
+ [JsonSerializable(typeof(IEnumerable<uint?>))]
+ [JsonSerializable(typeof(Collection<uint?>))]
+ [JsonSerializable(typeof(HashSet<uint?>))]
+ [JsonSerializable(typeof(List<uint?>))]
+ [JsonSerializable(typeof(Queue<uint?>))]
+ [JsonSerializable(typeof(ImmutableList<uint?>))]
+ [JsonSerializable(typeof(ulong?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<ulong?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<ulong?>))]
+ [JsonSerializable(typeof(IEnumerable<ulong?>))]
+ [JsonSerializable(typeof(Collection<ulong?>))]
+ [JsonSerializable(typeof(HashSet<ulong?>))]
+ [JsonSerializable(typeof(List<ulong?>))]
+ [JsonSerializable(typeof(Queue<ulong?>))]
+ [JsonSerializable(typeof(ImmutableList<ulong?>))]
+ [JsonSerializable(typeof(float?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<float?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<float?>))]
+ [JsonSerializable(typeof(IEnumerable<float?>))]
+ [JsonSerializable(typeof(Collection<float?>))]
+ [JsonSerializable(typeof(HashSet<float?>))]
+ [JsonSerializable(typeof(List<float?>))]
+ [JsonSerializable(typeof(Queue<float?>))]
+ [JsonSerializable(typeof(ImmutableList<float?>))]
+ [JsonSerializable(typeof(double?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<double?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<double?>))]
+ [JsonSerializable(typeof(IEnumerable<double?>))]
+ [JsonSerializable(typeof(Collection<double?>))]
+ [JsonSerializable(typeof(HashSet<double?>))]
+ [JsonSerializable(typeof(List<double?>))]
+ [JsonSerializable(typeof(Queue<double?>))]
+ [JsonSerializable(typeof(ImmutableList<double?>))]
+ [JsonSerializable(typeof(decimal?[]))]
+ [JsonSerializable(typeof(ConcurrentQueue<decimal?>))]
+ [JsonSerializable(typeof(GenericICollectionWrapper<decimal?>))]
+ [JsonSerializable(typeof(IEnumerable<decimal?>))]
+ [JsonSerializable(typeof(Collection<decimal?>))]
+ [JsonSerializable(typeof(HashSet<decimal?>))]
+ [JsonSerializable(typeof(List<decimal?>))]
+ [JsonSerializable(typeof(Queue<decimal?>))]
+ [JsonSerializable(typeof(ImmutableList<decimal?>))]
+ [JsonSerializable(typeof(IDictionary))]
+ [JsonSerializable(typeof(Dictionary<string, object>))]
+ [JsonSerializable(typeof(Dictionary<string, ulong>))]
+ [JsonSerializable(typeof(ConcurrentDictionary<string, ulong>))]
+ [JsonSerializable(typeof(IDictionary<string, ulong>))]
+ [JsonSerializable(typeof(GenericIDictionaryWrapper<string, ulong>))]
+ [JsonSerializable(typeof(ImmutableDictionary<string, ulong>))]
+ [JsonSerializable(typeof(IReadOnlyDictionary<string, ulong>))]
+ [JsonSerializable(typeof(Dictionary<string, float>))]
+ [JsonSerializable(typeof(ConcurrentDictionary<string, float>))]
+ [JsonSerializable(typeof(IDictionary<string, float>))]
+ [JsonSerializable(typeof(GenericIDictionaryWrapper<string, float>))]
+ [JsonSerializable(typeof(ImmutableDictionary<string, float>))]
+ [JsonSerializable(typeof(IReadOnlyDictionary<string, float>))]
+ [JsonSerializable(typeof(Dictionary<string, double>))]
+ [JsonSerializable(typeof(ConcurrentDictionary<string, double>))]
+ [JsonSerializable(typeof(IDictionary<string, double>))]
+ [JsonSerializable(typeof(GenericIDictionaryWrapper<string, double>))]
+ [JsonSerializable(typeof(ImmutableDictionary<string, double>))]
+ [JsonSerializable(typeof(IReadOnlyDictionary<string, double>))]
+ [JsonSerializable(typeof(Dictionary<ulong, ulong>))]
+ [JsonSerializable(typeof(Dictionary<float, float>))]
+ [JsonSerializable(typeof(Dictionary<double, double>))]
+ [JsonSerializable(typeof(SortedList))]
+ internal sealed partial class NumberHandlingTestsContext_Default : JsonSerializerContext
+ {
+ }
+ }
+}
[JsonSerializable(typeof(TypeWithValidationAttributes))]
[JsonSerializable(typeof(TypeWithDerivedAttribute))]
[JsonSerializable(typeof(PolymorphicClass))]
+ [JsonSerializable(typeof(PocoWithNumberHandlingAttr))]
internal partial class SerializationContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
[JsonSerializable(typeof(TypeWithValidationAttributes), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
[JsonSerializable(typeof(TypeWithValidationAttributes), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
<Compile Include="..\Common\JsonCreationHandlingTests.Enumerable.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonCreationHandlingTests.Enumerable.cs" />
<Compile Include="..\Common\JsonCreationHandlingTests.Generic.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonCreationHandlingTests.Generic.cs" />
<Compile Include="..\Common\JsonCreationHandlingTests.Object.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonCreationHandlingTests.Object.cs" />
+ <Compile Include="..\Common\JsonNumberTestData.cs" Link="CommonTest\System\Text\Json\Tests\JsonNumberTestData" />
<Compile Include="..\Common\NodeInteropTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\NodeInteropTests.cs" />
+ <Compile Include="..\Common\NumberHandlingTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\NumberHandlingTests.cs" />
<Compile Include="..\Common\PropertyNameTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\PropertyNameTests.cs" />
<Compile Include="..\Common\PropertyVisibilityTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\PropertyVisibilityTests.cs" />
<Compile Include="..\Common\PropertyVisibilityTests.InitOnly.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\PropertyVisibilityTests.InitOnly.cs" />
<Compile Include="Serialization\ReferenceHandlerTests.cs" />
<Compile Include="Serialization\ReferenceHandlerTests.IgnoreCycles.cs" />
<Compile Include="Serialization\NodeInteropTests.cs" />
+ <Compile Include="Serialization\NumberHandlingTests.cs" />
<Compile Include="Serialization\PropertyNameTests.cs" />
<Compile Include="Serialization\PropertyVisibilityTests.cs" />
<Compile Include="Serialization\UnmappedMemberHandlingTests.cs" />
public ClassWithDictionaryProperty(Dictionary<string, object?> property) => DictionaryProperty = property;
public Dictionary<string, object?> DictionaryProperty { get; }
}
+
+ [JsonNumberHandling(JsonNumberHandling.WriteAsString)]
+ public class PocoWithNumberHandlingAttr
+ {
+ public int Id { get; set; }
+ }
}
{
internal static partial class JsonTestHelper
{
-#if NETCOREAPP
- public const string DoubleFormatString = null;
- public const string SingleFormatString = null;
-#else
- public const string DoubleFormatString = "G17";
- public const string SingleFormatString = "G9";
-#endif
-
public static string NewtonsoftReturnStringHelper(TextReader reader)
{
var sb = new StringBuilder();
return arrayList;
}
- public static float NextFloat(Random random)
- {
- double mantissa = (random.NextDouble() * 2.0) - 1.0;
- double exponent = Math.Pow(2.0, random.Next(-126, 128));
- float value = (float)(mantissa * exponent);
- return value;
- }
-
- public static double NextDouble(Random random, double minValue, double maxValue)
- {
- double value = random.NextDouble() * (maxValue - minValue) + minValue;
- return value;
- }
-
- public static decimal NextDecimal(Random random, double minValue, double maxValue)
- {
- double value = random.NextDouble() * (maxValue - minValue) + minValue;
- return (decimal)value;
- }
-
public static string GetCompactString(string jsonString)
{
using (var jsonReader = new JsonTextReader(new StringReader(jsonString)) { MaxDepth = null })
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Collections.ObjectModel;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text.Encodings.Web;
-using System.Text.Json.Tests;
using System.Threading.Tasks;
using Xunit;
-using System.Runtime.InteropServices;
namespace System.Text.Json.Serialization.Tests
{
- public static partial class NumberHandlingTests
+ public sealed partial class NumberHandlingTestsDynamic : NumberHandlingTests
{
- private static readonly JsonSerializerOptions s_optionReadFromStr = new JsonSerializerOptions
- {
- NumberHandling = JsonNumberHandling.AllowReadingFromString
- };
-
- private static readonly JsonSerializerOptions s_optionWriteAsStr = new JsonSerializerOptions
- {
- NumberHandling = JsonNumberHandling.WriteAsString
- };
-
- private static readonly JsonSerializerOptions s_optionReadAndWriteFromStr = new JsonSerializerOptions
- {
- NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString
- };
-
- private static readonly JsonSerializerOptions s_optionsAllowFloatConstants = new JsonSerializerOptions
- {
- NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
- };
-
- private static readonly JsonSerializerOptions s_optionReadFromStrAllowFloatConstants = new JsonSerializerOptions
- {
- NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals
- };
-
- private static readonly JsonSerializerOptions s_optionWriteAsStrAllowFloatConstants = new JsonSerializerOptions
- {
- NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals
- };
-
- [Fact]
- public static void Number_AsRootType_RoundTrip()
- {
- RunAsRootTypeTest(JsonNumberTestData.Bytes);
- RunAsRootTypeTest(JsonNumberTestData.SBytes);
- RunAsRootTypeTest(JsonNumberTestData.Shorts);
- RunAsRootTypeTest(JsonNumberTestData.Ints);
- RunAsRootTypeTest(JsonNumberTestData.Longs);
- RunAsRootTypeTest(JsonNumberTestData.UShorts);
- RunAsRootTypeTest(JsonNumberTestData.UInts);
- RunAsRootTypeTest(JsonNumberTestData.ULongs);
- RunAsRootTypeTest(JsonNumberTestData.Floats);
- RunAsRootTypeTest(JsonNumberTestData.Doubles);
- RunAsRootTypeTest(JsonNumberTestData.Decimals);
- RunAsRootTypeTest(JsonNumberTestData.NullableBytes);
- RunAsRootTypeTest(JsonNumberTestData.NullableSBytes);
- RunAsRootTypeTest(JsonNumberTestData.NullableShorts);
- RunAsRootTypeTest(JsonNumberTestData.NullableInts);
- RunAsRootTypeTest(JsonNumberTestData.NullableLongs);
- RunAsRootTypeTest(JsonNumberTestData.NullableUShorts);
- RunAsRootTypeTest(JsonNumberTestData.NullableUInts);
- RunAsRootTypeTest(JsonNumberTestData.NullableULongs);
- RunAsRootTypeTest(JsonNumberTestData.NullableFloats);
- RunAsRootTypeTest(JsonNumberTestData.NullableDoubles);
- RunAsRootTypeTest(JsonNumberTestData.NullableDecimals);
- }
-
- private static void RunAsRootTypeTest<T>(List<T> numbers)
- {
- foreach (T number in numbers)
- {
- string numberAsString = GetNumberAsString(number);
- string json = $"{numberAsString}";
- string jsonWithNumberAsString = @$"""{numberAsString}""";
- PerformAsRootTypeSerialization(number, json, jsonWithNumberAsString);
- }
- }
-
- private static string GetNumberAsString<T>(T number)
- {
- return number switch
- {
- double @double => @double.ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture),
- float @float => @float.ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture),
- decimal @decimal => @decimal.ToString(CultureInfo.InvariantCulture),
- _ => number.ToString()
- };
- }
-
- private static void PerformAsRootTypeSerialization<T>(T number, string jsonWithNumberAsNumber, string jsonWithNumberAsString)
- {
- // Option: read from string
-
- // Deserialize
- Assert.Equal(number, JsonSerializer.Deserialize<T>(jsonWithNumberAsNumber, s_optionReadFromStr));
- Assert.Equal(number, JsonSerializer.Deserialize<T>(jsonWithNumberAsString, s_optionReadFromStr));
-
- // Serialize
- Assert.Equal(jsonWithNumberAsNumber, JsonSerializer.Serialize(number, s_optionReadFromStr));
-
- // Option: write as string
-
- // Deserialize
- Assert.Equal(number, JsonSerializer.Deserialize<T>(jsonWithNumberAsNumber, s_optionWriteAsStr));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<T>(jsonWithNumberAsString, s_optionWriteAsStr));
-
- // Serialize
- Assert.Equal(jsonWithNumberAsString, JsonSerializer.Serialize(number, s_optionWriteAsStr));
-
- // Option: read and write from/to string
-
- // Deserialize
- Assert.Equal(number, JsonSerializer.Deserialize<T>(jsonWithNumberAsNumber, s_optionReadAndWriteFromStr));
- Assert.Equal(number, JsonSerializer.Deserialize<T>(jsonWithNumberAsString, s_optionReadAndWriteFromStr));
-
- // Serialize
- Assert.Equal(jsonWithNumberAsString, JsonSerializer.Serialize(number, s_optionReadAndWriteFromStr));
- }
-
- [Fact]
- public static void Number_AsBoxed_RootType()
- {
- string numberAsString = @"""2""";
-
- int @int = 2;
- float @float = 2;
- int? nullableInt = 2;
- float? nullableFloat = 2;
-
- Assert.Equal(numberAsString, JsonSerializer.Serialize((object)@int, s_optionReadAndWriteFromStr));
- Assert.Equal(numberAsString, JsonSerializer.Serialize((object)@float, s_optionReadAndWriteFromStr));
- Assert.Equal(numberAsString, JsonSerializer.Serialize((object)nullableInt, s_optionReadAndWriteFromStr));
- Assert.Equal(numberAsString, JsonSerializer.Serialize((object)nullableFloat, s_optionReadAndWriteFromStr));
-
- Assert.Equal(2, (int)JsonSerializer.Deserialize(numberAsString, typeof(int), s_optionReadAndWriteFromStr));
- Assert.Equal(2, (float)JsonSerializer.Deserialize(numberAsString, typeof(float), s_optionReadAndWriteFromStr));
- Assert.Equal(2, (int?)JsonSerializer.Deserialize(numberAsString, typeof(int?), s_optionReadAndWriteFromStr));
- Assert.Equal(2, (float?)JsonSerializer.Deserialize(numberAsString, typeof(float?), s_optionReadAndWriteFromStr));
- }
-
- [Fact]
- public static void Number_AsBoxed_Property()
- {
- int @int = 1;
- float? nullableFloat = 2;
-
- string expected = @"{""MyInt"":""1"",""MyNullableFloat"":""2""}";
-
- var obj = new Class_With_BoxedNumbers
- {
- MyInt = @int,
- MyNullableFloat = nullableFloat
- };
-
- string serialized = JsonSerializer.Serialize(obj);
- JsonTestHelper.AssertJsonEqual(expected, serialized);
-
- obj = JsonSerializer.Deserialize<Class_With_BoxedNumbers>(serialized);
-
- JsonElement el = Assert.IsType<JsonElement>(obj.MyInt);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal("1", el.GetString());
-
- el = Assert.IsType<JsonElement>(obj.MyNullableFloat);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal("2", el.GetString());
- }
-
- public class Class_With_BoxedNumbers
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public object MyInt { get; set; }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public object MyNullableFloat { get; set; }
- }
-
- [Fact]
- public static void Number_AsBoxed_CollectionRootType_Element()
- {
- int @int = 1;
- float? nullableFloat = 2;
-
- string expected = @"[""1""]";
-
- var obj = new List<object> { @int };
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
- Assert.Equal(expected, serialized);
-
- obj = JsonSerializer.Deserialize<List<object>>(serialized, s_optionReadAndWriteFromStr);
-
- JsonElement el = Assert.IsType<JsonElement>(obj[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal("1", el.GetString());
-
- expected = @"[""2""]";
-
- IList obj2 = new object[] { nullableFloat };
- serialized = JsonSerializer.Serialize(obj2, s_optionReadAndWriteFromStr);
- Assert.Equal(expected, serialized);
-
- obj2 = JsonSerializer.Deserialize<IList>(serialized, s_optionReadAndWriteFromStr);
-
- el = Assert.IsType<JsonElement>(obj2[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal("2", el.GetString());
- }
-
- [Fact]
- public static void Number_AsBoxed_CollectionProperty_Element()
- {
- int @int = 2;
- float? nullableFloat = 2;
-
- string expected = @"{""MyInts"":[""2""],""MyNullableFloats"":[""2""]}";
-
- var obj = new Class_With_ListsOfBoxedNumbers
- {
- MyInts = new List<object> { @int },
- MyNullableFloats = new object[] { nullableFloat }
- };
-
- string serialized = JsonSerializer.Serialize(obj);
- JsonTestHelper.AssertJsonEqual(expected, serialized);
-
- obj = JsonSerializer.Deserialize<Class_With_ListsOfBoxedNumbers>(serialized);
-
- JsonElement el = Assert.IsType<JsonElement>(obj.MyInts[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal("2", el.GetString());
-
- el = Assert.IsType<JsonElement>(obj.MyNullableFloats[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal("2", el.GetString());
- }
-
- public class Class_With_ListsOfBoxedNumbers
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public List<object> MyInts { get; set; }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public IList MyNullableFloats { get; set; }
- }
-
- [Fact]
- public static void NonNumber_AsBoxed_Property()
- {
- DateTime dateTime = DateTimeTestHelpers.FixedDateTimeValue;
- Guid? nullableGuid = Guid.NewGuid();
-
- string expected = @$"{{""MyDateTime"":{JsonSerializer.Serialize(dateTime)},""MyNullableGuid"":{JsonSerializer.Serialize(nullableGuid)}}}";
-
- var obj = new Class_With_BoxedNonNumbers
- {
- MyDateTime = dateTime,
- MyNullableGuid = nullableGuid
- };
-
- string serialized = JsonSerializer.Serialize(obj);
- JsonTestHelper.AssertJsonEqual(expected, serialized);
-
- obj = JsonSerializer.Deserialize<Class_With_BoxedNonNumbers>(serialized);
-
- JsonElement el = Assert.IsType<JsonElement>(obj.MyDateTime);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal(dateTime, el.GetDateTime());
-
- el = Assert.IsType<JsonElement>(obj.MyNullableGuid);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal(nullableGuid.Value, el.GetGuid());
- }
-
- public class Class_With_BoxedNonNumbers
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public object MyDateTime { get; set; }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public object MyNullableGuid { get; set; }
- }
-
- [Fact]
- public static void NonNumber_AsBoxed_CollectionRootType_Element()
- {
- DateTime dateTime = DateTimeTestHelpers.FixedDateTimeValue;
- Guid? nullableGuid = Guid.NewGuid();
-
- string expected = @$"[{JsonSerializer.Serialize(dateTime)}]";
-
- var obj = new List<object> { dateTime };
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
- Assert.Equal(expected, serialized);
-
- obj = JsonSerializer.Deserialize<List<object>>(serialized, s_optionReadAndWriteFromStr);
-
- JsonElement el = Assert.IsType<JsonElement>(obj[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal(dateTime, el.GetDateTime());
-
- expected = @$"[{JsonSerializer.Serialize(nullableGuid)}]";
-
- IList obj2 = new object[] { nullableGuid };
- serialized = JsonSerializer.Serialize(obj2, s_optionReadAndWriteFromStr);
- Assert.Equal(expected, serialized);
-
- obj2 = JsonSerializer.Deserialize<IList>(serialized, s_optionReadAndWriteFromStr);
-
- el = Assert.IsType<JsonElement>(obj2[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal(nullableGuid.Value, el.GetGuid());
- }
-
- [Fact]
- public static void NonNumber_AsBoxed_CollectionProperty_Element()
- {
- DateTime dateTime = DateTimeTestHelpers.FixedDateTimeValue;
- Guid? nullableGuid = Guid.NewGuid();
-
- string expected = @$"{{""MyDateTimes"":[{JsonSerializer.Serialize(dateTime)}],""MyNullableGuids"":[{JsonSerializer.Serialize(nullableGuid)}]}}";
-
- var obj = new Class_With_ListsOfBoxedNonNumbers
- {
- MyDateTimes = new List<object> { dateTime },
- MyNullableGuids = new object[] { nullableGuid }
- };
-
- string serialized = JsonSerializer.Serialize(obj);
- JsonTestHelper.AssertJsonEqual(expected, serialized);
-
- obj = JsonSerializer.Deserialize<Class_With_ListsOfBoxedNonNumbers>(serialized);
-
- JsonElement el = Assert.IsType<JsonElement>(obj.MyDateTimes[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal(dateTime, el.GetDateTime());
-
- el = Assert.IsType<JsonElement>(obj.MyNullableGuids[0]);
- Assert.Equal(JsonValueKind.String, el.ValueKind);
- Assert.Equal(nullableGuid, el.GetGuid());
- }
-
- public class Class_With_ListsOfBoxedNonNumbers
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public List<object> MyDateTimes { get; set; }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public IList MyNullableGuids { get; set; }
- }
-
- [Fact]
- public static void Number_AsCollectionElement_RoundTrip()
- {
- RunAsCollectionElementTest(JsonNumberTestData.Bytes);
- RunAsCollectionElementTest(JsonNumberTestData.SBytes);
- RunAsCollectionElementTest(JsonNumberTestData.Shorts);
- RunAsCollectionElementTest(JsonNumberTestData.Ints);
- RunAsCollectionElementTest(JsonNumberTestData.Longs);
- RunAsCollectionElementTest(JsonNumberTestData.UShorts);
- RunAsCollectionElementTest(JsonNumberTestData.UInts);
- RunAsCollectionElementTest(JsonNumberTestData.ULongs);
- RunAsCollectionElementTest(JsonNumberTestData.Floats);
- RunAsCollectionElementTest(JsonNumberTestData.Doubles);
- RunAsCollectionElementTest(JsonNumberTestData.Decimals);
-
- // https://github.com/dotnet/runtime/issues/66220
- if (!PlatformDetection.IsAppleMobile)
- {
- RunAsCollectionElementTest(JsonNumberTestData.NullableBytes);
- RunAsCollectionElementTest(JsonNumberTestData.NullableSBytes);
- RunAsCollectionElementTest(JsonNumberTestData.NullableShorts);
- RunAsCollectionElementTest(JsonNumberTestData.NullableInts);
- RunAsCollectionElementTest(JsonNumberTestData.NullableLongs);
- RunAsCollectionElementTest(JsonNumberTestData.NullableUShorts);
- RunAsCollectionElementTest(JsonNumberTestData.NullableUInts);
- RunAsCollectionElementTest(JsonNumberTestData.NullableULongs);
- RunAsCollectionElementTest(JsonNumberTestData.NullableFloats);
- RunAsCollectionElementTest(JsonNumberTestData.NullableDoubles);
- RunAsCollectionElementTest(JsonNumberTestData.NullableDecimals);
- }
- }
-
- private static void RunAsCollectionElementTest<T>(List<T> numbers)
- {
- StringBuilder jsonBuilder_NumbersAsNumbers = new StringBuilder();
- StringBuilder jsonBuilder_NumbersAsStrings = new StringBuilder();
- StringBuilder jsonBuilder_NumbersAsNumbersAndStrings = new StringBuilder();
- StringBuilder jsonBuilder_NumbersAsNumbersAndStrings_Alternate = new StringBuilder();
- bool asNumber = false;
-
- jsonBuilder_NumbersAsNumbers.Append("[");
- jsonBuilder_NumbersAsStrings.Append("[");
- jsonBuilder_NumbersAsNumbersAndStrings.Append("[");
- jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Append("[");
-
- foreach (T number in numbers)
- {
- string numberAsString = GetNumberAsString(number);
-
- string jsonWithNumberAsString = @$"""{numberAsString}""";
-
- jsonBuilder_NumbersAsNumbers.Append($"{numberAsString},");
- jsonBuilder_NumbersAsStrings.Append($"{jsonWithNumberAsString},");
- jsonBuilder_NumbersAsNumbersAndStrings.Append(asNumber
- ? $"{numberAsString},"
- : $"{jsonWithNumberAsString},");
- jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Append(!asNumber
- ? $"{numberAsString},"
- : $"{jsonWithNumberAsString},");
-
- asNumber = !asNumber;
- }
-
- jsonBuilder_NumbersAsNumbers.Remove(jsonBuilder_NumbersAsNumbers.Length - 1, 1);
- jsonBuilder_NumbersAsStrings.Remove(jsonBuilder_NumbersAsStrings.Length - 1, 1);
- jsonBuilder_NumbersAsNumbersAndStrings.Remove(jsonBuilder_NumbersAsNumbersAndStrings.Length - 1, 1);
- jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Remove(jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Length - 1, 1);
-
- jsonBuilder_NumbersAsNumbers.Append("]");
- jsonBuilder_NumbersAsStrings.Append("]");
- jsonBuilder_NumbersAsNumbersAndStrings.Append("]");
- jsonBuilder_NumbersAsNumbersAndStrings_Alternate.Append("]");
-
- string jsonNumbersAsStrings = jsonBuilder_NumbersAsStrings.ToString();
-
- PerformAsCollectionElementSerialization(
- numbers,
- jsonBuilder_NumbersAsNumbers.ToString(),
- jsonNumbersAsStrings,
- jsonBuilder_NumbersAsNumbersAndStrings.ToString(),
- jsonBuilder_NumbersAsNumbersAndStrings_Alternate.ToString());
-
- // Reflection based tests for every collection type.
- RunAllCollectionsRoundTripTest<T>(jsonNumbersAsStrings);
- }
-
- private static void PerformAsCollectionElementSerialization<T>(
- List<T> numbers,
- string json_NumbersAsNumbers,
- string json_NumbersAsStrings,
- string json_NumbersAsNumbersAndStrings,
- string json_NumbersAsNumbersAndStrings_Alternate)
- {
- List<T> deserialized;
-
- // Option: read from string
-
- // Deserialize
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbers, s_optionReadFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsStrings, s_optionReadFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbersAndStrings, s_optionReadFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbersAndStrings_Alternate, s_optionReadFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- // Serialize
- Assert.Equal(json_NumbersAsNumbers, JsonSerializer.Serialize(numbers, s_optionReadFromStr));
-
- // Option: write as string
-
- // Deserialize
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbers, s_optionWriteAsStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<T>>(json_NumbersAsStrings, s_optionWriteAsStr));
-
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbersAndStrings, s_optionWriteAsStr));
-
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbersAndStrings_Alternate, s_optionWriteAsStr));
-
- // Serialize
- Assert.Equal(json_NumbersAsStrings, JsonSerializer.Serialize(numbers, s_optionWriteAsStr));
-
- // Option: read and write from/to string
-
- // Deserialize
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbers, s_optionReadAndWriteFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsStrings, s_optionReadAndWriteFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbersAndStrings, s_optionReadAndWriteFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- deserialized = JsonSerializer.Deserialize<List<T>>(json_NumbersAsNumbersAndStrings_Alternate, s_optionReadAndWriteFromStr);
- AssertIEnumerableEqual(numbers, deserialized);
-
- // Serialize
- Assert.Equal(json_NumbersAsStrings, JsonSerializer.Serialize(numbers, s_optionReadAndWriteFromStr));
- }
-
- private static void AssertIEnumerableEqual<T>(IEnumerable<T> list1, IEnumerable<T> list2)
- {
- IEnumerator<T> enumerator1 = list1.GetEnumerator();
- IEnumerator<T> enumerator2 = list2.GetEnumerator();
-
- while (enumerator1.MoveNext())
- {
- enumerator2.MoveNext();
- Assert.Equal(enumerator1.Current, enumerator2.Current);
- }
-
- Assert.False(enumerator2.MoveNext());
- }
-
- private static void RunAllCollectionsRoundTripTest<T>(string json)
- {
- foreach (Type type in CollectionTestTypes.DeserializableGenericEnumerableTypes<T>())
- {
- if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
- {
- HashSet<T> obj1 = (HashSet<T>)JsonSerializer.Deserialize(json, type, s_optionReadAndWriteFromStr);
- string serialized = JsonSerializer.Serialize(obj1, s_optionReadAndWriteFromStr);
-
- HashSet<T> obj2 = (HashSet<T>)JsonSerializer.Deserialize(serialized, type, s_optionReadAndWriteFromStr);
-
- Assert.Equal(obj1.Count, obj2.Count);
- foreach (T element in obj1)
- {
- Assert.True(obj2.Contains(element));
- }
- }
- else if (type != typeof(byte[]))
- {
- object obj = JsonSerializer.Deserialize(json, type, s_optionReadAndWriteFromStr);
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
- Assert.Equal(json, serialized);
- }
- }
-
- foreach (Type type in CollectionTestTypes.DeserializableNonGenericEnumerableTypes())
- {
- // Deserialized as collection of JsonElements.
- object obj = JsonSerializer.Deserialize(json, type, s_optionReadAndWriteFromStr);
- // Serialized as strings with escaping.
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
-
- // Ensure escaped values were serialized accurately
- List<T> list = JsonSerializer.Deserialize<List<T>>(serialized, s_optionReadAndWriteFromStr);
- serialized = JsonSerializer.Serialize(list, s_optionReadAndWriteFromStr);
- Assert.Equal(json, serialized);
-
- // Serialize instance which is a collection of numbers (not JsonElements).
- obj = Activator.CreateInstance(type, new[] { list });
- serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
- Assert.Equal(json, serialized);
- }
- }
-
- [Fact]
- public static void Number_AsDictionaryElement_RoundTrip()
- {
- var dict = new Dictionary<int, float>();
- for (int i = 0; i < 10; i++)
- {
- dict[JsonNumberTestData.Ints[i]] = JsonNumberTestData.Floats[i];
- }
-
- // Serialize
- string serialized = JsonSerializer.Serialize(dict, s_optionReadAndWriteFromStr);
- AssertDictionaryElements_StringValues(serialized);
-
- // Deserialize
- dict = JsonSerializer.Deserialize<Dictionary<int, float>>(serialized, s_optionReadAndWriteFromStr);
-
- // Test roundtrip
- JsonTestHelper.AssertJsonEqual(serialized, JsonSerializer.Serialize(dict, s_optionReadAndWriteFromStr));
- }
-
- private static void AssertDictionaryElements_StringValues(string serialized)
- {
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(serialized));
- reader.Read();
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndObject)
- {
- break;
- }
- else if (reader.TokenType == JsonTokenType.String)
- {
- Assert.True(reader.ValueSpan.IndexOf((byte)'\\') == -1);
- }
- else
- {
- Assert.Equal(JsonTokenType.PropertyName, reader.TokenType);
- }
- }
- }
-
- [Fact]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/39674", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))]
- [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", ~RuntimeConfiguration.Release)]
- public static void DictionariesRoundTrip()
- {
- RunAllDictionariessRoundTripTest(JsonNumberTestData.ULongs);
- RunAllDictionariessRoundTripTest(JsonNumberTestData.Floats);
- RunAllDictionariessRoundTripTest(JsonNumberTestData.Doubles);
- }
-
- private static void RunAllDictionariessRoundTripTest<T>(List<T> numbers)
- {
- StringBuilder jsonBuilder_NumbersAsStrings = new StringBuilder();
-
- jsonBuilder_NumbersAsStrings.Append("{");
-
- foreach (T number in numbers)
- {
- string numberAsString = GetNumberAsString(number);
- string jsonWithNumberAsString = @$"""{numberAsString}""";
-
- jsonBuilder_NumbersAsStrings.Append($"{jsonWithNumberAsString}:");
- jsonBuilder_NumbersAsStrings.Append($"{jsonWithNumberAsString},");
- }
-
- jsonBuilder_NumbersAsStrings.Remove(jsonBuilder_NumbersAsStrings.Length - 1, 1);
- jsonBuilder_NumbersAsStrings.Append("}");
-
- string jsonNumbersAsStrings = jsonBuilder_NumbersAsStrings.ToString();
-
- foreach (Type type in CollectionTestTypes.DeserializableDictionaryTypes<string, T>())
- {
- object obj = JsonSerializer.Deserialize(jsonNumbersAsStrings, type, s_optionReadAndWriteFromStr);
- JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr));
- }
-
- foreach (Type type in CollectionTestTypes.DeserializableNonGenericDictionaryTypes())
- {
- Dictionary<T, T> dict = JsonSerializer.Deserialize<Dictionary<T, T>>(jsonNumbersAsStrings, s_optionReadAndWriteFromStr);
-
- // Serialize instance which is a dictionary of numbers (not JsonElements).
- object obj = Activator.CreateInstance(type, new[] { dict });
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
- JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, serialized);
- }
- }
-
- [Fact]
- public static void Number_AsPropertyValue_RoundTrip()
- {
- var obj = new Class_With_NullableUInt64_And_Float()
- {
- NullableUInt64Number = JsonNumberTestData.NullableULongs.LastOrDefault(),
- FloatNumbers = JsonNumberTestData.Floats
- };
-
- // Serialize
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
-
- // Deserialize
- obj = JsonSerializer.Deserialize<Class_With_NullableUInt64_And_Float>(serialized, s_optionReadAndWriteFromStr);
-
- // Test roundtrip
- JsonTestHelper.AssertJsonEqual(serialized, JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr));
- }
-
- private class Class_With_NullableUInt64_And_Float
- {
- public ulong? NullableUInt64Number { get; set; }
- [JsonInclude]
- public List<float> FloatNumbers;
- }
-
- [Fact]
- public static void Number_AsKeyValuePairValue_RoundTrip()
- {
- var obj = new KeyValuePair<ulong?, List<float>>(JsonNumberTestData.NullableULongs.LastOrDefault(), JsonNumberTestData.Floats);
-
- // Serialize
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
-
- // Deserialize
- obj = JsonSerializer.Deserialize<KeyValuePair<ulong?, List<float>>>(serialized, s_optionReadAndWriteFromStr);
-
- // Test roundtrip
- JsonTestHelper.AssertJsonEqual(serialized, JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr));
- }
-
- [Fact]
- public static void Number_AsObjectWithParameterizedCtor_RoundTrip()
- {
- var obj = new MyClassWithNumbers(JsonNumberTestData.NullableULongs.LastOrDefault(), JsonNumberTestData.Floats);
-
- // Serialize
- string serialized = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
-
- // Deserialize
- obj = JsonSerializer.Deserialize<MyClassWithNumbers>(serialized, s_optionReadAndWriteFromStr);
-
- // Test roundtrip
- JsonTestHelper.AssertJsonEqual(serialized, JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr));
- }
-
- private class MyClassWithNumbers
- {
- public ulong? Ulong { get; }
- public List<float> ListOfFloats { get; }
-
- public MyClassWithNumbers(ulong? @ulong, List<float> listOfFloats)
- {
- Ulong = @ulong;
- ListOfFloats = listOfFloats;
- }
- }
-
- [Fact]
- public static void Number_AsObjectWithParameterizedCtor_PropHasAttribute()
- {
- string json = @"{""ListOfFloats"":[""1""]}";
- // Strict handling on property overrides loose global policy.
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<MyClassWithNumbers_PropsHasAttribute>(json, s_optionReadFromStr));
-
- // Serialize
- json = @"{""ListOfFloats"":[1]}";
- MyClassWithNumbers_PropsHasAttribute obj = JsonSerializer.Deserialize<MyClassWithNumbers_PropsHasAttribute>(json);
-
- // Number serialized as JSON number due to strict handling on property which overrides loose global policy.
- Assert.Equal(json, JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr));
- }
-
- private class MyClassWithNumbers_PropsHasAttribute
- {
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- public List<float> ListOfFloats { get; }
-
- public MyClassWithNumbers_PropsHasAttribute(List<float> listOfFloats)
- {
- ListOfFloats = listOfFloats;
- }
- }
-
- [Fact]
- public static void FloatingPointConstants_Pass()
- {
- // Valid values
- PerformFloatingPointSerialization("NaN");
- PerformFloatingPointSerialization("Infinity");
- PerformFloatingPointSerialization("-Infinity");
-
- PerformFloatingPointSerialization("\u004EaN"); // NaN
- PerformFloatingPointSerialization("Inf\u0069ni\u0074y"); // Infinity
- PerformFloatingPointSerialization("\u002DInf\u0069nity"); // -Infinity
-
- static void PerformFloatingPointSerialization(string testString)
- {
- string testStringAsJson = $@"""{testString}""";
- string testJson = @$"{{""FloatNumber"":{testStringAsJson},""DoubleNumber"":{testStringAsJson}}}";
-
- StructWithNumbers obj;
- switch (testString)
- {
- case "NaN":
- obj = JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionsAllowFloatConstants);
- Assert.Equal(float.NaN, obj.FloatNumber);
- Assert.Equal(double.NaN, obj.DoubleNumber);
-
- obj = JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionReadFromStr);
- Assert.Equal(float.NaN, obj.FloatNumber);
- Assert.Equal(double.NaN, obj.DoubleNumber);
- break;
- case "Infinity":
- obj = JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionsAllowFloatConstants);
- Assert.Equal(float.PositiveInfinity, obj.FloatNumber);
- Assert.Equal(double.PositiveInfinity, obj.DoubleNumber);
-
- obj = JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionReadFromStr);
- Assert.Equal(float.PositiveInfinity, obj.FloatNumber);
- Assert.Equal(double.PositiveInfinity, obj.DoubleNumber);
- break;
- case "-Infinity":
- obj = JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionsAllowFloatConstants);
- Assert.Equal(float.NegativeInfinity, obj.FloatNumber);
- Assert.Equal(double.NegativeInfinity, obj.DoubleNumber);
-
- obj = JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionReadFromStr);
- Assert.Equal(float.NegativeInfinity, obj.FloatNumber);
- Assert.Equal(double.NegativeInfinity, obj.DoubleNumber);
- break;
- default:
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionsAllowFloatConstants));
- return;
- }
-
- JsonTestHelper.AssertJsonEqual(testJson, JsonSerializer.Serialize(obj, s_optionsAllowFloatConstants));
- JsonTestHelper.AssertJsonEqual(testJson, JsonSerializer.Serialize(obj, s_optionWriteAsStr));
- }
- }
-
- [Theory]
- [InlineData("naN")]
- [InlineData("Nan")]
- [InlineData("NAN")]
- [InlineData("+Infinity")]
- [InlineData("+infinity")]
- [InlineData("infinity")]
- [InlineData("infinitY")]
- [InlineData("INFINITY")]
- [InlineData("+INFINITY")]
- [InlineData("-infinity")]
- [InlineData("-infinitY")]
- [InlineData("-INFINITY")]
- [InlineData(" NaN")]
- [InlineData("NaN ")]
- [InlineData(" Infinity")]
- [InlineData(" -Infinity")]
- [InlineData("Infinity ")]
- [InlineData("-Infinity ")]
- [InlineData("a-Infinity")]
- [InlineData("NaNa")]
- [InlineData("Infinitya")]
- [InlineData("-Infinitya")]
-#pragma warning disable xUnit1025 // Theory method 'FloatingPointConstants_Fail' on test class 'NumberHandlingTests' has InlineData duplicate(s)
- [InlineData("\u006EaN")] // "naN"
- [InlineData("\u0020Inf\u0069ni\u0074y")] // " Infinity"
- [InlineData("\u002BInf\u0069nity")] // "+Infinity"
-#pragma warning restore xUnit1025
- public static void FloatingPointConstants_Fail(string testString)
- {
- string testStringAsJson = $@"""{testString}""";
-
- string testJson = @$"{{""FloatNumber"":{testStringAsJson}}}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionReadFromStr));
-
- testJson = @$"{{""DoubleNumber"":{testStringAsJson}}}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<StructWithNumbers>(testJson, s_optionReadFromStr));
- }
-
- [Fact]
- public static void AllowFloatingPointConstants_WriteAsNumber_IfNotConstant()
- {
- float @float = 1;
- // Not written as "1"
- Assert.Equal("1", JsonSerializer.Serialize(@float, s_optionsAllowFloatConstants));
-
- double @double = 1;
- // Not written as "1"
- Assert.Equal("1", JsonSerializer.Serialize(@double, s_optionsAllowFloatConstants));
- }
-
- [Theory]
- [InlineData("NaN")]
- [InlineData("Infinity")]
- [InlineData("-Infinity")]
- public static void Unquoted_FloatingPointConstants_Read_Fail(string testString)
- {
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<float>(testString, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<double?>(testString, s_optionReadFromStr));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<double>(testString, s_optionReadFromStrAllowFloatConstants));
- }
-
- private struct StructWithNumbers
- {
- public float FloatNumber { get; set; }
- public double DoubleNumber { get; set; }
- }
-
- [Fact]
- public static void ReadFromString_AllowFloatingPoint()
- {
- string json = @"{""IntNumber"":""1"",""FloatNumber"":""NaN""}";
- ClassWithNumbers obj = JsonSerializer.Deserialize<ClassWithNumbers>(json, s_optionReadFromStrAllowFloatConstants);
-
- Assert.Equal(1, obj.IntNumber);
- Assert.Equal(float.NaN, obj.FloatNumber);
-
- JsonTestHelper.AssertJsonEqual(@"{""IntNumber"":1,""FloatNumber"":""NaN""}", JsonSerializer.Serialize(obj, s_optionReadFromStrAllowFloatConstants));
- }
-
- [Fact]
- public static void WriteAsString_AllowFloatingPoint()
- {
- string json = @"{""IntNumber"":""1"",""FloatNumber"":""NaN""}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWithNumbers>(json, s_optionWriteAsStrAllowFloatConstants));
-
- var obj = new ClassWithNumbers
- {
- IntNumber = 1,
- FloatNumber = float.NaN
- };
-
- JsonTestHelper.AssertJsonEqual(json, JsonSerializer.Serialize(obj, s_optionWriteAsStrAllowFloatConstants));
- }
-
- public class ClassWithNumbers
- {
- public int IntNumber { get; set; }
- public float FloatNumber { get; set; }
- }
-
- [Fact]
- public static void FloatingPointConstants_IncompatibleNumber()
- {
- AssertFloatingPointIncompatible_Fails<byte>();
- AssertFloatingPointIncompatible_Fails<sbyte>();
- AssertFloatingPointIncompatible_Fails<short>();
- AssertFloatingPointIncompatible_Fails<int>();
- AssertFloatingPointIncompatible_Fails<long>();
- AssertFloatingPointIncompatible_Fails<ushort>();
- AssertFloatingPointIncompatible_Fails<uint>();
- AssertFloatingPointIncompatible_Fails<ulong>();
- AssertFloatingPointIncompatible_Fails<decimal>();
- AssertFloatingPointIncompatible_Fails<byte?>();
- AssertFloatingPointIncompatible_Fails<sbyte?>();
- AssertFloatingPointIncompatible_Fails<short?>();
- AssertFloatingPointIncompatible_Fails<int?>();
- AssertFloatingPointIncompatible_Fails<long?>();
- AssertFloatingPointIncompatible_Fails<ushort?>();
- AssertFloatingPointIncompatible_Fails<uint?>();
- AssertFloatingPointIncompatible_Fails<ulong?>();
- AssertFloatingPointIncompatible_Fails<decimal?>();
- }
-
- private static void AssertFloatingPointIncompatible_Fails<T>()
- {
- string[] testCases = new[]
- {
- @"""NaN""",
- @"""Infinity""",
- @"""-Infinity""",
- };
-
- foreach (string test in testCases)
- {
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<T>(test, s_optionReadFromStrAllowFloatConstants));
- }
- }
-
- [Fact]
- public static void UnsupportedFormats()
- {
- AssertUnsupportedFormatThrows<byte>();
- AssertUnsupportedFormatThrows<sbyte>();
- AssertUnsupportedFormatThrows<short>();
- AssertUnsupportedFormatThrows<int>();
- AssertUnsupportedFormatThrows<long>();
- AssertUnsupportedFormatThrows<ushort>();
- AssertUnsupportedFormatThrows<uint>();
- AssertUnsupportedFormatThrows<ulong>();
- AssertUnsupportedFormatThrows<float>();
- AssertUnsupportedFormatThrows<decimal>();
- AssertUnsupportedFormatThrows<byte?>();
- AssertUnsupportedFormatThrows<sbyte?>();
- AssertUnsupportedFormatThrows<short?>();
- AssertUnsupportedFormatThrows<int?>();
- AssertUnsupportedFormatThrows<long?>();
- AssertUnsupportedFormatThrows<ushort?>();
- AssertUnsupportedFormatThrows<uint?>();
- AssertUnsupportedFormatThrows<ulong?>();
- AssertUnsupportedFormatThrows<float?>();
- AssertUnsupportedFormatThrows<decimal?>();
- }
-
- private static void AssertUnsupportedFormatThrows<T>()
- {
- string[] testCases = new[]
- {
- "$123.46", // Currency
- "100.00 %", // Percent
- "1234,57", // Fixed point
- "00FF", // Hexadecimal
- };
-
- foreach (string test in testCases)
- {
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<T>(test, s_optionReadFromStr));
- }
- }
-
- [Fact]
- public static void EscapingTest()
- {
- // Cause all characters to be escaped.
- var encoderSettings = new TextEncoderSettings();
- encoderSettings.ForbidCharacters('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '-', 'e', 'E');
-
- JavaScriptEncoder encoder = JavaScriptEncoder.Create(encoderSettings);
- var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
- {
- Encoder = encoder
- };
-
- PerformEscapingTest(JsonNumberTestData.Bytes, options);
- PerformEscapingTest(JsonNumberTestData.SBytes, options);
- PerformEscapingTest(JsonNumberTestData.Shorts, options);
- PerformEscapingTest(JsonNumberTestData.Ints, options);
- PerformEscapingTest(JsonNumberTestData.Longs, options);
- PerformEscapingTest(JsonNumberTestData.UShorts, options);
- PerformEscapingTest(JsonNumberTestData.UInts, options);
- PerformEscapingTest(JsonNumberTestData.ULongs, options);
- PerformEscapingTest(JsonNumberTestData.Floats, options);
- PerformEscapingTest(JsonNumberTestData.Doubles, options);
- PerformEscapingTest(JsonNumberTestData.Decimals, options);
- }
-
- private static void PerformEscapingTest<T>(List<T> numbers, JsonSerializerOptions options)
- {
- // All input characters are escaped
- IEnumerable<string> numbersAsStrings = numbers.Select(num => GetNumberAsString(num));
- string input = JsonSerializer.Serialize(numbersAsStrings, options);
- AssertListNumbersEscaped(input);
-
- // Unescaping works
- List<T> deserialized = JsonSerializer.Deserialize<List<T>>(input, options);
- Assert.Equal(numbers.Count, deserialized.Count);
- for (int i = 0; i < numbers.Count; i++)
- {
- Assert.Equal(numbers[i], deserialized[i]);
- }
-
- // Every number is written as a string, and custom escaping is not honored.
- string serialized = JsonSerializer.Serialize(deserialized, options);
- AssertListNumbersUnescaped(serialized);
- }
-
- private static void AssertListNumbersEscaped(string json)
- {
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
- reader.Read();
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndArray)
- {
- break;
- }
- else
- {
- Assert.Equal(JsonTokenType.String, reader.TokenType);
- Assert.True(reader.ValueSpan.IndexOf((byte)'\\') != -1);
- }
- }
- }
-
- private static void AssertListNumbersUnescaped(string json)
- {
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
- reader.Read();
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndArray)
- {
- break;
- }
- else
- {
- Assert.Equal(JsonTokenType.String, reader.TokenType);
- Assert.True(reader.ValueSpan.IndexOf((byte)'\\') == -1);
- }
- }
- }
-
- [Fact]
- public static void Number_RoundtripNull()
- {
- Perform_Number_RoundTripNull_Test<byte>();
- Perform_Number_RoundTripNull_Test<sbyte>();
- Perform_Number_RoundTripNull_Test<short>();
- Perform_Number_RoundTripNull_Test<int>();
- Perform_Number_RoundTripNull_Test<long>();
- Perform_Number_RoundTripNull_Test<ushort>();
- Perform_Number_RoundTripNull_Test<uint>();
- Perform_Number_RoundTripNull_Test<ulong>();
- Perform_Number_RoundTripNull_Test<float>();
- Perform_Number_RoundTripNull_Test<decimal>();
- }
-
- private static void Perform_Number_RoundTripNull_Test<T>()
- {
- string nullAsJson = "null";
- string nullAsQuotedJson = $@"""{nullAsJson}""";
-
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<T>(nullAsJson, s_optionReadAndWriteFromStr));
- Assert.Equal("0", JsonSerializer.Serialize(default(T)));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<T>(nullAsQuotedJson, s_optionReadAndWriteFromStr));
- }
-
- [Fact]
- public static void NullableNumber_RoundtripNull()
- {
- Perform_NullableNumber_RoundTripNull_Test<byte?>();
- Perform_NullableNumber_RoundTripNull_Test<sbyte?>();
- Perform_NullableNumber_RoundTripNull_Test<short?>();
- Perform_NullableNumber_RoundTripNull_Test<int?>();
- Perform_NullableNumber_RoundTripNull_Test<long?>();
- Perform_NullableNumber_RoundTripNull_Test<ushort?>();
- Perform_NullableNumber_RoundTripNull_Test<uint?>();
- Perform_NullableNumber_RoundTripNull_Test<ulong?>();
- Perform_NullableNumber_RoundTripNull_Test<float?>();
- Perform_NullableNumber_RoundTripNull_Test<decimal?>();
- }
-
- private static void Perform_NullableNumber_RoundTripNull_Test<T>()
- {
- string nullAsJson = "null";
- string nullAsQuotedJson = $@"""{nullAsJson}""";
-
- Assert.Null(JsonSerializer.Deserialize<T>(nullAsJson, s_optionReadAndWriteFromStr));
- Assert.Equal(nullAsJson, JsonSerializer.Serialize(default(T)));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<T>(nullAsQuotedJson, s_optionReadAndWriteFromStr));
- }
-
- [Fact]
- public static void Disallow_ArbritaryStrings_On_AllowFloatingPointConstants()
- {
- string json = @"""12345""";
-
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<byte>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<sbyte>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<short>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<int>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<long>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ushort>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<uint>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ulong>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<float>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<double>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<decimal>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<byte?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<sbyte?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<short?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<int?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<long?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ushort?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<uint?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ulong?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<float?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<double?>(json, s_optionsAllowFloatConstants));
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<decimal?>(json, s_optionsAllowFloatConstants));
- }
-
- [Fact]
- public static void Attributes_OnMembers_Work()
- {
- // Bad JSON because Int should not be string.
- string intIsString = @"{""Float"":""1234.5"",""Int"":""12345""}";
-
- // Good JSON because Float can be string.
- string floatIsString = @"{""Float"":""1234.5"",""Int"":12345}";
-
- // Good JSON because Float can be number.
- string floatIsNumber = @"{""Float"":1234.5,""Int"":12345}";
-
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWith_Attribute_OnNumber>(intIsString));
-
- ClassWith_Attribute_OnNumber obj = JsonSerializer.Deserialize<ClassWith_Attribute_OnNumber>(floatIsString);
- Assert.Equal(1234.5, obj.Float);
- Assert.Equal(12345, obj.Int);
-
- obj = JsonSerializer.Deserialize<ClassWith_Attribute_OnNumber>(floatIsNumber);
- Assert.Equal(1234.5, obj.Float);
- Assert.Equal(12345, obj.Int);
-
- // Per options, float should be written as string.
- JsonTestHelper.AssertJsonEqual(floatIsString, JsonSerializer.Serialize(obj));
- }
-
- private class ClassWith_Attribute_OnNumber
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public float Float { get; set; }
-
- public int Int { get; set; }
- }
-
- [Fact]
- public static void Attribute_OnRootType_Works()
- {
- // Not allowed
- string floatIsString = @"{""Float"":""1234"",""Int"":123}";
-
- // Allowed
- string floatIsNan = @"{""Float"":""NaN"",""Int"":123}";
-
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Type_AllowFloatConstants>(floatIsString));
-
- Type_AllowFloatConstants obj = JsonSerializer.Deserialize<Type_AllowFloatConstants>(floatIsNan);
- Assert.Equal(float.NaN, obj.Float);
- Assert.Equal(123, obj.Int);
-
- JsonTestHelper.AssertJsonEqual(floatIsNan, JsonSerializer.Serialize(obj));
- }
-
- [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)]
- private class Type_AllowFloatConstants
- {
- public float Float { get; set; }
-
- public int Int { get; set; }
- }
-
- [Fact]
- public static void AttributeOnType_WinsOver_GlobalOption()
- {
- // Global options strict, type options loose
- string json = @"{""Float"":""12345""}";
- var obj1 = JsonSerializer.Deserialize<ClassWith_LooseAttribute>(json);
-
- Assert.Equal(@"{""Float"":""12345""}", JsonSerializer.Serialize(obj1));
-
- // Global options loose, type options strict
- json = @"{""Float"":""12345""}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWith_StrictAttribute>(json, s_optionReadAndWriteFromStr));
-
- var obj2 = new ClassWith_StrictAttribute() { Float = 12345 };
- Assert.Equal(@"{""Float"":12345}", JsonSerializer.Serialize(obj2, s_optionReadAndWriteFromStr));
- }
-
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- public class ClassWith_StrictAttribute
- {
- public float Float { get; set; }
- }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- private class ClassWith_LooseAttribute
- {
- public float Float { get; set; }
- }
-
- [Fact]
- public static void AttributeOnMember_WinsOver_AttributeOnType()
- {
- string json = @"{""Double"":""NaN""}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWith_Attribute_On_TypeAndMember>(json));
-
- var obj = new ClassWith_Attribute_On_TypeAndMember { Double = float.NaN };
- Assert.Throws<ArgumentException>(() => JsonSerializer.Serialize(obj));
- }
-
- [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)]
- private class ClassWith_Attribute_On_TypeAndMember
- {
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- public double Double { get; set; }
- }
-
- [Fact]
- public static void Attribute_OnNestedType_Works()
- {
- string jsonWithShortProperty = @"{""Short"":""1""}";
- ClassWith_ReadAsStringAttribute obj = JsonSerializer.Deserialize<ClassWith_ReadAsStringAttribute>(jsonWithShortProperty);
- Assert.Equal(1, obj.Short);
-
- string jsonWithMyObjectProperty = @"{""MyObject"":{""Float"":""1""}}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWith_ReadAsStringAttribute>(jsonWithMyObjectProperty));
- }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
- public class ClassWith_ReadAsStringAttribute
- {
- public short Short { get; set; }
-
- public ClassWith_StrictAttribute MyObject { get; set; }
- }
-
- [Fact]
- public static void MemberAttributeAppliesToCollection_SimpleElements()
- {
- RunTest<int[]>();
- RunTest<ConcurrentQueue<int>>();
- RunTest<GenericICollectionWrapper<int>>();
- RunTest<IEnumerable<int>>();
- RunTest<Collection<int>>();
- RunTest<ImmutableList<int>>();
- RunTest<HashSet<int>>();
- RunTest<List<int>>();
- RunTest<IList<int>>();
- RunTest<IList>();
- RunTest<Queue<int>>();
-
- static void RunTest<T>()
- {
- string json = @"{""MyList"":[""1"",""2""]}";
- ClassWithSimpleCollectionProperty<T> obj = global::System.Text.Json.JsonSerializer.Deserialize<ClassWithSimpleCollectionProperty<T>>(json);
- Assert.Equal(json, global::System.Text.Json.JsonSerializer.Serialize(obj));
- }
- }
-
- public class ClassWithSimpleCollectionProperty<T>
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public T MyList { get; set; }
- }
-
- [Fact]
- public static void NestedCollectionElementTypeHandling_Overrides_GlobalOption()
- {
- // Strict policy on the collection element type overrides read-as-string on the collection property
- string json = @"{""MyList"":[{""Float"":""1""}]}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWithComplexListProperty>(json, s_optionReadAndWriteFromStr));
-
- // Strict policy on the collection element type overrides write-as-string on the collection property
- var obj = new ClassWithComplexListProperty
- {
- MyList = new List<ClassWith_StrictAttribute> { new ClassWith_StrictAttribute { Float = 1 } }
- };
- Assert.Equal(@"{""MyList"":[{""Float"":1}]}", JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr));
- }
-
- public class ClassWithComplexListProperty
- {
- public List<ClassWith_StrictAttribute> MyList { get; set; }
- }
-
- [Fact]
- public static void NumberHandlingAttribute_NotAllowedOn_CollectionOfNonNumbers()
- {
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWith_AttributeOnComplexListProperty>(""));
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new ClassWith_AttributeOnComplexListProperty()));
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWith_AttributeOnComplexDictionaryProperty>(""));
- Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new ClassWith_AttributeOnComplexDictionaryProperty()));
- }
-
- public class ClassWith_AttributeOnComplexListProperty
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public List<ClassWith_StrictAttribute> MyList { get; set; }
- }
-
- public class ClassWith_AttributeOnComplexDictionaryProperty
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
- public Dictionary<string, ClassWith_StrictAttribute> MyDictionary { get; set; }
- }
-
- [Fact]
- public static void MemberAttributeAppliesToDictionary_SimpleElements()
- {
- string json = @"{""First"":""1"",""Second"":""2""}";
- ClassWithSimpleDictionaryProperty obj = JsonSerializer.Deserialize<ClassWithSimpleDictionaryProperty>(json);
- }
-
- public class ClassWithSimpleDictionaryProperty
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public Dictionary<string, int> MyDictionary { get; set; }
- }
-
- [Fact]
- public static void NestedDictionaryElementTypeHandling_Overrides_GlobalOption()
- {
- // Strict policy on the dictionary element type overrides read-as-string on the collection property.
- string json = @"{""MyDictionary"":{""Key"":{""Float"":""1""}}}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ClassWithComplexDictionaryProperty>(json, s_optionReadFromStr));
-
- // Strict policy on the collection element type overrides write-as-string on the collection property
- var obj = new ClassWithComplexDictionaryProperty
- {
- MyDictionary = new Dictionary<string, ClassWith_StrictAttribute> { ["Key"] = new ClassWith_StrictAttribute { Float = 1 } }
- };
- Assert.Equal(@"{""MyDictionary"":{""Key"":{""Float"":1}}}", JsonSerializer.Serialize(obj, s_optionReadFromStr));
- }
-
- public class ClassWithComplexDictionaryProperty
- {
- public Dictionary<string, ClassWith_StrictAttribute> MyDictionary { get; set; }
- }
-
- [Fact]
- public static void TypeAttributeAppliesTo_CustomCollectionElements()
- {
- string json = @"[""1""]";
- MyCustomList obj = JsonSerializer.Deserialize<MyCustomList>(json);
- Assert.Equal(json, JsonSerializer.Serialize(obj));
- }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public class MyCustomList : List<int> { }
-
- [Fact]
- public static void TypeAttributeAppliesTo_CustomCollectionElements_HonoredWhenProperty()
- {
- string json = @"{""List"":[""1""]}";
- ClassWithCustomList obj = JsonSerializer.Deserialize<ClassWithCustomList>(json);
- Assert.Equal(json, JsonSerializer.Serialize(obj));
- }
-
- public class ClassWithCustomList
- {
- public MyCustomList List { get; set; }
- }
-
- [Fact]
- public static void TypeAttributeAppliesTo_CustomDictionaryElements()
- {
- string json = @"{""Key"":""1""}";
- MyCustomDictionary obj = JsonSerializer.Deserialize<MyCustomDictionary>(json);
- Assert.Equal(json, JsonSerializer.Serialize(obj));
- }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public class MyCustomDictionary : Dictionary<string, int> { }
-
- [Fact]
- public static void TypeAttributeAppliesTo_CustomDictionaryElements_HonoredWhenProperty()
- {
- string json = @"{""Dictionary"":{""Key"":""1""}}";
- ClassWithCustomDictionary obj = JsonSerializer.Deserialize<ClassWithCustomDictionary>(json);
- Assert.Equal(json, JsonSerializer.Serialize(obj));
- }
-
- public class ClassWithCustomDictionary
- {
- public MyCustomDictionary Dictionary { get; set; }
- }
-
- [Fact]
- public static void Attribute_OnType_NotRecursive()
- {
- // Recursive behavior, where number handling setting on a property is applied to subsequent
- // properties in its type closure, would allow a string number. This is not supported.
- string json = @"{""NestedClass"":{""MyInt"":""1""}}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<AttributeAppliedToFirstLevelProp>(json));
-
- var obj = new AttributeAppliedToFirstLevelProp
- {
- NestedClass = new NonNumberType { MyInt = 1 }
- };
- Assert.Equal(@"{""NestedClass"":{""MyInt"":1}}", JsonSerializer.Serialize(obj));
- }
-
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public class AttributeAppliedToFirstLevelProp
- {
- public NonNumberType NestedClass { get; set; }
- }
-
- public class NonNumberType
- {
- public int MyInt { get; set; }
- }
-
- [Fact]
- public static void HandlingOnMemberOverridesHandlingOnType_Enumerable()
- {
- string json = @"{""List"":[""1""]}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<MyCustomListWrapper>(json));
-
- var obj = new MyCustomListWrapper
- {
- List = new MyCustomList { 1 }
- };
- Assert.Equal(@"{""List"":[1]}", JsonSerializer.Serialize(obj));
- }
-
- public class MyCustomListWrapper
- {
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- public MyCustomList List { get; set; }
- }
-
- [Fact]
- public static void HandlingOnMemberOverridesHandlingOnType_Dictionary()
- {
- string json = @"{""Dictionary"":{""Key"":""1""}}";
- Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<MyCustomDictionaryWrapper>(json));
-
- var obj1 = new MyCustomDictionaryWrapper
- {
- Dictionary = new MyCustomDictionary { ["Key"] = 1 }
- };
- Assert.Equal(@"{""Dictionary"":{""Key"":1}}", JsonSerializer.Serialize(obj1));
- }
-
- public class MyCustomDictionaryWrapper
- {
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- public MyCustomDictionary Dictionary { get; set; }
- }
-
- [Fact]
- public static void Attribute_Allowed_On_NonNumber_NonCollection_Property()
- {
- const string Json = @"{""MyProp"":{""MyInt"":1}}";
-
- ClassWith_NumberHandlingOn_ObjectProperty obj = JsonSerializer.Deserialize<ClassWith_NumberHandlingOn_ObjectProperty>(Json);
- Assert.Equal(1, obj.MyProp.MyInt);
-
- string json = JsonSerializer.Serialize(obj);
- Assert.Equal(Json, json);
- }
-
- public class ClassWith_NumberHandlingOn_ObjectProperty
- {
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- public NonNumberType MyProp { get; set; }
- }
-
- [Fact]
- public static void Attribute_Allowed_On_Property_WithCustomConverter()
- {
- string json = @"{""Prop"":1}";
-
- // Converter returns 25 regardless of input.
- var obj = JsonSerializer.Deserialize<ClassWith_NumberHandlingOn_Property_WithCustomConverter>(json);
- Assert.Equal(25, obj.Prop);
-
- // Converter throws this exception regardless of input.
- NotImplementedException ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(obj));
- Assert.Equal("Converter was called", ex.Message);
- }
-
- public class ClassWith_NumberHandlingOn_Property_WithCustomConverter
- {
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- [JsonConverter(typeof(ConverterForInt32))]
- public int Prop { get; set; }
- }
-
- [Fact]
- public static void Attribute_Allowed_On_Type_WithCustomConverter()
- {
- string json = @"{}";
- NotImplementedException ex;
-
- // Assert regular Read/Write methods on custom converter are called.
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Deserialize<ClassWith_NumberHandlingOn_Type_WithCustomConverter>(json));
- Assert.Equal("Converter was called", ex.Message);
-
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(new ClassWith_NumberHandlingOn_Type_WithCustomConverter()));
- Assert.Equal("Converter was called", ex.Message);
- }
-
- [JsonNumberHandling(JsonNumberHandling.Strict)]
- [JsonConverter(typeof(ConverterForMyType))]
- public class ClassWith_NumberHandlingOn_Type_WithCustomConverter
- {
- }
-
- private class ConverterForMyType : JsonConverter<ClassWith_NumberHandlingOn_Type_WithCustomConverter>
- {
- public override ClassWith_NumberHandlingOn_Type_WithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException("Converter was called");
- }
-
- public override void Write(Utf8JsonWriter writer, ClassWith_NumberHandlingOn_Type_WithCustomConverter value, JsonSerializerOptions options)
- {
- throw new NotImplementedException("Converter was called");
- }
- }
-
- [Fact]
- public static void CustomConverterOverridesBuiltInLogic()
- {
- var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
- {
- Converters = { new ConverterForInt32(), new ConverterForFloat() }
- };
-
- string json = @"""32""";
-
- // Converter returns 25 regardless of input.
- Assert.Equal(25, JsonSerializer.Deserialize<int>(json, options));
-
- // Converter throws this exception regardless of input.
- NotImplementedException ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(4, options));
- Assert.Equal("Converter was called", ex.Message);
-
- json = @"""NaN""";
-
- // Converter returns 25 if NaN.
- Assert.Equal(25, JsonSerializer.Deserialize<float?>(json, options));
-
- // Converter writes 25 if NaN.
- Assert.Equal("25", JsonSerializer.Serialize((float?)float.NaN, options));
- }
-
- public class ConverterForFloat : JsonConverter<float?>
- {
- public override float? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.String && reader.GetString() == "NaN")
- {
- return 25;
- }
-
- throw new NotSupportedException();
- }
-
- public override void Write(Utf8JsonWriter writer, float? value, JsonSerializerOptions options)
- {
- if (float.IsNaN(value.Value))
- {
- writer.WriteNumberValue(25);
- return;
- }
-
- throw new NotSupportedException();
- }
- }
-
- [Fact]
- public static void JsonNumberHandling_ArgOutOfRangeFail()
- {
- // Global options
- ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
- () => new JsonSerializerOptions { NumberHandling = (JsonNumberHandling)(-1) });
- Assert.Contains("value", ex.ToString());
- Assert.Throws<ArgumentOutOfRangeException>(
- () => new JsonSerializerOptions { NumberHandling = (JsonNumberHandling)(8) });
-
- ex = Assert.Throws<ArgumentOutOfRangeException>(
- () => new JsonNumberHandlingAttribute((JsonNumberHandling)(-1)));
- Assert.Contains("handling", ex.ToString());
- Assert.Throws<ArgumentOutOfRangeException>(
- () => new JsonNumberHandlingAttribute((JsonNumberHandling)(8)));
- }
-
- [Fact]
- public static void InternalCollectionConverter_CustomNumberConverter_GlobalOption()
- {
- NotImplementedException ex;
-
- var list = new List<int> { 1 };
- var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
- {
- Converters = { new ConverterForInt32() }
- };
-
- // Assert converter methods are called and not Read/WriteWithNumberHandling (which would throw InvalidOperationException).
- // Converter returns 25 regardless of input.
- Assert.Equal(25, JsonSerializer.Deserialize<List<int>>(@"[""1""]", options)[0]);
- // Converter throws this exception regardless of input.
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(list, options));
- Assert.Equal("Converter was called", ex.Message);
-
- var list2 = new List<int?> { 1 };
- Assert.Equal(25, JsonSerializer.Deserialize<List<int?>>(@"[""1""]", options)[0]);
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(list2, options));
- Assert.Equal("Converter was called", ex.Message);
-
- // Okay to set number handling for number collection property when number is handled with custom converter;
- // converter Read/Write methods called.
- ClassWithListPropAndAttribute obj1 = JsonSerializer.Deserialize<ClassWithListPropAndAttribute>(@"{""Prop"":[""1""]}", options);
- Assert.Equal(25, obj1.Prop[0]);
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(obj1, options));
- Assert.Equal("Converter was called", ex.Message);
-
- ClassWithDictPropAndAttribute obj2 = JsonSerializer.Deserialize<ClassWithDictPropAndAttribute>(@"{""Prop"":{""1"":""1""}}", options);
- Assert.Equal(25, obj2.Prop[1]);
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(obj2, options));
- Assert.Equal("Converter was called", ex.Message);
- }
-
- private class ClassWithListPropAndAttribute
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public List<int> Prop { get; set; }
- }
-
- private class ClassWithDictPropAndAttribute
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- public Dictionary<int, int?> Prop { get; set; }
- }
-
- [Fact]
- public static void InternalCollectionConverter_CustomNumberConverter_OnProperty()
- {
- // Invalid to set number handling for number collection property when number is handled with custom converter.
- var ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithListPropAndAttribute_ConverterOnProp>(""));
- Assert.Contains(nameof(ClassWithListPropAndAttribute_ConverterOnProp), ex.ToString());
- Assert.Contains("IntProp", ex.ToString());
-
- ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new ClassWithListPropAndAttribute_ConverterOnProp()));
- Assert.Contains(nameof(ClassWithListPropAndAttribute_ConverterOnProp), ex.ToString());
- Assert.Contains("IntProp", ex.ToString());
-
- ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<ClassWithDictPropAndAttribute_ConverterOnProp>(""));
- Assert.Contains(nameof(ClassWithDictPropAndAttribute_ConverterOnProp), ex.ToString());
- Assert.Contains("IntProp", ex.ToString());
-
- ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new ClassWithDictPropAndAttribute_ConverterOnProp()));
- Assert.Contains(nameof(ClassWithDictPropAndAttribute_ConverterOnProp), ex.ToString());
- Assert.Contains("IntProp", ex.ToString());
- }
-
- private class ClassWithListPropAndAttribute_ConverterOnProp
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- [JsonConverter(typeof(ListOfIntConverter))]
- public List<int> IntProp { get; set; }
- }
-
- private class ClassWithDictPropAndAttribute_ConverterOnProp
- {
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
- [JsonConverter(typeof(ClassWithDictPropAndAttribute_ConverterOnProp))]
- public Dictionary<int, int?> IntProp { get; set; }
- }
-
- public class ListOfIntConverter : JsonConverter<List<int>>
- {
- public override List<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
- public override void Write(Utf8JsonWriter writer, List<int> value, JsonSerializerOptions options) => throw new NotImplementedException();
- }
-
- [Fact]
- public static void InternalCollectionConverter_CustomNullableNumberConverter()
- {
- NotImplementedException ex;
-
- var dict = new Dictionary<int, int?> { [1] = 1 };
- var options = new JsonSerializerOptions(s_optionReadAndWriteFromStr)
- {
- Converters = { new ConverterForNullableInt32() }
- };
-
- // Assert converter methods are called and not Read/WriteWithNumberHandling (which would throw InvalidOperationException).
- // Converter returns 25 regardless of input.
- Assert.Equal(25, JsonSerializer.Deserialize<Dictionary<int, int?>>(@"{""1"":""1""}", options)[1]);
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(dict, options));
- Assert.Equal("Converter was called", ex.Message);
-
- var obj = JsonSerializer.Deserialize<ClassWithDictPropAndAttribute>(@"{""Prop"":{""1"":""1""}}", options);
- Assert.Equal(25, obj.Prop[1]);
- ex = Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(obj, options));
- Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(dict, options));
- Assert.Equal("Converter was called", ex.Message);
- }
-
- public class ConverterForNullableInt32 : JsonConverter<int?>
- {
- public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- return 25;
- }
-
- public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
- {
- throw new NotImplementedException("Converter was called");
- }
- }
-
- /// <summary>
- /// Example of a custom converter that uses the options to determine behavior.
- /// </summary>
- [Fact]
- public static void AdaptableCustomConverter()
- {
- // Baseline without custom converter
- PlainClassWithList obj = new() { Prop = new List<int>() { 1 } };
- string json = JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr);
- Assert.Equal("{\"Prop\":[\"1\"]}", json);
-
- obj = JsonSerializer.Deserialize<PlainClassWithList>(json, s_optionReadAndWriteFromStr);
- Assert.Equal(1, obj.Prop[0]);
-
- // First with numbers
- JsonSerializerOptions options = new()
- {
- Converters = { new AdaptableInt32Converter() }
- };
-
- obj = new PlainClassWithList() { Prop = new List<int>() { 1 } };
- json = JsonSerializer.Serialize(obj, options);
- Assert.Equal("{\"Prop\":[101]}", json);
-
- obj = JsonSerializer.Deserialize<PlainClassWithList>(json, options);
- Assert.Equal(1, obj.Prop[0]);
-
- // Then with strings
- options = new JsonSerializerOptions()
- {
- NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString,
- Converters = { new AdaptableInt32Converter() }
- };
-
- obj = new PlainClassWithList() { Prop = new List<int>() { 1 } };
- json = JsonSerializer.Serialize(obj, options);
- Assert.Equal("{\"Prop\":[\"101\"]}", json);
-
- obj = JsonSerializer.Deserialize<PlainClassWithList>(json, options);
- Assert.Equal(1, obj.Prop[0]);
- }
-
- private class PlainClassWithList
- {
- public List<int> Prop { get; set; }
- }
-
- public class AdaptableInt32Converter : JsonConverter<int>
- {
- public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if ((JsonNumberHandling.AllowReadingFromString & options.NumberHandling) != 0)
- {
- // Assume it's a string; don't use TryParse().
- return int.Parse(reader.GetString(), CultureInfo.InvariantCulture) - 100;
- }
- else
- {
- return reader.GetInt32() - 100;
- }
- }
-
- public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
- {
- if ((JsonNumberHandling.WriteAsString & options.NumberHandling) != 0)
- {
- writer.WriteStringValue((value + 100).ToString(CultureInfo.InvariantCulture));
- }
- else
- {
- writer.WriteNumberValue(value + 100);
- }
- }
- }
+ public NumberHandlingTestsDynamic() : base(JsonSerializerWrapper.StringSerializer) { }
}
public class NumberHandlingTests_AsyncStreamOverload : NumberHandlingTests_OverloadSpecific
};
Result result = await Serializer.DeserializeWrapper<Result>(json, options);
- JsonTestHelper.AssertJsonEqual(json, JsonSerializer.Serialize(result, options));
+ JsonTestHelper.AssertJsonEqual(json, await Serializer.SerializeWrapper(result, options));
}
public static IEnumerable<object[]> NumberHandling_ForPropsReadAfter_DeserializingCtorParams_TestData()
<Compile Include="..\Common\JsonCreationHandlingTests.Enumerable.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonCreationHandlingTests.Enumerable.cs" />
<Compile Include="..\Common\JsonCreationHandlingTests.Generic.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonCreationHandlingTests.Generic.cs" />
<Compile Include="..\Common\JsonCreationHandlingTests.Object.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonCreationHandlingTests.Object.cs" />
+ <Compile Include="..\Common\JsonNumberTestData.cs" Link="CommonTest\System\Text\Json\Tests\JsonNumberTestData" />
<Compile Include="..\Common\JsonSerializerWrapper.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\JsonSerializerWrapper.cs" />
<Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />
<Compile Include="..\Common\NodeInteropTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\NodeInteropTests.cs" />
+ <Compile Include="..\Common\NumberHandlingTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\NumberHandlingTests.cs" />
<Compile Include="..\Common\PropertyNameTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\PropertyNameTests.cs" />
<Compile Include="..\Common\PropertyVisibilityTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\PropertyVisibilityTests.cs" />
<Compile Include="..\Common\PropertyVisibilityTests.InitOnly.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\PropertyVisibilityTests.InitOnly.cs" />
<Compile Include="JsonNode\ParseTests.cs" />
<Compile Include="JsonNode\ParentPathRootTests.cs" />
<Compile Include="JsonNode\ToStringTests.cs" />
- <Compile Include="JsonNumberTestData.cs" />
<Compile Include="JsonPropertyTests.cs" />
<Compile Include="JsonReaderStateAndOptionsTests.cs" />
<Compile Include="JsonTestHelper.cs" />