{
// The maximum number of parameters a constructor can have where it can be supported by the serializer.
public const int MaxParameterCount = 64;
+
+ // Standard format for double and single on non-inbox frameworks.
+ public const string DoubleFormatString = "G17";
+ public const string SingleFormatString = "G9";
}
}
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
using System.Reflection;
+using System.Reflection.Metadata;
using System.Text.Json;
using System.Text.Json.Reflection;
using System.Text.Json.Serialization;
sb.Append($@"
{JsonPropertyInfoValuesTypeRef}<{memberTypeCompilableName}> {infoVarName} = new {JsonPropertyInfoValuesTypeRef}<{memberTypeCompilableName}>()
{{
- IsProperty = {ToCSharpKeyword(memberMetadata.IsProperty)},
- IsPublic = {ToCSharpKeyword(memberMetadata.IsPublic)},
- IsVirtual = {ToCSharpKeyword(memberMetadata.IsVirtual)},
+ IsProperty = {FormatBool(memberMetadata.IsProperty)},
+ IsPublic = {FormatBool(memberMetadata.IsPublic)},
+ IsVirtual = {FormatBool(memberMetadata.IsVirtual)},
DeclaringType = typeof({memberMetadata.DeclaringTypeRef}),
PropertyTypeInfo = {memberTypeFriendlyName},
Converter = {converterValue},
Getter = {getterValue},
Setter = {setterValue},
IgnoreCondition = {ignoreConditionNamedArg},
- HasJsonInclude = {ToCSharpKeyword(memberMetadata.HasJsonInclude)},
- IsExtensionData = {ToCSharpKeyword(memberMetadata.IsExtensionData)},
+ HasJsonInclude = {FormatBool(memberMetadata.HasJsonInclude)},
+ IsExtensionData = {FormatBool(memberMetadata.IsExtensionData)},
NumberHandling = {GetNumberHandlingAsStr(memberMetadata.NumberHandling)},
PropertyName = ""{clrPropertyName}"",
JsonPropertyName = {jsonPropertyNameValue}
for (int i = 0; i < paramCount; i++)
{
ParameterInfo reflectionInfo = parameters[i].ParameterInfo;
-
- string parameterTypeRef = reflectionInfo.ParameterType.GetCompilableName();
+ Type parameterType = reflectionInfo.ParameterType;
+ string parameterTypeRef = parameterType.GetCompilableName();
object? defaultValue = reflectionInfo.GetDefaultValue();
- string defaultValueAsStr = GetParamDefaultValueAsString(defaultValue, parameterTypeRef);
+ string defaultValueAsStr = GetParamDefaultValueAsString(defaultValue, parameterType, parameterTypeRef);
sb.Append(@$"
{InfoVarName} = new()
Name = ""{reflectionInfo.Name!}"",
ParameterType = typeof({parameterTypeRef}),
Position = {reflectionInfo.Position},
- HasDefaultValue = {ToCSharpKeyword(reflectionInfo.HasDefaultValue)},
+ HasDefaultValue = {FormatBool(reflectionInfo.HasDefaultValue)},
DefaultValue = {defaultValueAsStr}
}};
{parametersVarName}[{i}] = {InfoVarName};
private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ get; }} = new {JsonSerializerOptionsTypeRef}()
{{
DefaultIgnoreCondition = {JsonIgnoreConditionTypeRef}.{options.DefaultIgnoreCondition},
- IgnoreReadOnlyFields = {ToCSharpKeyword(options.IgnoreReadOnlyFields)},
- IgnoreReadOnlyProperties = {ToCSharpKeyword(options.IgnoreReadOnlyProperties)},
- IncludeFields = {ToCSharpKeyword(options.IncludeFields)},
- WriteIndented = {ToCSharpKeyword(options.WriteIndented)},{namingPolicyInit}
+ IgnoreReadOnlyFields = {FormatBool(options.IgnoreReadOnlyFields)},
+ IgnoreReadOnlyProperties = {FormatBool(options.IgnoreReadOnlyProperties)},
+ IncludeFields = {FormatBool(options.IncludeFields)},
+ WriteIndented = {FormatBool(options.WriteIndented)},{namingPolicyInit}
}};";
}
: "default";
private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";
- }
- private static string ToCSharpKeyword(bool value) => value.ToString().ToLowerInvariant();
+ private static string FormatBool(bool value) => value ? "true" : "false";
- private static string GetParamDefaultValueAsString(object? value, string objectTypeAsStr)
- {
- switch (value)
+ private string GetParamDefaultValueAsString(object? value, Type type, string typeRef)
{
- case null:
- return $"default({objectTypeAsStr})";
- case bool boolVal:
- return ToCSharpKeyword(boolVal);
- default:
- return value!.ToString();
+ if (value == null)
+ {
+ return $"default({typeRef})";
+ }
+
+ if (type.IsEnum)
+ {
+ // Roslyn gives us an instance of the underlying type, which is numerical.
+#if DEBUG
+ Type runtimeType = _generationSpec.MetadataLoadContext.Resolve(value.GetType());
+ Debug.Assert(_generationSpec.IsNumberType(runtimeType));
+#endif
+
+ // Return the numeric value.
+ return FormatNumber();
+ }
+
+ switch (value)
+ {
+ case string @string:
+ return SymbolDisplay.FormatLiteral(@string, quote: true); ;
+ case char @char:
+ return SymbolDisplay.FormatLiteral(@char, quote: true);
+ case double.NegativeInfinity:
+ return "double.NegativeInfinity";
+ case double.PositiveInfinity:
+ return "double.PositiveInfinity";
+ case double.NaN:
+ return "double.NaN";
+ case double @double:
+ return $"({typeRef})({@double.ToString(JsonConstants.DoubleFormatString, CultureInfo.InvariantCulture)})";
+ case float.NegativeInfinity:
+ return "float.NegativeInfinity";
+ case float.PositiveInfinity:
+ return "float.PositiveInfinity";
+ case float.NaN:
+ return "float.NaN";
+ case float @float:
+ return $"({typeRef})({@float.ToString(JsonConstants.SingleFormatString, CultureInfo.InvariantCulture)})";
+ case decimal.MaxValue:
+ return "decimal.MaxValue";
+ case decimal.MinValue:
+ return "decimal.MinValue";
+ case decimal @decimal:
+ return @decimal.ToString(CultureInfo.InvariantCulture);
+ case bool @bool:
+ return FormatBool(@bool);
+ default:
+ // Assume this is a number.
+ return FormatNumber();
+ }
+
+ string FormatNumber() => $"({typeRef})({Convert.ToString(value, CultureInfo.InvariantCulture)})";
}
}
}
GuidType = _guidType,
StringType = _stringType,
NumberTypes = _numberTypes,
+#if DEBUG
+ MetadataLoadContext = _metadataLoadContext,
+#endif
};
}
using System;
using System.Collections.Generic;
using System.Text;
+using System.Text.Json.Reflection;
+using Microsoft.CodeAnalysis;
namespace System.Text.Json.SourceGeneration
{
{
public List<ContextGenerationSpec> ContextGenerationSpecList { get; init; }
+#if DEBUG
+ public MetadataLoadContextInternal MetadataLoadContext { get; init; }
+#endif
public Type BooleanType { get; init; }
public Type ByteArrayType { get; init; }
public Type CharType { get; init; }
#if BUILDING_INBOX_LIBRARY
return Utf8Formatter.TryFormat(value, destination, out bytesWritten);
#else
- const string FormatString = "G17";
-
- string utf16Text = value.ToString(FormatString, CultureInfo.InvariantCulture);
+ string utf16Text = value.ToString(JsonConstants.DoubleFormatString, CultureInfo.InvariantCulture);
// Copy the value to the destination, if it's large enough.
#if BUILDING_INBOX_LIBRARY
return Utf8Formatter.TryFormat(value, destination, out bytesWritten);
#else
- const string FormatString = "G9";
-
- string utf16Text = value.ToString(FormatString, CultureInfo.InvariantCulture);
+ string utf16Text = value.ToString(JsonConstants.SingleFormatString, CultureInfo.InvariantCulture);
// Copy the value to the destination, if it's large enough.
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
using System.Threading.Tasks;
using Xunit;
public ClassWithIgnoredSameType(ClassWithIgnoredSameType prop) { }
}
+
+ public async Task TestClassWithDefaultCtorParams()
+ {
+ ClassWithDefaultCtorParams obj = new ClassWithDefaultCtorParams(
+ new Point_2D_Struct_WithAttribute(1, 2),
+ DayOfWeek.Sunday,
+ (DayOfWeek)(-2),
+ (DayOfWeek)19,
+ BindingFlags.CreateInstance | BindingFlags.FlattenHierarchy,
+ SampleEnumUInt32.MinZero,
+ "Hello world!",
+ null,
+ "xzy",
+ 'c',
+ ' ',
+ 23,
+ 4,
+ -40,
+ double.Epsilon,
+ 23,
+ 4,
+ -40,
+ float.MinValue,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10);
+
+ string json = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+ obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithDefaultCtorParams>(json);
+ JsonTestHelper.AssertJsonEqual(json, await JsonSerializerWrapperForString.SerializeWrapper(obj));
+ }
+
+ public class ClassWithDefaultCtorParams
+ {
+ public Point_2D_Struct_WithAttribute Struct { get; }
+ public DayOfWeek Enum1 { get; }
+ public DayOfWeek Enum2 { get; }
+ public DayOfWeek Enum3 { get; }
+ public BindingFlags Enum4 { get; }
+ public SampleEnumUInt32 Enum5 { get; }
+ public string Str1 { get; }
+ public string Str2 { get; }
+ public string Str3 { get; }
+ public char Char1 { get; }
+ public char Char2 { get; }
+ public double Double1 { get; }
+ public double Double2 { get; }
+ public double Double3 { get; }
+ public double Double4 { get; }
+ public double Double5 { get; }
+ public float Float1 { get; }
+ public float Float2 { get; }
+ public float Float3 { get; }
+ public float Float4 { get; }
+ public float Float5 { get; }
+ public byte Byte { get; }
+ public decimal Decimal1 { get; }
+ public decimal Decimal2 { get; }
+ public short Short { get; }
+ public sbyte Sbyte { get; }
+ public int Int { get; }
+ public long Long { get; }
+ public ushort UShort { get; }
+ public uint UInt { get; }
+ public ulong ULong { get; }
+
+ public ClassWithDefaultCtorParams(
+ Point_2D_Struct_WithAttribute @struct = default,
+ DayOfWeek enum1 = default,
+ DayOfWeek enum2 = DayOfWeek.Sunday,
+ DayOfWeek enum3 = DayOfWeek.Sunday | DayOfWeek.Monday,
+ BindingFlags enum4 = BindingFlags.CreateInstance | BindingFlags.ExactBinding,
+ SampleEnumUInt32 enum5 = SampleEnumUInt32.MinZero,
+ string str1 = "abc",
+ string str2 = "",
+ string str3 = "\n\r⁉️\'\"\u200D\f\t\v\0\a\b\\\'\"",
+ char char1 = 'a',
+ char char2 = '\u200D',
+ double double1 = double.NegativeInfinity,
+ double double2 = double.PositiveInfinity,
+ double double3 = double.NaN,
+ double double4 = double.MaxValue,
+ double double5 = double.Epsilon,
+ float float1 = float.NegativeInfinity,
+ float float2 = float.PositiveInfinity,
+ float float3 = float.NaN,
+ float float4 = float.MinValue,
+ float float5 = float.Epsilon,
+ byte @byte = byte.MinValue,
+ decimal @decimal1 = decimal.MinValue,
+ decimal @decimal2 = decimal.MaxValue,
+ short @short = short.MinValue,
+ sbyte @sbyte = sbyte.MaxValue,
+ int @int = int.MinValue,
+ long @long = long.MaxValue,
+ ushort @ushort = ushort.MinValue,
+ uint @uint = uint.MaxValue,
+ ulong @ulong = ulong.MinValue)
+ {
+ Struct = @struct;
+ Enum1 = enum1;
+ Enum2 = enum2;
+ Enum3 = enum3;
+ Enum4 = enum4;
+ Enum5 = enum5;
+ Str1 = str1;
+ Str2 = str2;
+ Str3 = str3;
+ Char1 = char1;
+ Char2 = char2;
+ Double1 = double1;
+ Double2 = double2;
+ Double3 = double3;
+ Double4 = double4;
+ Float1 = float1;
+ Float2 = float2;
+ Float3 = float3;
+ Float4 = float4;
+ Byte = @byte;
+ Decimal1 = @decimal1;
+ Decimal2 = @decimal2;
+ Short = @short;
+ Sbyte = @sbyte;
+ Int = @int;
+ Long = @long;
+ UShort = @ushort;
+ UInt = @uint;
+ ULong = @ulong;
+ }
+ }
}
}
[JsonSerializable(typeof(LargeType_IgnoredProp_Bind_ParamWithDefaultValue))]
[JsonSerializable(typeof(LargeType_IgnoredProp_Bind_Param))]
[JsonSerializable(typeof(ClassWithIgnoredSameType))]
+ [JsonSerializable(typeof(ClassWithDefaultCtorParams))]
internal sealed partial class ConstructorTestsContext_Metadata : JsonSerializerContext
{
}
[JsonSerializable(typeof(LargeType_IgnoredProp_Bind_ParamWithDefaultValue))]
[JsonSerializable(typeof(LargeType_IgnoredProp_Bind_Param))]
[JsonSerializable(typeof(ClassWithIgnoredSameType))]
+ [JsonSerializable(typeof(ClassWithDefaultCtorParams))]
internal sealed partial class ConstructorTestsContext_Default : JsonSerializerContext
{
}