From 91021fe27b1b845f51ff29cfd47b2405fa91b683 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 6 Aug 2021 09:47:48 -0700 Subject: [PATCH] Add JSON src-gen support for deserializing with parameterized ctors (#56354) * Add JSON src-gen support for deserializing with parameterized ctors * Address API review and PR feedback * Fix issue with init-only properties * Make misc fixes for build --- eng/Versions.props | 4 +- .../System.Text.Json/Common/JsonConstants.cs | 11 + .../Common/ReflectionExtensions.cs | 81 ++++ .../System.Text.Json/gen/ClassType.cs | 2 +- .../gen/ContextGenerationSpec.cs | 2 +- .../gen/JsonSourceGenerator.Emitter.cs | 188 ++++++-- .../gen/JsonSourceGenerator.Parser.cs | 149 ++++--- .../gen/ParameterGenerationSpec.cs | 17 + .../gen/Reflection/ConstructorInfoWrapper.cs | 4 +- .../gen/Reflection/MethodInfoWrapper.cs | 41 +- .../gen/Reflection/ParameterInfoWrapper.cs | 6 + .../gen/Reflection/ReflectionExtensions.cs | 16 + .../gen/Reflection/RoslynExtensions.cs | 35 ++ .../gen/Reflection/TypeWrapper.cs | 14 +- .../gen/Resources/Strings.resx | 6 + .../gen/Resources/xlf/Strings.cs.xlf | 10 + .../gen/Resources/xlf/Strings.de.xlf | 10 + .../gen/Resources/xlf/Strings.es.xlf | 10 + .../gen/Resources/xlf/Strings.fr.xlf | 10 + .../gen/Resources/xlf/Strings.it.xlf | 10 + .../gen/Resources/xlf/Strings.ja.xlf | 10 + .../gen/Resources/xlf/Strings.ko.xlf | 10 + .../gen/Resources/xlf/Strings.pl.xlf | 10 + .../gen/Resources/xlf/Strings.pt-BR.xlf | 10 + .../gen/Resources/xlf/Strings.ru.xlf | 10 + .../gen/Resources/xlf/Strings.tr.xlf | 10 + .../gen/Resources/xlf/Strings.zh-Hans.xlf | 10 + .../gen/Resources/xlf/Strings.zh-Hant.xlf | 10 + .../System.Text.Json.SourceGeneration.csproj | 6 +- .../gen/TypeGenerationSpec.cs | 9 +- .../System.Text.Json/ref/System.Text.Json.cs | 19 +- .../ref/System.Text.Json.csproj | 1 + .../src/Resources/Strings.resx | 17 +- .../src/System.Text.Json.csproj | 9 +- ...eExtensions.cs => ReflectionExtensions.cs} | 9 +- .../src/System/Text/Json/JsonConstants.cs | 5 +- .../JsonMetadataServicesConverter.cs | 27 +- .../Object/ObjectConverterFactory.cs | 82 +--- ...edConstructorConverter.Large.Reflection.cs | 32 ++ ...ParameterizedConstructorConverter.Large.cs | 31 +- ...ParameterizedConstructorConverter.Small.cs | 6 +- ...ctWithParameterizedConstructorConverter.cs | 6 +- .../Value/NullableConverterFactory.cs | 1 + .../JsonSerializerOptions.Converters.cs | 1 + .../Metadata/GenericMethodHolder.cs | 1 + .../JsonMetadataServices.Converters.cs | 1 + .../Metadata/JsonMetadataServices.cs | 20 +- .../Metadata/JsonObjectInfoValues.cs | 42 ++ .../Metadata/JsonParameterInfo.cs | 49 ++- .../Metadata/JsonParameterInfoOfT.cs | 31 +- .../Metadata/JsonParameterInfoValues.cs | 36 ++ .../Metadata/JsonPropertyInfo.cs | 13 +- .../Metadata/JsonPropertyInfoOfT.cs | 5 +- .../Metadata/JsonTypeInfo.Cache.cs | 39 +- .../Serialization/Metadata/JsonTypeInfo.cs | 89 ++-- .../Metadata/JsonTypeInfoInternalOfT.cs | 41 +- .../Serialization/Metadata/MemberAccessor.cs | 2 +- .../Metadata/ReflectionEmitMemberAccessor.cs | 4 +- .../Metadata/ReflectionMemberAccessor.cs | 2 +- .../Text/Json/Serialization/ReadStack.cs | 2 +- .../Text/Json/ThrowHelper.Serialization.cs | 30 +- .../CollectionTests.Dictionary.cs | 2 +- .../CollectionTests.Generic.Read.cs | 2 +- .../CollectionTests.KeyValuePair.cs | 57 ++- .../CollectionTests.NonGeneric.Read.cs | 2 +- .../ConstructorTests.AttributePresence.cs | 46 +- .../ConstructorTests.Cache.cs | 37 +- .../ConstructorTests.Exceptions.cs | 75 ++-- .../ConstructorTests.ParameterMatching.cs | 416 +++++++++--------- .../ConstructorTests.Stream.cs | 19 +- .../Common/JsonSerializerWrapperForStream.cs | 8 +- .../ContextClasses.cs | 13 +- .../JsonSerializerContextTests.cs | 21 + .../RealWorldContextTests.cs | 5 +- .../Serialization/CollectionTests.cs | 107 ++++- .../Serialization/ConstructorTests.cs | 244 ++++++++++ .../Serialization/JsonSerializerWrapper.cs | 28 +- ...em.Text.Json.SourceGeneration.Tests.csproj | 6 + .../Serialization/ConstructorTests.cs | 30 ++ .../Serialization/ExceptionTests.cs | 4 +- .../JsonSerializerApiValidation.cs | 18 +- .../System.Text.Json.Tests.csproj | 11 +- 82 files changed, 1785 insertions(+), 740 deletions(-) create mode 100644 src/libraries/System.Text.Json/Common/JsonConstants.cs create mode 100644 src/libraries/System.Text.Json/gen/ParameterGenerationSpec.cs rename src/libraries/System.Text.Json/src/System/{TypeExtensions.cs => ReflectionExtensions.cs} (84%) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.Reflection.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonObjectInfoValues.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoValues.cs rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/ConstructorTests/ConstructorTests.AttributePresence.cs (67%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/ConstructorTests/ConstructorTests.Cache.cs (78%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/ConstructorTests/ConstructorTests.Exceptions.cs (72%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/ConstructorTests/ConstructorTests.ParameterMatching.cs (59%) rename src/libraries/System.Text.Json/tests/{System.Text.Json.Tests/Serialization => Common}/ConstructorTests/ConstructorTests.Stream.cs (94%) create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests.cs diff --git a/eng/Versions.props b/eng/Versions.props index 9bcc7facdfbc1..3446baa3f981d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -43,8 +43,8 @@ - 3.8.0 - 3.8.0 + 3.9.0 + 3.9.0 diff --git a/src/libraries/System.Text.Json/Common/JsonConstants.cs b/src/libraries/System.Text.Json/Common/JsonConstants.cs new file mode 100644 index 0000000000000..a97b11cf15bf2 --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonConstants.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json +{ + internal static partial class JsonConstants + { + // The maximum number of parameters a constructor can have where it can be supported by the serializer. + public const int MaxParameterCount = 64; + } +} diff --git a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs index 623aa48fceeba..8a1a58a97a2cb 100644 --- a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Reflection; +using System.Collections.Generic; #if !BUILDING_SOURCE_GENERATOR using System.Diagnostics.CodeAnalysis; #endif @@ -229,5 +230,85 @@ public static bool IsVirtual(this PropertyInfo? propertyInfo) Debug.Assert(propertyInfo != null); return propertyInfo != null && (propertyInfo.GetMethod?.IsVirtual == true || propertyInfo.SetMethod?.IsVirtual == true); } + + public static bool IsKeyValuePair(this Type type, Type? keyValuePairType = null) + { + if (!type.IsGenericType) + { + return false; + } + + // Work around not being able to use typeof(KeyValuePair<,>) directly during compile-time src gen type analysis. + keyValuePairType ??= typeof(KeyValuePair<,>); + + Type generic = type.GetGenericTypeDefinition(); + return generic == keyValuePairType; + } + + public static bool TryGetDeserializationConstructor( +#if !BUILDING_SOURCE_GENERATOR + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] +#endif + this Type type, + bool useDefaultCtorInAnnotatedStructs, + out ConstructorInfo? deserializationCtor) + { + ConstructorInfo? ctorWithAttribute = null; + ConstructorInfo? publicParameterlessCtor = null; + ConstructorInfo? lonePublicCtor = null; + + ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); + + if (constructors.Length == 1) + { + lonePublicCtor = constructors[0]; + } + + foreach (ConstructorInfo constructor in constructors) + { + if (HasJsonConstructorAttribute(constructor)) + { + if (ctorWithAttribute != null) + { + deserializationCtor = null; + return false; + } + + ctorWithAttribute = constructor; + } + else if (constructor.GetParameters().Length == 0) + { + publicParameterlessCtor = constructor; + } + } + + // For correctness, throw if multiple ctors have [JsonConstructor], even if one or more are non-public. + ConstructorInfo? dummyCtorWithAttribute = ctorWithAttribute; + + constructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); + foreach (ConstructorInfo constructor in constructors) + { + if (HasJsonConstructorAttribute(constructor)) + { + if (dummyCtorWithAttribute != null) + { + deserializationCtor = null; + return false; + } + + dummyCtorWithAttribute = constructor; + } + } + + // Structs will use default constructor if attribute isn't used. + if (useDefaultCtorInAnnotatedStructs && type.IsValueType && ctorWithAttribute == null) + { + deserializationCtor = null; + return true; + } + + deserializationCtor = ctorWithAttribute ?? publicParameterlessCtor ?? lonePublicCtor; + return true; + } } } diff --git a/src/libraries/System.Text.Json/gen/ClassType.cs b/src/libraries/System.Text.Json/gen/ClassType.cs index fa6aab3d2573b..d0df94330ec8e 100644 --- a/src/libraries/System.Text.Json/gen/ClassType.cs +++ b/src/libraries/System.Text.Json/gen/ClassType.cs @@ -16,6 +16,6 @@ internal enum ClassType Enumerable = 4, Dictionary = 5, Nullable = 6, - Enum = 7, + Enum = 7 } } diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs index 1339e4b9d167b..545d5628297b8 100644 --- a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs @@ -21,7 +21,7 @@ internal sealed class ContextGenerationSpec public List RootSerializableTypes { get; } = new(); - public HashSet? NullableUnderlyingTypes { get; } = new(); + public HashSet? ImplicitlyRegisteredTypes { get; } = new(); public List ContextClassDeclarationList { get; init; } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index fe8447e1309a8..118a113910a7c 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; +using System.Text.Json.Reflection; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -20,6 +21,7 @@ private sealed partial class Emitter private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter"; private const string OptionsInstanceVariableName = "Options"; private const string PropInitMethodNameSuffix = "PropInit"; + private const string CtorParamInitMethodNameSuffix = "CtorParamInit"; private const string SerializeMethodNameSuffix = "Serialize"; private const string CreateValueInfoMethodName = "CreateValueInfo"; private const string DefaultOptionsStaticVarName = "s_defaultOptions"; @@ -55,6 +57,8 @@ private sealed partial class Emitter private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling"; private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext"; private const string JsonMetadataServicesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonMetadataServices"; + private const string JsonObjectInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues"; + private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues"; private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo"; private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo"; @@ -213,11 +217,16 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) { source = GenerateForObject(typeGenerationSpec); - if (typeGenerationSpec.PropertyGenSpecList != null) + foreach (PropertyGenerationSpec spec in typeGenerationSpec.PropertyGenSpecList) { - foreach (PropertyGenerationSpec metadata in typeGenerationSpec.PropertyGenSpecList) + GenerateTypeInfo(spec.TypeGenerationSpec); + } + + if (typeGenerationSpec.ConstructionStrategy == ObjectConstructionStrategy.ParameterizedConstructor) + { + foreach (ParameterGenerationSpec spec in typeGenerationSpec.CtorParamGenSpecArray!) { - GenerateTypeInfo(metadata.TypeGenerationSpec); + GenerateTypeInfo(spec.TypeGenerationSpec); } } } @@ -541,74 +550,77 @@ private string GenerateFastPathFuncForDictionary(TypeGenerationSpec typeGenerati private string GenerateForObject(TypeGenerationSpec typeMetadata) { string typeFriendlyName = typeMetadata.TypeInfoPropertyName; + ObjectConstructionStrategy constructionStrategy = typeMetadata.ConstructionStrategy; - string createObjectFuncTypeArg = typeMetadata.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor - ? $"createObjectFunc: static () => new {typeMetadata.TypeRef}()" - : "createObjectFunc: null"; + string creatorInvocation = constructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor + ? $"static () => new {typeMetadata.TypeRef}()" + : "null"; - string propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}"; - string? propMetadataInitFuncSource = null; - string propMetadataInitFuncNamedArg; + string parameterizedCreatorInvocation = constructionStrategy == ObjectConstructionStrategy.ParameterizedConstructor + ? GetParameterizedCtorInvocationFunc(typeMetadata) + : "null"; - string serializeMethodName = $"{typeFriendlyName}{SerializeMethodNameSuffix}"; + string? propMetadataInitFuncSource = null; + string? ctorParamMetadataInitFuncSource = null; string? serializeFuncSource = null; - string serializeFuncNamedArg; + + string propInitMethodName = "null"; + string ctorParamMetadataInitMethodName = "null"; + string serializeMethodName = "null"; if (typeMetadata.GenerateMetadata) { - propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, typeMetadata.PropertyGenSpecList!); - propMetadataInitFuncNamedArg = $@"propInitFunc: {propInitMethodName}"; - } - else - { - propMetadataInitFuncNamedArg = @"propInitFunc: null"; + propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata); + propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}"; + + if (constructionStrategy == ObjectConstructionStrategy.ParameterizedConstructor) + { + ctorParamMetadataInitFuncSource = GenerateCtorParamMetadataInitFunc(typeMetadata); + ctorParamMetadataInitMethodName = $"{typeFriendlyName}{CtorParamInitMethodNameSuffix}"; + } } if (typeMetadata.GenerateSerializationLogic) { - serializeFuncSource = GenerateFastPathFuncForObject(typeMetadata, serializeMethodName); - serializeFuncNamedArg = $@"serializeFunc: {serializeMethodName}"; - } - else - { - serializeFuncNamedArg = @"serializeFunc: null"; + serializeFuncSource = GenerateFastPathFuncForObject(typeMetadata); + serializeMethodName = $"{typeFriendlyName}{SerializeMethodNameSuffix}"; } - string objectInfoInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeMetadata.TypeRef}>( - {OptionsInstanceVariableName}, - {createObjectFuncTypeArg}, - {propMetadataInitFuncNamedArg}, - {GetNumberHandlingAsStr(typeMetadata.NumberHandling)}, - {serializeFuncNamedArg});"; + const string ObjectInfoVarName = "objectInfo"; + string genericArg = typeMetadata.TypeRef; - string additionalSource; - if (propMetadataInitFuncSource == null || serializeFuncSource == null) - { - additionalSource = propMetadataInitFuncSource ?? serializeFuncSource; - } - else - { - additionalSource = @$"{propMetadataInitFuncSource}{serializeFuncSource}"; - } + string objectInfoInitSource = $@"{JsonObjectInfoValuesTypeRef}<{genericArg}> {ObjectInfoVarName} = new {JsonObjectInfoValuesTypeRef}<{genericArg}>() + {{ + ObjectCreator = {creatorInvocation}, + ObjectWithParameterizedConstructorCreator = {parameterizedCreatorInvocation}, + PropertyMetadataInitializer = {propInitMethodName}, + ConstructorParameterMetadataInitializer = {ctorParamMetadataInitMethodName}, + NumberHandling = {GetNumberHandlingAsStr(typeMetadata.NumberHandling)}, + SerializeHandler = {serializeMethodName} + }}; + + _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeMetadata.TypeRef}>({OptionsInstanceVariableName}, {ObjectInfoVarName});"; + + string additionalSource = @$"{propMetadataInitFuncSource}{serializeFuncSource}{ctorParamMetadataInitFuncSource}"; return GenerateForType(typeMetadata, objectInfoInitSource, additionalSource); } - private string GeneratePropMetadataInitFunc( - bool declaringTypeIsValueType, - string propInitMethodName, - List properties) + private string GeneratePropMetadataInitFunc(TypeGenerationSpec typeGenerationSpec) { const string PropVarName = "properties"; const string JsonContextVarName = "jsonContext"; + List properties = typeGenerationSpec.PropertyGenSpecList!; + int propCount = properties.Count; - string propertyArrayInstantiationValue = properties == null + string propertyArrayInstantiationValue = propCount == 0 ? $"{ArrayTypeRef}.Empty<{JsonPropertyInfoTypeRef}>()" : $"new {JsonPropertyInfoTypeRef}[{propCount}]"; string contextTypeRef = _currentContext.ContextTypeRef; + string propInitMethodName = $"{typeGenerationSpec.TypeInfoPropertyName}{PropInitMethodNameSuffix}"; StringBuilder sb = new(); @@ -649,7 +661,7 @@ private string GeneratePropMetadataInitFunc( string setterNamedArg; if (memberMetadata.CanUseSetter) { - string propMutation = declaringTypeIsValueType + string propMutation = typeGenerationSpec.IsValueType ? @$"{UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value" : $@"(({declaringTypeCompilableName})obj).{clrPropertyName} = value"; @@ -697,10 +709,52 @@ private string GeneratePropMetadataInitFunc( return sb.ToString(); } - private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec, string serializeMethodName) + private string GenerateCtorParamMetadataInitFunc(TypeGenerationSpec typeGenerationSpec) + { + const string parametersVarName = "parameters"; + const string infoVarName = "info"; + + ParameterGenerationSpec[] parameters = typeGenerationSpec.CtorParamGenSpecArray; + int paramCount = parameters.Length; + Debug.Assert(paramCount > 0); + + StringBuilder sb = new($@" + +private static {JsonParameterInfoValuesTypeRef}[] {typeGenerationSpec.TypeInfoPropertyName}{CtorParamInitMethodNameSuffix}() +{{ + {JsonParameterInfoValuesTypeRef}[] {parametersVarName} = new {JsonParameterInfoValuesTypeRef}[{paramCount}]; + {JsonParameterInfoValuesTypeRef} info; +"); + + for (int i = 0; i < paramCount; i++) + { + ParameterInfo reflectionInfo = parameters[i].ParameterInfo; + + sb.Append(@$" + {infoVarName} = new() + {{ + Name = ""{reflectionInfo.Name!}"", + ParameterType = typeof({reflectionInfo.ParameterType.GetCompilableName()}), + Position = {reflectionInfo.Position}, + HasDefaultValue = {ToCSharpKeyword(reflectionInfo.HasDefaultValue)}, + DefaultValue = {GetParamDefaultValueAsString(reflectionInfo.DefaultValue)} + }}; + {parametersVarName}[{i}] = {infoVarName}; +"); + } + + sb.Append(@$" + return {parametersVarName}; +}}"); + + return sb.ToString(); + } + + private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec) { JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions; string typeRef = typeGenSpec.TypeRef; + string serializeMethodName = $"{typeGenSpec.TypeInfoPropertyName}{SerializeMethodNameSuffix}"; if (!typeGenSpec.TryFilterSerializableProps( options, @@ -842,6 +896,35 @@ private static bool ShouldIncludePropertyForFastPath(PropertyGenerationSpec prop return true; } + private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec typeGenerationSpec) + { + ParameterGenerationSpec[] parameters = typeGenerationSpec.CtorParamGenSpecArray; + int paramCount = parameters.Length; + Debug.Assert(paramCount != 0); + + if (paramCount > JsonConstants.MaxParameterCount) + { + return "null"; + } + + const string ArgsVarName = "args"; + int lastIndex = paramCount - 1; + + StringBuilder sb = new($"static ({ArgsVarName}) => new {typeGenerationSpec.TypeRef}("); + + for (int i = 0; i < lastIndex; i++) + { + sb.Append($"{GetParamUnboxing(parameters[i], i)}, "); + } + + sb.Append($"{GetParamUnboxing(parameters[lastIndex], lastIndex)})"); + + return sb.ToString(); + + static string GetParamUnboxing(ParameterGenerationSpec spec, int index) + => $"({spec.ParameterInfo.ParameterType.GetCompilableName()}){ArgsVarName}[{index}]"; + } + private string? GetWriterMethod(Type type) { string? method; @@ -1066,7 +1149,7 @@ private string GetGetTypeInfoImplementation() {{"); HashSet types = new(_currentContext.RootSerializableTypes); - types.UnionWith(_currentContext.NullableUnderlyingTypes); + types.UnionWith(_currentContext.ImplicitlyRegisteredTypes); // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64. foreach (TypeGenerationSpec metadata in types) @@ -1122,5 +1205,18 @@ private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) } private static string ToCSharpKeyword(bool value) => value.ToString().ToLowerInvariant(); + + private static string GetParamDefaultValueAsString(object? value) + { + switch (value) + { + case null: + return "null"; + case bool boolVal: + return ToCSharpKeyword(boolVal); + default: + return value!.ToString(); + } + } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 184a1774c29cd..b763eb5a1390c 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -29,7 +29,6 @@ private sealed class Parser private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute"; private const string JsonNumberHandlingAttributeFullName = "System.Text.Json.Serialization.JsonNumberHandlingAttribute"; private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute"; - private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute"; private readonly GeneratorExecutionContext _executionContext; @@ -53,6 +52,7 @@ private sealed class Parser private readonly Type? _ilistType; private readonly Type? _stackType; private readonly Type? _queueType; + private readonly Type? _keyValuePair; private readonly Type _booleanType; private readonly Type _charType; @@ -76,7 +76,7 @@ private sealed class Parser /// private readonly Dictionary _typeGenerationSpecCache = new(); - private readonly HashSet _nullableTypeGenerationSpecCache = new(); + private readonly HashSet _implicitlyRegisteredTypes = new(); private JsonKnownNamingPolicy _currentContextNamingPolicy; @@ -88,6 +88,14 @@ private sealed class Parser defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); + private static DiagnosticDescriptor MultipleJsonConstructorAttribute { get; } = new DiagnosticDescriptor( + id: "SYSLIB1033", + title: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + public Parser(in GeneratorExecutionContext executionContext) { _executionContext = executionContext; @@ -111,6 +119,7 @@ public Parser(in GeneratorExecutionContext executionContext) _ilistType = ResolveType(typeof(IList).FullName!); _stackType = ResolveType(typeof(Stack).FullName!); _queueType = ResolveType(typeof(Queue).FullName!); + _keyValuePair = ResolveType(typeof(KeyValuePair<,>).FullName!); _booleanType = ResolveType(SpecialType.System_Boolean); _charType = ResolveType(SpecialType.System_Char); @@ -217,14 +226,14 @@ public Parser(in GeneratorExecutionContext executionContext) continue; } - contextGenSpec.NullableUnderlyingTypes.UnionWith(_nullableTypeGenerationSpecCache); + contextGenSpec.ImplicitlyRegisteredTypes.UnionWith(_implicitlyRegisteredTypes); contextGenSpecList ??= new List(); contextGenSpecList.Add(contextGenSpec); // Clear the cache of generated metadata between the processing of context classes. _typeGenerationSpecCache.Clear(); - _nullableTypeGenerationSpecCache.Clear(); + _implicitlyRegisteredTypes.Clear(); } if (contextGenSpecList == null) @@ -530,6 +539,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener TypeGenerationSpec? nullableUnderlyingTypeGenSpec = null; List? propGenSpecList = null; ObjectConstructionStrategy constructionStrategy = default; + ParameterGenerationSpec[]? paramGenSpecArray = null; CollectionType collectionType = CollectionType.NotApplicable; JsonNumberHandling? numberHandling = null; bool foundDesignTimeCustomConverter = false; @@ -541,7 +551,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener foreach (CustomAttributeData attributeData in attributeDataList) { Type attributeType = attributeData.AttributeType; - if (attributeType.FullName == "System.Text.Json.Serialization.JsonNumberHandlingAttribute") + if (attributeType.FullName == JsonNumberHandlingAttributeFullName) { IList ctorArgs = attributeData.ConstructorArguments; numberHandling = (JsonNumberHandling)ctorArgs[0].Value; @@ -554,15 +564,6 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } } - if (type.Name.StartsWith("StackWrapper")) - { - } - - if (type.GetConstructor(Type.EmptyTypes) != null && !type.IsAbstract && !type.IsInterface) - { - constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; - } - if (foundDesignTimeCustomConverter) { classType = converterInstatiationLogic != null @@ -578,7 +579,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener Debug.Assert(nullableUnderlyingType != null); classType = ClassType.Nullable; nullableUnderlyingTypeGenSpec = GetOrAddTypeGenerationSpec(nullableUnderlyingType, generationMode); - _nullableTypeGenerationSpecCache.Add(nullableUnderlyingTypeGenSpec); + _implicitlyRegisteredTypes.Add(nullableUnderlyingTypeGenSpec); } else if (type.IsEnum) { @@ -586,6 +587,11 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } else if (_ienumerableType.IsAssignableFrom(type)) { + if ((type.GetConstructor(Type.EmptyTypes) != null || type.IsValueType) && !type.IsAbstract && !type.IsInterface) + { + constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; + } + Type actualTypeToConvert; if (type.IsArray) @@ -726,57 +732,97 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } else { - classType = ClassType.Object; - - // GetInterface() is currently not implemented, so we use GetInterfaces(). - IEnumerable interfaces = type.GetInterfaces().Select(interfaceType => interfaceType.FullName!); - implementsIJsonOnSerialized = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializedFullName) != null; - implementsIJsonOnSerializing = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializingFullName) != null; - - propGenSpecList = new List(); - Dictionary? ignoredMembers = null; + bool useDefaultCtorInAnnotatedStructs = !type.IsKeyValuePair(_keyValuePair); - const BindingFlags bindingFlags = - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic | - BindingFlags.DeclaredOnly; - - bool propertyOrderSpecified = false; - - for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) + if (!type.TryGetDeserializationConstructor(useDefaultCtorInAnnotatedStructs, out ConstructorInfo? constructor)) { - foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) + classType = ClassType.TypeUnsupportedBySourceGen; + _executionContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonConstructorAttribute, Location.None, new string[] { $"{type}" })); + } + else + { + classType = ClassType.Object; + + if ((constructor != null || type.IsValueType) && !type.IsAbstract) { - bool isVirtual = propertyInfo.IsVirtual(); + ParameterInfo[]? parameters = constructor?.GetParameters(); + int paramCount = parameters?.Length ?? 0; - if (propertyInfo.GetIndexParameters().Length > 0 || - PropertyIsOverridenAndIgnored(propertyInfo.Name, propertyInfo.PropertyType, isVirtual, ignoredMembers)) + if (paramCount == 0) { - continue; + constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; } + else + { + constructionStrategy = ObjectConstructionStrategy.ParameterizedConstructor; + paramGenSpecArray = new ParameterGenerationSpec[paramCount]; - PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); - CacheMember(spec, ref propGenSpecList, ref ignoredMembers); - propertyOrderSpecified |= spec.Order != 0; + for (int i = 0; i < paramCount; i++) + { + ParameterInfo parameterInfo = parameters[i]; + TypeGenerationSpec typeGenerationSpec = GetOrAddTypeGenerationSpec(parameterInfo.ParameterType, generationMode); + + paramGenSpecArray[i] = new ParameterGenerationSpec() + { + TypeGenerationSpec = typeGenerationSpec, + ParameterInfo = parameterInfo + }; + + _implicitlyRegisteredTypes.Add(typeGenerationSpec); + } + } } - foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) + // GetInterface() is currently not implemented, so we use GetInterfaces(). + IEnumerable interfaces = type.GetInterfaces().Select(interfaceType => interfaceType.FullName!); + implementsIJsonOnSerialized = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializedFullName) != null; + implementsIJsonOnSerializing = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializingFullName) != null; + + propGenSpecList = new List(); + Dictionary? ignoredMembers = null; + + const BindingFlags bindingFlags = + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly; + + bool propertyOrderSpecified = false; + + for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) { - if (PropertyIsOverridenAndIgnored(fieldInfo.Name, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers)) + foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) { - continue; + bool isVirtual = propertyInfo.IsVirtual(); + + if (propertyInfo.GetIndexParameters().Length > 0 || + PropertyIsOverridenAndIgnored(propertyInfo.Name, propertyInfo.PropertyType, isVirtual, ignoredMembers)) + { + continue; + } + + PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); + CacheMember(spec, ref propGenSpecList, ref ignoredMembers); + propertyOrderSpecified |= spec.Order != 0; } - PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); - CacheMember(spec, ref propGenSpecList, ref ignoredMembers); - propertyOrderSpecified |= spec.Order != 0; + foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) + { + if (PropertyIsOverridenAndIgnored(fieldInfo.Name, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers)) + { + continue; + } + + PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); + CacheMember(spec, ref propGenSpecList, ref ignoredMembers); + propertyOrderSpecified |= spec.Order != 0; + } } - } - if (propertyOrderSpecified) - { - propGenSpecList.Sort((p1, p2) => p1.Order.CompareTo(p2.Order)); + if (propertyOrderSpecified) + { + propGenSpecList.Sort((p1, p2) => p1.Order.CompareTo(p2.Order)); + } } } @@ -786,6 +832,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener classType, numberHandling, propGenSpecList, + paramGenSpecArray, collectionType, collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType, generationMode) : null, collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType, generationMode) : null, diff --git a/src/libraries/System.Text.Json/gen/ParameterGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ParameterGenerationSpec.cs new file mode 100644 index 0000000000000..00b8b840d507c --- /dev/null +++ b/src/libraries/System.Text.Json/gen/ParameterGenerationSpec.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace System.Text.Json.SourceGeneration +{ + internal class ParameterGenerationSpec + { + public TypeGenerationSpec TypeGenerationSpec { get; init; } + + public ParameterInfo ParameterInfo { get; init; } + } +} diff --git a/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs index 42d167a462207..51dcef2c0af48 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs @@ -21,7 +21,9 @@ public ConstructorInfoWrapper(IMethodSymbol ctor, MetadataLoadContextInternal me public override Type DeclaringType => _ctor.ContainingType.AsType(_metadataLoadContext); - public override MethodAttributes Attributes => throw new NotImplementedException(); + private MethodAttributes? _attributes; + + public override MethodAttributes Attributes => _attributes ??= _ctor.GetMethodAttributes(); public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); diff --git a/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs index 637483847e599..4b7ef69ce483c 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs @@ -23,46 +23,7 @@ public MethodInfoWrapper(IMethodSymbol method, MetadataLoadContextInternal metad private MethodAttributes? _attributes; - public override MethodAttributes Attributes - { - get - { - if (!_attributes.HasValue) - { - _attributes = default(MethodAttributes); - - if (_method.IsAbstract) - { - _attributes |= MethodAttributes.Abstract; - } - - if (_method.IsStatic) - { - _attributes |= MethodAttributes.Static; - } - - if (_method.IsVirtual || _method.IsOverride) - { - _attributes |= MethodAttributes.Virtual; - } - - switch (_method.DeclaredAccessibility) - { - case Accessibility.Public: - _attributes |= MethodAttributes.Public; - break; - case Accessibility.Private: - _attributes |= MethodAttributes.Private; - break; - case Accessibility.Internal: - _attributes |= MethodAttributes.Assembly; - break; - } - } - - return _attributes.Value; - } - } + public override MethodAttributes Attributes => _attributes ??= _method.GetMethodAttributes(); public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); diff --git a/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs index ad9091017728f..81c4cc828ae4e 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs @@ -23,6 +23,12 @@ public ParameterInfoWrapper(IParameterSymbol parameter, MetadataLoadContextInter public override string Name => _parameter.Name; + public override bool HasDefaultValue => _parameter.HasExplicitDefaultValue; + + public override object DefaultValue => HasDefaultValue ? _parameter.ExplicitDefaultValue : null; + + public override int Position => _parameter.Ordinal; + public override IList GetCustomAttributesData() { var attributes = new List(); diff --git a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs index 599cae49aebd5..101f3e48fbfb7 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs @@ -1,6 +1,7 @@ // 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.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -29,5 +30,20 @@ public static bool IsInitOnly(this MethodInfo method) MethodInfoWrapper methodInfoWrapper = (MethodInfoWrapper)method; return methodInfoWrapper.IsInitOnly; } + + private static bool HasJsonConstructorAttribute(ConstructorInfo constructorInfo) + { + IList attributeDataList = CustomAttributeData.GetCustomAttributes(constructorInfo); + + foreach (CustomAttributeData attributeData in attributeDataList) + { + if (attributeData.AttributeType.FullName == "System.Text.Json.Serialization.JsonConstructorAttribute") + { + return true; + } + } + + return false; + } } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs index 3dd1e925e2f19..4e2479784de13 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs @@ -30,5 +30,40 @@ public static IEnumerable BaseTypes(this INamedTypeSymbol type t = t.BaseType; } } + + public static MethodAttributes GetMethodAttributes(this IMethodSymbol methodSymbol) + { + MethodAttributes attributes = default(MethodAttributes); + + if (methodSymbol.IsAbstract) + { + attributes |= MethodAttributes.Abstract; + } + + if (methodSymbol.IsStatic) + { + attributes |= MethodAttributes.Static; + } + + if (methodSymbol.IsVirtual || methodSymbol.IsOverride) + { + attributes |= MethodAttributes.Virtual; + } + + switch (methodSymbol.DeclaredAccessibility) + { + case Accessibility.Public: + attributes |= MethodAttributes.Public; + break; + case Accessibility.Private: + attributes |= MethodAttributes.Private; + break; + case Accessibility.Internal: + attributes |= MethodAttributes.Assembly; + break; + } + + return attributes; + } } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs index db2c79a949069..964f129ae7af8 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs @@ -249,7 +249,13 @@ public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) foreach (IMethodSymbol c in _namedTypeSymbol.Constructors) { - if (c.DeclaredAccessibility == Accessibility.Public) + if (c.IsImplicitlyDeclared && IsValueType) + { + continue; + } + + if (((BindingFlags.Public & bindingAttr) != 0 && c.DeclaredAccessibility == Accessibility.Public) || + ((BindingFlags.NonPublic & bindingAttr) != 0 && c.DeclaredAccessibility != Accessibility.Public)) { ctors.Add(new ConstructorInfoWrapper(c, _metadataLoadContext)); } @@ -382,6 +388,12 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) { if (item is IPropertySymbol propertySymbol) { + // Skip auto-generated properties on records. + if (_typeSymbol.IsRecord && propertySymbol.DeclaringSyntaxReferences.Length == 0) + { + continue; + } + // Skip if: if ( // we want a static property and this is not static diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index d6d4a153ed2e3..5e60f5e377c5d 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -135,4 +135,10 @@ Derived 'JsonSerializerContext' types and all containing types must be partial. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + Type has multiple constructors annotated with JsonConstructorAttribute. + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index 94f13a8bc9acd..578dded931de8 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -22,6 +22,16 @@ Duplicitní název typu + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. Nevygenerovala se metadata serializace pro typ {0}. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index e23cf13d8953e..9a154a7b3c7d2 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -22,6 +22,16 @@ Doppelter Typname + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. Die Serialisierungsmetadaten für den Typ "{0}" wurden nicht generiert. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index 893e9374de74d..591795f67d7e7 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -22,6 +22,16 @@ Nombre de tipo duplicado. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. No generó metadatos de serialización para el tipo '{0}". diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index 1a364c168966f..0a46e3e37dc7f 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -22,6 +22,16 @@ Nom de type dupliqué. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. Les métadonnées de sérialisation pour le type « {0} » n’ont pas été générées. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index 7a700a27d8594..663222f90862a 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -22,6 +22,16 @@ Nome di tipo duplicato. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. Non sono stati generati metadati di serializzazione per il tipo '{0}'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index f05edfd844eaf..73b7ba0eda936 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -22,6 +22,16 @@ 重複した種類名。 + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. '{0}'型 のシリアル化メタデータを生成ませんでした。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index 308d672c998ef..a9c2c1ee7e337 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -22,6 +22,16 @@ 중복된 형식 이름입니다. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. '{0}' 형식에 대한 직렬화 메타데이터가 생성되지 않았습니다. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index 45865274732ab..97ee4a15ec077 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -22,6 +22,16 @@ Zduplikowana nazwa typu. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. Nie wygenerowano metadanych serializacji dla typu „{0}”. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index cb75ff91ce38c..40d8e88fa31b5 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -22,6 +22,16 @@ Nome de tipo duplicado. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. Não gerou metadados de serialização para o tipo '{0}'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index cfe41af9643dd..66b70494f1544 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -22,6 +22,16 @@ Дублирующееся имя типа. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. Метаданные сериализации для типа "{0}" не сформированы. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index 3cb4589cff743..0334244bca30a 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -22,6 +22,16 @@ Yinelenen tür adı. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. '{0}' türü için serileştirme meta verileri oluşturulmadı. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index bd0e02092b849..fde4989102089 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -22,6 +22,16 @@ 重复的类型名称。 + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. 未生成类型 '{0}' 的序列化元数据。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index 756866eb4f6e8..2928ee14c10c9 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -22,6 +22,16 @@ 重複類型名稱。 + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. + + + + Type has multiple constructors annotated with JsonConstructorAttribute. + Type has multiple constructors annotated with JsonConstructorAttribute. + + Did not generate serialization metadata for type '{0}'. 未產生類型 '{0}' 的序列化中繼資料。 diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj index 008a5b221895d..0bdc4da423a85 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 false @@ -28,7 +28,8 @@ - + + @@ -42,6 +43,7 @@ + diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index 9241d57c1d593..4c2eb61e27d66 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Reflection; using System.Text.Json.Reflection; using System.Text.Json.Serialization; @@ -45,6 +46,8 @@ internal class TypeGenerationSpec public List? PropertyGenSpecList { get; private set; } + public ParameterGenerationSpec[]? CtorParamGenSpecArray { get; private set; } + public CollectionType CollectionType { get; private set; } public TypeGenerationSpec? CollectionKeyTypeMetadata { get; private set; } @@ -96,6 +99,7 @@ public void Initialize( ClassType classType, JsonNumberHandling? numberHandling, List? propertyGenSpecList, + ParameterGenerationSpec[]? ctorParamGenSpecArray, CollectionType collectionType, TypeGenerationSpec? collectionKeyTypeMetadata, TypeGenerationSpec? collectionValueTypeMetadata, @@ -114,6 +118,7 @@ public void Initialize( CanBeNull = !IsValueType || nullableUnderlyingTypeMetadata != null; NumberHandling = numberHandling; PropertyGenSpecList = propertyGenSpecList; + CtorParamGenSpecArray = ctorParamGenSpecArray; CollectionType = collectionType; CollectionKeyTypeMetadata = collectionKeyTypeMetadata; CollectionValueTypeMetadata = collectionValueTypeMetadata; @@ -210,7 +215,9 @@ private bool FastPathIsSupported() { foreach (PropertyGenerationSpec property in PropertyGenSpecList) { - if (property.TypeGenerationSpec.Type.IsObjectType()) + if (property.TypeGenerationSpec.Type.IsObjectType() || + property.NumberHandling == JsonNumberHandling.AllowNamedFloatingPointLiterals || + property.NumberHandling == JsonNumberHandling.WriteAsString) { return false; } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 74e702221a1ab..97da0d7f7faa2 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -991,7 +991,7 @@ public static partial class JsonMetadataServices public static JsonTypeInfo CreateIReadOnlyDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, JsonTypeInfo keyInfo, JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.IReadOnlyDictionary where TKey : notnull { throw null; } public static JsonTypeInfo CreateISetInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.ISet { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateListInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.List { throw null; } - public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Func? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where T : notnull { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonObjectInfoValues objectInfo) where T : notnull { throw null; } public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, bool isPublic, bool isVirtual, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func? getter, System.Action? setter, System.Text.Json.Serialization.JsonIgnoreCondition? ignoreCondition, bool hasJsonInclude, System.Text.Json.Serialization.JsonNumberHandling? numberHandling, string propertyName, string? jsonPropertyName) { throw null; } public static JsonTypeInfo CreateQueueInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Queue { throw null; } public static JsonTypeInfo CreateStackInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Stack { throw null; } @@ -1000,6 +1000,23 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.JsonConverter GetEnumConverter(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; } public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.Serialization.Metadata.JsonTypeInfo underlyingTypeInfo) where T : struct { throw null; } } + public sealed partial class JsonObjectInfoValues + { + public System.Func? ConstructorParameterMetadataInitializer { get; init; } + public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get; init; } + public System.Func? ObjectCreator { get; init; } + public System.Func? ObjectWithParameterizedConstructorCreator { get; init; } + public System.Func? PropertyMetadataInitializer { get; init; } + public System.Action? SerializeHandler { get; init; } + } + public sealed class JsonParameterInfoValues + { + public object? DefaultValue { get { throw null; } init { } } + public bool HasDefaultValue { get { throw null; } init { } } + public string Name { get { throw null; } init { } } + public System.Type ParameterType { get { throw null; } init { } } + public int Position { get { throw null; } init { } } + } public abstract partial class JsonPropertyInfo { internal JsonPropertyInfo() { } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj index 5244502eb33a5..6ef8f2ad1f044 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj @@ -8,6 +8,7 @@ + diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 14f5e6b50a60c..434708f0c1ddd 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -465,13 +465,13 @@ Properties that start with '$' are not allowed on preserve mode, either escape the character or turn off preserve references by setting ReferenceHandler to null. - Members '{0}' and '{1}' on type '{2}' cannot both bind with parameter '{3}' in constructor '{4}' on deserialization. + Members '{0}' and '{1}' on type '{2}' cannot both bind with parameter '{3}' in the deserialization constructor. - Each parameter in constructor '{0}' on type '{1}' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive. + Each parameter in the deserialization constructor on type '{0}' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive. - The constructor '{0}' on type '{1}' may not have more than 64 parameters for deserialization. + The deserialization constructor on type '{0}' may not have more than 64 parameters for deserialization. Reference metadata is not honored when deserializing types using parameterized constructors. See type '{0}'. @@ -483,7 +483,7 @@ The unsupported member type is located on type '{0}'. - The extension data property '{0}' on type '{1}' cannot bind with a parameter in constructor '{2}'. + The extension data property '{0}' on type '{1}' cannot bind with a parameter in the deserialization constructor. Cannot allocate a buffer of size {0}. @@ -599,8 +599,8 @@ A custom converter for JsonObject is not allowed on an extension property. - - 'propInitFunc' and 'serializeFunc' cannot both be 'null'. + + Invalid configuration provided for 'propInitFunc', 'ctorParamInitFunc' and 'serializeFunc'. 'JsonSerializerContext' '{0}' did not provide property metadata for type '{1}'. @@ -617,4 +617,7 @@ F# discriminated union serialization is not supported. Consider authoring a custom converter for the type. - + + 'JsonSerializerContext' '{0}' did not provide constructor parameter metadata for type '{1}'. + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index ed4d4208c4332..fd16d56018607 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -31,6 +31,7 @@ System.Text.Json.Utf8JsonReader + @@ -103,6 +104,7 @@ System.Text.Json.Utf8JsonReader + @@ -118,6 +120,7 @@ System.Text.Json.Utf8JsonReader + @@ -228,6 +231,7 @@ System.Text.Json.Utf8JsonReader + @@ -291,16 +295,17 @@ System.Text.Json.Utf8JsonReader - + + + - diff --git a/src/libraries/System.Text.Json/src/System/TypeExtensions.cs b/src/libraries/System.Text.Json/src/System/ReflectionExtensions.cs similarity index 84% rename from src/libraries/System.Text.Json/src/System/TypeExtensions.cs rename to src/libraries/System.Text.Json/src/System/ReflectionExtensions.cs index 1e8352761bd45..006c9c4e0e3ab 100644 --- a/src/libraries/System.Text.Json/src/System/TypeExtensions.cs +++ b/src/libraries/System.Text.Json/src/System/ReflectionExtensions.cs @@ -1,11 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; -namespace System.Text.Json +namespace System.Text.Json.Reflection { - internal static class TypeExtensions + internal static partial class ReflectionExtensions { private static readonly Type s_nullableType = typeof(Nullable<>); @@ -38,5 +40,8 @@ public static bool IsAssignableFromInternal(this Type type, Type from) return type.IsAssignableFrom(from); } + + private static bool HasJsonConstructorAttribute(ConstructorInfo constructorInfo) + => constructorInfo.GetCustomAttribute() != null; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs index 6a9969e5d8f27..f004e8d5d8347 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs @@ -3,7 +3,7 @@ namespace System.Text.Json { - internal static class JsonConstants + internal static partial class JsonConstants { public const byte OpenBrace = (byte)'{'; public const byte CloseBrace = (byte)'}'; @@ -114,8 +114,5 @@ internal static class JsonConstants // The maximum number of parameters a constructor can have where it can be considered // for a path on deserialization where we don't box the constructor arguments. public const int UnboxedParameterCountThreshold = 4; - - // The maximum number of parameters a constructor can have where it can be supported. - public const int MaxParameterCount = 64; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs index 9bc8a95c97ada..88b93f95d1c48 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs @@ -19,9 +19,6 @@ internal sealed class JsonMetadataServicesConverter : JsonResumableConverter< private readonly ConverterStrategy _converterStrategy; - private readonly Type? _keyType; - private readonly Type? _elementType; - private JsonConverter? _converter; // A backing converter for when fast-path logic cannot be used. @@ -32,33 +29,39 @@ private JsonConverter Converter _converter ??= _converterCreator(); Debug.Assert(_converter != null); Debug.Assert(_converter.ConverterStrategy == _converterStrategy); - Debug.Assert(_converter.KeyType == _keyType); - Debug.Assert(_converter.ElementType == _elementType); return _converter; } } internal override ConverterStrategy ConverterStrategy => _converterStrategy; - internal override Type? KeyType => _keyType; + internal override Type? KeyType => Converter.KeyType; + + internal override Type? ElementType => Converter.ElementType; - internal override Type? ElementType => _elementType; + internal override bool ConstructorIsParameterized => Converter.ConstructorIsParameterized; - public JsonMetadataServicesConverter(Func> converterCreator, ConverterStrategy converterStrategy, Type? keyType, Type? elementType) + public JsonMetadataServicesConverter(Func> converterCreator, ConverterStrategy converterStrategy) { _converterCreator = converterCreator ?? throw new ArgumentNullException(nameof(converterCreator)); _converterStrategy = converterStrategy; - _keyType = keyType; - _elementType = elementType; } internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out T? value) { JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; - if (_converterStrategy == ConverterStrategy.Object && jsonTypeInfo.PropertyCache == null) + if (_converterStrategy == ConverterStrategy.Object) { - jsonTypeInfo.InitializePropCache(); + if (jsonTypeInfo.PropertyCache == null) + { + jsonTypeInfo.InitializePropCache(); + } + + if (jsonTypeInfo.ParameterCache == null && jsonTypeInfo.IsObjectWithParameterizedCtor) + { + jsonTypeInfo.InitializeParameterCache(); + } } return Converter.OnTryRead(ref reader, typeToConvert, options, ref state, out value); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs index aee6ff86db31f..e0fd1bac5059c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverterFactory.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Text.Json.Reflection; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Serialization.Converters @@ -32,11 +32,13 @@ public override bool CanConvert(Type typeToConvert) return true; } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "The ctor is marked RequiresUnreferencedCode.")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", Justification = "The ctor is marked RequiresUnreferencedCode.")] public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { - if (IsKeyValuePair(typeToConvert)) + if (typeToConvert.IsKeyValuePair()) { return CreateKeyValuePairConverter(typeToConvert, options); } @@ -44,7 +46,11 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer JsonConverter converter; Type converterType; - ConstructorInfo? constructor = GetDeserializationConstructor(typeToConvert); + if (!typeToConvert.TryGetDeserializationConstructor(_useDefaultConstructorInUnannotatedStructs, out ConstructorInfo? constructor)) + { + ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(typeToConvert); + } + ParameterInfo[]? parameters = constructor?.GetParameters(); if (constructor == null || typeToConvert.IsAbstract || parameters!.Length == 0) @@ -78,7 +84,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer } else { - converterType = typeof(LargeObjectWithParameterizedConstructorConverter<>).MakeGenericType(typeToConvert); + converterType = typeof(LargeObjectWithParameterizedConstructorConverterWithReflection<>).MakeGenericType(typeToConvert); } } @@ -93,18 +99,9 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer return converter; } - private bool IsKeyValuePair(Type typeToConvert) - { - if (!typeToConvert.IsGenericType) - return false; - - Type generic = typeToConvert.GetGenericTypeDefinition(); - return (generic == typeof(KeyValuePair<,>)); - } - private JsonConverter CreateKeyValuePairConverter(Type type, JsonSerializerOptions options) { - Debug.Assert(IsKeyValuePair(type)); + Debug.Assert(type.IsKeyValuePair()); Type keyType = type.GetGenericArguments()[0]; Type valueType = type.GetGenericArguments()[1]; @@ -120,62 +117,5 @@ private JsonConverter CreateKeyValuePairConverter(Type type, JsonSerializerOptio return converter; } - - private ConstructorInfo? GetDeserializationConstructor( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) - { - ConstructorInfo? ctorWithAttribute = null; - ConstructorInfo? publicParameterlessCtor = null; - ConstructorInfo? lonePublicCtor = null; - - ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); - - if (constructors.Length == 1) - { - lonePublicCtor = constructors[0]; - } - - foreach (ConstructorInfo constructor in constructors) - { - if (constructor.GetCustomAttribute() != null) - { - if (ctorWithAttribute != null) - { - ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(type); - } - - ctorWithAttribute = constructor; - } - else if (constructor.GetParameters().Length == 0) - { - publicParameterlessCtor = constructor; - } - } - - // For correctness, throw if multiple ctors have [JsonConstructor], even if one or more are non-public. - ConstructorInfo? dummyCtorWithAttribute = ctorWithAttribute; - - constructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); - foreach (ConstructorInfo constructor in constructors) - { - if (constructor.GetCustomAttribute() != null) - { - if (dummyCtorWithAttribute != null) - { - ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(type); - } - - dummyCtorWithAttribute = constructor; - } - } - - // Structs will use default constructor if attribute isn't used. - if (_useDefaultConstructorInUnannotatedStructs && type.IsValueType && ctorWithAttribute == null) - { - return null; - } - - return ctorWithAttribute ?? publicParameterlessCtor ?? lonePublicCtor; - } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.Reflection.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.Reflection.cs new file mode 100644 index 0000000000000..3d373952f32ed --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.Reflection.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization.Metadata; + +namespace System.Text.Json.Serialization.Converters +{ + /// + /// Implementation of JsonObjectConverter{T} that supports the deserialization + /// of JSON objects using parameterized constructors. + /// + internal sealed class LargeObjectWithParameterizedConstructorConverterWithReflection + : LargeObjectWithParameterizedConstructorConverter where T : notnull + { + [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] + public LargeObjectWithParameterizedConstructorConverterWithReflection() + { + } + + internal override bool RequiresDynamicMemberAccessors => true; + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "The ctor is marked RequiresUnreferencedCode.")] + internal override void Initialize(JsonSerializerOptions options, JsonTypeInfo? jsonTypeInfo = null) + { + Debug.Assert(jsonTypeInfo != null); + jsonTypeInfo.CreateObjectWithArgs = options.MemberAccessorStrategy.CreateParameterizedConstructor(ConstructorInfo!); + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs index 02c714900b0a3..3e25b10c13a23 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs @@ -12,9 +12,9 @@ namespace System.Text.Json.Serialization.Converters /// Implementation of JsonObjectConverter{T} that supports the deserialization /// of JSON objects using parameterized constructors. /// - internal sealed class LargeObjectWithParameterizedConstructorConverter : ObjectWithParameterizedConstructorConverter where T : notnull + internal class LargeObjectWithParameterizedConstructorConverter : ObjectWithParameterizedConstructorConverter where T : notnull { - protected override bool ReadAndCacheConstructorArgument(ref ReadStack state, ref Utf8JsonReader reader, JsonParameterInfo jsonParameterInfo) + protected sealed override bool ReadAndCacheConstructorArgument(ref ReadStack state, ref Utf8JsonReader reader, JsonParameterInfo jsonParameterInfo) { Debug.Assert(jsonParameterInfo.ShouldDeserialize); Debug.Assert(jsonParameterInfo.Options != null); @@ -23,23 +23,23 @@ protected override bool ReadAndCacheConstructorArgument(ref ReadStack state, ref if (success && !(arg == null && jsonParameterInfo.IgnoreDefaultValuesOnRead)) { - ((object[])state.Current.CtorArgumentState!.Arguments)[jsonParameterInfo.Position] = arg!; + ((object[])state.Current.CtorArgumentState!.Arguments)[jsonParameterInfo.ClrInfo.Position] = arg!; } return success; } - protected override object CreateObject(ref ReadStackFrame frame) + protected sealed override object CreateObject(ref ReadStackFrame frame) { object[] arguments = (object[])frame.CtorArgumentState!.Arguments; frame.CtorArgumentState.Arguments = null!; - var createObject = (JsonTypeInfo.ParameterizedConstructorDelegate?)frame.JsonTypeInfo.CreateObjectWithArgs; + var createObject = (Func?)frame.JsonTypeInfo.CreateObjectWithArgs; if (createObject == null) { // This means this constructor has more than 64 parameters. - ThrowHelper.ThrowNotSupportedException_ConstructorMaxOf64Parameters(ConstructorInfo!, TypeToConvert); + ThrowHelper.ThrowNotSupportedException_ConstructorMaxOf64Parameters(TypeToConvert); } object obj = createObject(arguments); @@ -48,26 +48,29 @@ protected override object CreateObject(ref ReadStackFrame frame) return obj; } - protected override void InitializeConstructorArgumentCaches(ref ReadStack state, JsonSerializerOptions options) + protected sealed override void InitializeConstructorArgumentCaches(ref ReadStack state, JsonSerializerOptions options) { JsonTypeInfo typeInfo = state.Current.JsonTypeInfo; - if (typeInfo.CreateObjectWithArgs == null) + // Ensure property cache has been initialized. + Debug.Assert(typeInfo.PropertyCache != null); + + if (typeInfo.ParameterCache == null) { - typeInfo.CreateObjectWithArgs = options.MemberAccessorStrategy.CreateParameterizedConstructor(ConstructorInfo!); + typeInfo.InitializePropCache(); } List> cache = typeInfo.ParameterCache!.List; - object[] arguments = ArrayPool.Shared.Rent(cache.Count); + object?[] arguments = ArrayPool.Shared.Rent(cache.Count); + for (int i = 0; i < typeInfo.ParameterCount; i++) { JsonParameterInfo? parameterInfo = cache[i].Value; Debug.Assert(parameterInfo != null); - if (parameterInfo.ShouldDeserialize) - { - arguments[parameterInfo.Position] = parameterInfo.DefaultValue!; - } + arguments[parameterInfo.ClrInfo.Position] = parameterInfo.ShouldDeserialize + ? parameterInfo.DefaultValue + : parameterInfo.ClrDefaultValue; } state.Current.CtorArgumentState!.Arguments = arguments; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs index e5f8c5ecefa22..fc16d5f4c4799 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Small.cs @@ -31,7 +31,7 @@ protected override bool ReadAndCacheConstructorArgument( bool success; - switch (jsonParameterInfo.Position) + switch (jsonParameterInfo.ClrInfo.Position) { case 0: success = TryRead(ref state, ref reader, jsonParameterInfo, out arguments.Arg0); @@ -92,9 +92,11 @@ protected override void InitializeConstructorArgumentCaches(ref ReadStack state, JsonParameterInfo? parameterInfo = cache[i].Value; Debug.Assert(parameterInfo != null); + // We can afford not to set default values for ctor arguments when we should't deserialize because the + // type parameters of the `Arguments` type provide default semantics that work well with value types. if (parameterInfo.ShouldDeserialize) { - int position = parameterInfo.Position; + int position = parameterInfo.ClrInfo.Position; switch (position) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs index 528ac02b9ef4d..f6420c6bf4de9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs @@ -19,6 +19,8 @@ namespace System.Text.Json.Serialization.Converters /// internal abstract partial class ObjectWithParameterizedConstructorConverter : ObjectDefaultConverter where T : notnull { + internal sealed override bool ConstructorIsParameterized => true; + internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value) { object obj; @@ -465,7 +467,7 @@ private void BeginRead(ref ReadStack state, ref Utf8JsonReader reader, JsonSeria if (state.Current.JsonTypeInfo.ParameterCount != state.Current.JsonTypeInfo.ParameterCache!.Count) { - ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(ConstructorInfo!, TypeToConvert); + ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(TypeToConvert); } // Set current JsonPropertyInfo to null to avoid conflicts on push. @@ -508,7 +510,5 @@ protected virtual bool TryLookupConstructorParameter( return jsonParameterInfo != null; } - - internal override bool ConstructorIsParameterized => true; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs index aec86617a8173..469205e4d99f6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Text.Json.Reflection; namespace System.Text.Json.Serialization.Converters { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index 27bcc4fb961c1..dc11589053386 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Text.Json.Reflection; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Converters; using System.Text.Json.Serialization.Metadata; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/GenericMethodHolder.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/GenericMethodHolder.cs index 8ccf2e5559934..a144c11d398c9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/GenericMethodHolder.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/GenericMethodHolder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Text.Json.Reflection; namespace System.Text.Json.Serialization.Metadata { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs index 2a4078f178e3d..39ad153d48da1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs @@ -1,6 +1,7 @@ // 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.Generic; using System.Text.Json.Serialization.Converters; namespace System.Text.Json.Serialization.Metadata diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 731869ec4a9bb..d9552c2ea1f8c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs @@ -1,8 +1,8 @@ // 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.Generic; using System.ComponentModel; -using System.Diagnostics; namespace System.Text.Json.Serialization.Metadata { @@ -104,20 +104,14 @@ public static JsonPropertyInfo CreatePropertyInfo( /// Creates metadata for a complex class or struct. /// /// The to initialize the metadata with. - /// Provides a mechanism to create an instance of the class or struct when deserializing. - /// Provides a mechanism to initialize metadata for properties and fields of the class or struct. - /// Provides a serialization implementation for instances of the class or struct which assumes options specified by . - /// Specifies how number properties and fields should be processed when serializing and deserializing. + /// Provides serialization metadata about an object type with constructors, properties, and fields. /// The type of the class or struct. - /// Thrown when and are both null. + /// Thrown when or is null. /// A instance representing the class or struct. - public static JsonTypeInfo CreateObjectInfo( - JsonSerializerOptions options, - Func? createObjectFunc, - Func? propInitFunc, - JsonNumberHandling numberHandling, - Action? serializeFunc) where T : notnull - => new JsonTypeInfoInternal(options, createObjectFunc, propInitFunc, numberHandling, serializeFunc); + public static JsonTypeInfo CreateObjectInfo(JsonSerializerOptions options, JsonObjectInfoValues objectInfo) where T : notnull + => new JsonTypeInfoInternal( + options ?? throw new ArgumentNullException(nameof(options)), + objectInfo ?? throw new ArgumentNullException(nameof(objectInfo))); /// /// Creates metadata for a primitive or a type with a custom converter. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonObjectInfoValues.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonObjectInfoValues.cs new file mode 100644 index 0000000000000..0dc8e47a97cd6 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonObjectInfoValues.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json.Serialization.Metadata +{ + /// + /// Provides serialization metadata about an object type with constructors, properties, and fields. + /// + /// The object type to serialize or deserialize. + public sealed class JsonObjectInfoValues + { + /// + /// Provides a mechanism to create an instance of the class or struct when deserializing, using a parameterless constructor. + /// + public Func? ObjectCreator { get; init; } + + /// + /// Provides a mechanism to create an instance of the class or struct when deserializing, using a parameterized constructor. + /// + public Func? ObjectWithParameterizedConstructorCreator { get; init; } + + /// + /// Provides a mechanism to initialize metadata for properties and fields of the class or struct. + /// + public Func? PropertyMetadataInitializer { get; init; } + + /// + /// Provides a mechanism to initialize metadata for a parameterized constructor of the class or struct to be used when deserializing. + /// + public Func? ConstructorParameterMetadataInitializer { get; init; } + + /// + /// Specifies how number properties and fields should be processed when serializing and deserializing. + /// + public JsonNumberHandling NumberHandling { get; init; } + + /// + /// Provides a serialization implementation for instances of the class or struct which assumes options specified by . + /// + public Action? SerializeHandler { get; init; } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs index e10bc2711b48a..38900c97f1870 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs @@ -12,11 +12,16 @@ namespace System.Text.Json.Serialization.Metadata /// internal abstract class JsonParameterInfo { + private JsonTypeInfo? _runtimeTypeInfo; public JsonConverter ConverterBase { get; private set; } = null!; + private protected bool MatchingPropertyCanBeNull { get; private set; } + + internal abstract object? ClrDefaultValue { get; } + // The default value of the parameter. This is `DefaultValue` of the `ParameterInfo`, if specified, or the CLR `default` for the `ParameterType`. - public object? DefaultValue { get; protected set; } + public object? DefaultValue { get; private protected set; } public bool IgnoreDefaultValuesOnRead { get; private set; } @@ -28,10 +33,9 @@ internal abstract class JsonParameterInfo public JsonNumberHandling? NumberHandling { get; private set; } - // The zero-based position of the parameter in the formal parameter list. - public int Position { get; private set; } + // Using a field to avoid copy semantics. + public JsonParameterInfoValues ClrInfo = null!; - private JsonTypeInfo? _runtimeTypeInfo; public JsonTypeInfo RuntimeTypeInfo { get @@ -45,37 +49,44 @@ public JsonTypeInfo RuntimeTypeInfo return _runtimeTypeInfo; } + set + { + // Used by JsonMetadataServices. + Debug.Assert(_runtimeTypeInfo == null); + _runtimeTypeInfo = value; + } } - internal Type RuntimePropertyType { get; set; } = null!; + public Type RuntimePropertyType { get; set; } = null!; public bool ShouldDeserialize { get; private set; } - public virtual void Initialize( - Type runtimePropertyType, - ParameterInfo parameterInfo, - JsonPropertyInfo matchingProperty, - JsonSerializerOptions options) + public virtual void Initialize(JsonParameterInfoValues parameterInfo, JsonPropertyInfo matchingProperty, JsonSerializerOptions options) { - RuntimePropertyType = runtimePropertyType; - Position = parameterInfo.Position; - NameAsUtf8Bytes = matchingProperty.NameAsUtf8Bytes!; + ClrInfo = parameterInfo; Options = options; ShouldDeserialize = true; + + RuntimePropertyType = matchingProperty.RuntimePropertyType!; + NameAsUtf8Bytes = matchingProperty.NameAsUtf8Bytes!; ConverterBase = matchingProperty.ConverterBase; IgnoreDefaultValuesOnRead = matchingProperty.IgnoreDefaultValuesOnRead; NumberHandling = matchingProperty.NumberHandling; + MatchingPropertyCanBeNull = matchingProperty.PropertyTypeCanBeNull; } // Create a parameter that is ignored at run-time. It uses the same type (typeof(sbyte)) to help // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. - public static JsonParameterInfo CreateIgnoredParameterPlaceholder(JsonPropertyInfo matchingProperty) + public static JsonParameterInfo CreateIgnoredParameterPlaceholder(JsonParameterInfoValues parameterInfo, JsonPropertyInfo matchingProperty) { - return new JsonParameterInfo - { - RuntimePropertyType = typeof(sbyte), - NameAsUtf8Bytes = matchingProperty.NameAsUtf8Bytes!, - }; + JsonParameterInfo jsonParameterInfo = matchingProperty.ConverterBase.CreateJsonParameterInfo(); + jsonParameterInfo.ClrInfo = parameterInfo; + jsonParameterInfo.RuntimePropertyType = matchingProperty.RuntimePropertyType!; + jsonParameterInfo.NameAsUtf8Bytes = matchingProperty.NameAsUtf8Bytes!; + jsonParameterInfo.InitializeDefaultValue(matchingProperty); + return jsonParameterInfo; } + + protected abstract void InitializeDefaultValue(JsonPropertyInfo matchingProperty); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoOfT.cs index d603d7e3e6e02..2195426a551ed 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoOfT.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Reflection; namespace System.Text.Json.Serialization.Metadata { @@ -12,32 +11,32 @@ namespace System.Text.Json.Serialization.Metadata /// internal sealed class JsonParameterInfo : JsonParameterInfo { + internal override object? ClrDefaultValue => default(T); + public T TypedDefaultValue { get; private set; } = default!; - public override void Initialize( - Type runtimePropertyType, - ParameterInfo parameterInfo, - JsonPropertyInfo matchingProperty, - JsonSerializerOptions options) + public override void Initialize(JsonParameterInfoValues parameterInfo, JsonPropertyInfo matchingProperty, JsonSerializerOptions options) { - base.Initialize( - runtimePropertyType, - parameterInfo, - matchingProperty, - options); + base.Initialize(parameterInfo, matchingProperty, options); + InitializeDefaultValue(matchingProperty); + } - Debug.Assert(parameterInfo.ParameterType == matchingProperty.DeclaredPropertyType); + protected override void InitializeDefaultValue(JsonPropertyInfo matchingProperty) + { + Debug.Assert(ClrInfo.ParameterType == matchingProperty.DeclaredPropertyType); - if (parameterInfo.HasDefaultValue) + if (ClrInfo.HasDefaultValue) { - if (parameterInfo.DefaultValue == null && !matchingProperty.PropertyTypeCanBeNull) + object? defaultValue = ClrInfo.DefaultValue; + + if (defaultValue == null && !matchingProperty.PropertyTypeCanBeNull) { DefaultValue = TypedDefaultValue; } else { - DefaultValue = parameterInfo.DefaultValue; - TypedDefaultValue = (T)parameterInfo.DefaultValue!; + DefaultValue = defaultValue; + TypedDefaultValue = (T)defaultValue!; } } else diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoValues.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoValues.cs new file mode 100644 index 0000000000000..dfcfcc6180e07 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfoValues.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json.Serialization.Metadata +{ + /// + /// Provides information about a constructor parameter required for JSON deserialization. + /// + public sealed class JsonParameterInfoValues + { + /// + /// The name of the parameter. + /// + public string Name { get; init; } = null!; + + /// + /// The type of the parameter. + /// + public Type ParameterType { get; init; } = null!; + + /// + /// The zero-based position of the parameter in the formal parameter list. + /// + public int Position { get; init; } + + /// + /// Whether a default value was specified for the parameter. + /// + public bool HasDefaultValue { get; init; } + + /// + /// The default value of the parameter. + /// + public object? DefaultValue { get; init; } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 9e34cc60a3561..b2962004c9898 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -40,13 +40,18 @@ internal static JsonPropertyInfo GetPropertyPlaceholder() return info; } - // Create a property that is ignored at run-time. It uses the same type (typeof(sbyte)) to help - // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. - internal static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(MemberInfo memberInfo, Type memberType, bool isVirtual, JsonSerializerOptions options) + // Create a property that is ignored at run-time. + internal static JsonPropertyInfo CreateIgnoredPropertyPlaceholder( + JsonConverter converter, + MemberInfo memberInfo, + Type memberType, + bool isVirtual, + JsonSerializerOptions options) { - JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfo(); + JsonPropertyInfo jsonPropertyInfo = converter.CreateJsonPropertyInfo(); jsonPropertyInfo.Options = options; + jsonPropertyInfo.ConverterBase = converter; jsonPropertyInfo.MemberInfo = memberInfo; jsonPropertyInfo.IsIgnored = true; jsonPropertyInfo.DeclaredPropertyType = memberType; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index 28d244109f0d4..c7a3585c5fc6c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Text.Json.Reflection; namespace System.Text.Json.Serialization.Metadata { @@ -156,6 +157,8 @@ internal void InitializeForSourceGen( SrcGen_IsPublic = isPublic; SrcGen_HasJsonInclude = hasJsonInclude; + DeclaredPropertyType = typeof(T); + ConverterBase = converter; if (ignoreCondition == JsonIgnoreCondition.Always) { @@ -169,9 +172,7 @@ internal void InitializeForSourceGen( Set = setter; HasGetter = Get != null; HasSetter = Set != null; - ConverterBase = converter; RuntimeTypeInfo = typeInfo; - DeclaredPropertyType = typeof(T); DeclaringType = declaringType; IgnoreCondition = ignoreCondition; MemberType = isProperty ? MemberTypes.Property : MemberTypes.Field; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs index cb2545c0cc10b..00911d756bdef 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs @@ -48,6 +48,8 @@ public partial class JsonTypeInfo internal Func? PropInitFunc; + internal Func? CtorParamInitFunc; + internal static JsonPropertyInfo AddProperty( MemberInfo memberInfo, Type memberType, @@ -56,12 +58,6 @@ internal static JsonPropertyInfo AddProperty( JsonNumberHandling? parentTypeNumberHandling, JsonSerializerOptions options) { - JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute(memberInfo)?.Condition; - if (ignoreCondition == JsonIgnoreCondition.Always) - { - return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(memberInfo, memberType, isVirtual, options); - } - JsonConverter converter = GetConverter( memberType, parentClassType, @@ -69,6 +65,12 @@ internal static JsonPropertyInfo AddProperty( out Type runtimeType, options); + JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute(memberInfo)?.Condition; + if (ignoreCondition == JsonIgnoreCondition.Always) + { + return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(converter, memberInfo, memberType, isVirtual, options); + } + return CreateProperty( declaredPropertyType: memberType, runtimePropertyType: runtimeType, @@ -575,18 +577,19 @@ internal void UpdateSortedParameterCache(ref ReadStackFrame frame) internal void InitializePropCache() { + Debug.Assert(PropertyCache == null); Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object); JsonSerializerContext? context = Options._context; Debug.Assert(context != null); - if (PropInitFunc == null) + JsonPropertyInfo[] array; + if (PropInitFunc == null || (array = PropInitFunc(context)) == null) { ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(context, Type); return; } - JsonPropertyInfo[] array = PropInitFunc(context); Dictionary? ignoredMembers = null; JsonPropertyDictionary propertyCache = new(Options.PropertyNameCaseInsensitive, array.Length); @@ -616,5 +619,25 @@ internal void InitializePropCache() // Avoid threading issues by populating a local cache and assigning it to the global cache after completion. PropertyCache = propertyCache; } + + internal void InitializeParameterCache() + { + Debug.Assert(ParameterCache == null); + Debug.Assert(PropertyCache != null); + Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object); + + JsonSerializerContext? context = Options._context; + Debug.Assert(context != null); + + JsonParameterInfoValues[] array; + if (CtorParamInitFunc == null || (array = CtorParamInitFunc()) == null) + { + ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeCtorParams(context, Type); + return; + } + + InitializeConstructorParameters(array); + Debug.Assert(ParameterCache != null); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index a88cbd39c646b..758e90835a1b5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Runtime.CompilerServices; using System.Text.Json.Reflection; namespace System.Text.Json.Serialization.Metadata @@ -20,8 +19,6 @@ public partial class JsonTypeInfo internal delegate object? ConstructorDelegate(); - internal delegate T ParameterizedConstructorDelegate(object[] arguments); - internal delegate T ParameterizedConstructorDelegate(TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3); internal ConstructorDelegate? CreateObject { get; set; } @@ -119,6 +116,8 @@ internal JsonTypeInfo? KeyTypeInfo /// internal JsonPropertyInfo PropertyInfoForTypeInfo { get; set; } + internal bool IsObjectWithParameterizedCtor => PropertyInfoForTypeInfo.ConverterBase.ConstructorIsParameterized; + private GenericMethodHolder? _genericMethods; /// @@ -296,7 +295,16 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json if (converter.ConstructorIsParameterized) { - InitializeConstructorParameters(converter.ConstructorInfo!); + // Create dynamic accessor to parameterized ctor. + // Only applies to the converter that handles "large ctors", + // since it is the only one shared with the source-gen code-paths. + converter.Initialize(Options, this); + + ParameterInfo[] parameters = converter.ConstructorInfo!.GetParameters(); + int parameterCount = parameters.Length; + + JsonParameterInfoValues[] jsonParameters = GetParameterInfoArray(parameters); + InitializeConstructorParameters(jsonParameters); } } break; @@ -440,19 +448,9 @@ public ParameterLookupValue(JsonPropertyInfo jsonPropertyInfo) public JsonPropertyInfo JsonPropertyInfo { get; } } - private void InitializeConstructorParameters(ConstructorInfo constructorInfo) + private void InitializeConstructorParameters(JsonParameterInfoValues[] jsonParameters) { - ParameterInfo[] parameters = constructorInfo.GetParameters(); - var parameterCache = new JsonPropertyDictionary(Options.PropertyNameCaseInsensitive, parameters.Length); - - static Type GetMemberType(MemberInfo memberInfo) - { - Debug.Assert(memberInfo is PropertyInfo || memberInfo is FieldInfo); - - return memberInfo is PropertyInfo propertyInfo - ? propertyInfo.PropertyType - : Unsafe.As(memberInfo).FieldType; - } + var parameterCache = new JsonPropertyDictionary(Options.PropertyNameCaseInsensitive, jsonParameters.Length); // Cache the lookup from object property name to JsonPropertyInfo using a case-insensitive comparer. // Case-insensitive is used to support both camel-cased parameter names and exact matches when C# @@ -464,21 +462,23 @@ static Type GetMemberType(MemberInfo memberInfo) foreach (KeyValuePair kvp in PropertyCache.List) { JsonPropertyInfo jsonProperty = kvp.Value!; - string propertyName = jsonProperty.MemberInfo!.Name; - var key = new ParameterLookupKey(propertyName, GetMemberType(jsonProperty.MemberInfo)); - var value = new ParameterLookupValue(jsonProperty); + string propertyName = jsonProperty.ClrName!; + + ParameterLookupKey key = new(propertyName, jsonProperty.DeclaredPropertyType); + ParameterLookupValue value = new(jsonProperty); + if (!JsonHelpers.TryAdd(nameLookup, key, value)) { // More than one property has the same case-insensitive name and Type. // Remember so we can throw a nice exception if this property is used as a parameter name. ParameterLookupValue existing = nameLookup[key]; - existing!.DuplicateName = propertyName; + existing.DuplicateName = propertyName; } } - foreach (ParameterInfo parameterInfo in parameters) + foreach (JsonParameterInfoValues parameterInfo in jsonParameters) { - var paramToCheck = new ParameterLookupKey(parameterInfo.Name!, parameterInfo.ParameterType); + ParameterLookupKey paramToCheck = new(parameterInfo.Name, parameterInfo.ParameterType); if (nameLookup.TryGetValue(paramToCheck, out ParameterLookupValue? matchingEntry)) { @@ -489,25 +489,48 @@ static Type GetMemberType(MemberInfo memberInfo) Type, parameterInfo.Name!, matchingEntry.JsonPropertyInfo.NameAsString, - matchingEntry.DuplicateName, - constructorInfo); + matchingEntry.DuplicateName); } Debug.Assert(matchingEntry.JsonPropertyInfo != null); JsonPropertyInfo jsonPropertyInfo = matchingEntry.JsonPropertyInfo; - JsonParameterInfo jsonParameterInfo = AddConstructorParameter(parameterInfo, jsonPropertyInfo, Options); + JsonParameterInfo jsonParameterInfo = CreateConstructorParameter(parameterInfo, jsonPropertyInfo, Options); parameterCache.Add(jsonPropertyInfo.NameAsString, jsonParameterInfo); } // It is invalid for the extension data property to bind with a constructor argument. else if (DataExtensionProperty != null && StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, DataExtensionProperty.NameAsString)) { - ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.MemberInfo!, Type, constructorInfo); + ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.MemberInfo!, Type); } } ParameterCache = parameterCache; - ParameterCount = parameters.Length; + ParameterCount = jsonParameters.Length; + } + + private static JsonParameterInfoValues[] GetParameterInfoArray(ParameterInfo[] parameters) + { + int parameterCount = parameters.Length; + JsonParameterInfoValues[] jsonParameters = new JsonParameterInfoValues[parameterCount]; + + for (int i = 0; i < parameterCount; i++) + { + ParameterInfo reflectionInfo = parameters[i]; + + JsonParameterInfoValues jsonInfo = new() + { + Name = reflectionInfo.Name!, + ParameterType = reflectionInfo.ParameterType, + Position = reflectionInfo.Position, + HasDefaultValue = reflectionInfo.HasDefaultValue, + DefaultValue = reflectionInfo.DefaultValue + }; + + jsonParameters[i] = jsonInfo; + } + + return jsonParameters; } private static bool PropertyIsOverridenAndIgnored( @@ -546,24 +569,20 @@ private void ValidateAndAssignDataExtensionProperty(JsonPropertyInfo jsonPropert DataExtensionProperty = jsonPropertyInfo; } - private static JsonParameterInfo AddConstructorParameter( - ParameterInfo parameterInfo, + private static JsonParameterInfo CreateConstructorParameter( + JsonParameterInfoValues parameterInfo, JsonPropertyInfo jsonPropertyInfo, JsonSerializerOptions options) { if (jsonPropertyInfo.IsIgnored) { - return JsonParameterInfo.CreateIgnoredParameterPlaceholder(jsonPropertyInfo); + return JsonParameterInfo.CreateIgnoredParameterPlaceholder(parameterInfo, jsonPropertyInfo); } JsonConverter converter = jsonPropertyInfo.ConverterBase; JsonParameterInfo jsonParameterInfo = converter.CreateJsonParameterInfo(); - jsonParameterInfo.Initialize( - jsonPropertyInfo.RuntimePropertyType!, - parameterInfo, - jsonPropertyInfo, - options); + jsonParameterInfo.Initialize(parameterInfo, jsonPropertyInfo, options); return jsonParameterInfo; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs index 6b59b13b97344..e7e5dc7067bf5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs @@ -1,6 +1,7 @@ // 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.Generic; using System.Text.Json.Serialization.Converters; namespace System.Text.Json.Serialization.Metadata @@ -22,30 +23,32 @@ public JsonTypeInfoInternal(JsonSerializerOptions options, ConverterStrategy con /// /// Creates serialization metadata for an object. /// - public JsonTypeInfoInternal( - JsonSerializerOptions options, - Func? createObjectFunc, - Func? propInitFunc, - JsonNumberHandling numberHandling, - Action? serializeFunc - ) : base(typeof(T), options, ConverterStrategy.Object) + public JsonTypeInfoInternal(JsonSerializerOptions options, JsonObjectInfoValues objectInfo) : base(typeof(T), options, ConverterStrategy.Object) { - if (propInitFunc == null && serializeFunc == null) - { - ThrowHelper.ThrowInvalidOperationException_PropInitAndSerializeFuncsNull(); - } - #pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. // Nullability of type argument doesn't match 'notnull' constraint. - JsonConverter converter = new JsonMetadataServicesConverter(() => new ObjectDefaultConverter(), ConverterStrategy.Object, keyType: null, elementType: null); + JsonConverter converter; + + if (objectInfo.ObjectWithParameterizedConstructorCreator != null) + { + converter = new JsonMetadataServicesConverter( + () => new LargeObjectWithParameterizedConstructorConverter(), + ConverterStrategy.Object); + CreateObjectWithArgs = objectInfo.ObjectWithParameterizedConstructorCreator; + CtorParamInitFunc = objectInfo.ConstructorParameterMetadataInitializer; + } + else + { + converter = new JsonMetadataServicesConverter(() => new ObjectDefaultConverter(), ConverterStrategy.Object); + SetCreateObjectFunc(objectInfo.ObjectCreator); + } #pragma warning restore CS8714 + PropInitFunc = objectInfo.PropertyMetadataInitializer; + Serialize = objectInfo.SerializeHandler; PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options); - NumberHandling = numberHandling; - PropInitFunc = propInitFunc; - Serialize = serializeFunc; - SetCreateObjectFunc(createObjectFunc); + NumberHandling = objectInfo.NumberHandling; } /// @@ -63,7 +66,7 @@ public JsonTypeInfoInternal( object? addFunc = null) : base(typeof(T), options, ConverterStrategy.Enumerable) { - JsonConverter converter = new JsonMetadataServicesConverter(converterCreator, ConverterStrategy.Enumerable, keyType: null, elementType); + JsonConverter converter = new JsonMetadataServicesConverter(converterCreator, ConverterStrategy.Enumerable); ElementType = converter.ElementType; ElementTypeInfo = elementInfo ?? throw new ArgumentNullException(nameof(elementInfo)); @@ -91,7 +94,7 @@ public JsonTypeInfoInternal( object? createObjectWithArgs = null) : base(typeof(T), options, ConverterStrategy.Dictionary) { - JsonConverter converter = new JsonMetadataServicesConverter(converterCreator, ConverterStrategy.Dictionary, keyType, elementType); + JsonConverter converter = new JsonMetadataServicesConverter(converterCreator, ConverterStrategy.Dictionary); KeyType = converter.KeyType; ElementType = converter.ElementType; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/MemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/MemberAccessor.cs index d246b45bf9a00..f01fc5d749333 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/MemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/MemberAccessor.cs @@ -12,7 +12,7 @@ internal abstract class MemberAccessor public abstract JsonTypeInfo.ConstructorDelegate? CreateConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type classType); - public abstract JsonTypeInfo.ParameterizedConstructorDelegate? CreateParameterizedConstructor(ConstructorInfo constructor); + public abstract Func? CreateParameterizedConstructor(ConstructorInfo constructor); public abstract JsonTypeInfo.ParameterizedConstructorDelegate? CreateParameterizedConstructor(ConstructorInfo constructor); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs index 2276b025979c4..d2ce031323a9a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs @@ -56,8 +56,8 @@ internal sealed class ReflectionEmitMemberAccessor : MemberAccessor return (JsonTypeInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonTypeInfo.ConstructorDelegate)); } - public override JsonTypeInfo.ParameterizedConstructorDelegate? CreateParameterizedConstructor(ConstructorInfo constructor) => - CreateDelegate>(CreateParameterizedConstructor(constructor)); + public override Func? CreateParameterizedConstructor(ConstructorInfo constructor) => + CreateDelegate>(CreateParameterizedConstructor(constructor)); private static DynamicMethod? CreateParameterizedConstructor(ConstructorInfo constructor) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionMemberAccessor.cs index 0352277260b17..99ce592b9fd79 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionMemberAccessor.cs @@ -41,7 +41,7 @@ public ConstructorContext([DynamicallyAccessedMembers(DynamicallyAccessedMemberT return new ConstructorContext(type).CreateInstance; } - public override JsonTypeInfo.ParameterizedConstructorDelegate? CreateParameterizedConstructor(ConstructorInfo constructor) + public override Func? CreateParameterizedConstructor(ConstructorInfo constructor) { Type type = typeof(T); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs index c78646f45f4b1..14eebe40dd368 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs @@ -337,7 +337,7 @@ static void AppendPropertyName(StringBuilder sb, string? propertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetConstructorArgumentState() { - if (Current.JsonTypeInfo.ParameterCount > 0) + if (Current.JsonTypeInfo.IsObjectWithParameterizedCtor) { // A zero index indicates a new stack frame. if (Current.CtorArgumentStateIndex == 0) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 7a3984b2b7a4d..0ed88d837a437 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -36,9 +36,9 @@ public static void ThrowNotSupportedException_TypeRequiresAsyncSerialization(Typ [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowNotSupportedException_ConstructorMaxOf64Parameters(ConstructorInfo constructorInfo, Type type) + public static void ThrowNotSupportedException_ConstructorMaxOf64Parameters(Type type) { - throw new NotSupportedException(SR.Format(SR.ConstructorMaxOf64Parameters, constructorInfo, type)); + throw new NotSupportedException(SR.Format(SR.ConstructorMaxOf64Parameters, type)); } [DoesNotReturn] @@ -195,8 +195,7 @@ public static void ThrowInvalidOperationException_MultiplePropertiesBindToConstr Type parentType, string parameterName, string firstMatchName, - string secondMatchName, - ConstructorInfo constructorInfo) + string secondMatchName) { throw new InvalidOperationException( SR.Format( @@ -204,25 +203,21 @@ public static void ThrowInvalidOperationException_MultiplePropertiesBindToConstr firstMatchName, secondMatchName, parentType, - parameterName, - constructorInfo)); + parameterName)); } [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(ConstructorInfo constructorInfo, Type parentType) + public static void ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(Type parentType) { - throw new InvalidOperationException(SR.Format(SR.ConstructorParamIncompleteBinding, constructorInfo, parentType)); + throw new InvalidOperationException(SR.Format(SR.ConstructorParamIncompleteBinding, parentType)); } [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam( - MemberInfo memberInfo, - Type classType, - ConstructorInfo constructorInfo) + public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(MemberInfo memberInfo, Type classType) { - throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, memberInfo, classType, constructorInfo)); + throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, memberInfo, classType)); } [DoesNotReturn] @@ -689,9 +684,9 @@ public static void ThrowInvalidOperationException_NoMetadataForType(Type type) [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_PropInitAndSerializeFuncsNull() + public static void ThrowInvalidOperationException_MetadatInitFuncsNull() { - throw new InvalidOperationException(SR.Format(SR.PropInitAndSerializeFuncsNull)); + throw new InvalidOperationException(SR.Format(SR.MetadataInitFuncsNull)); } public static void ThrowInvalidOperationException_NoMetadataForTypeProperties(JsonSerializerContext context, Type type) @@ -699,6 +694,11 @@ public static void ThrowInvalidOperationException_NoMetadataForTypeProperties(Js throw new InvalidOperationException(SR.Format(SR.NoMetadataForTypeProperties, context.GetType(), type)); } + public static void ThrowInvalidOperationException_NoMetadataForTypeCtorParams(JsonSerializerContext context, Type type) + { + throw new InvalidOperationException(SR.Format(SR.NoMetadataForTypeCtorParams, context.GetType(), type)); + } + public static void ThrowInvalidOperationException_NoDefaultOptionsForContext(JsonSerializerContext context, Type type) { throw new InvalidOperationException(SR.Format(SR.NoDefaultOptionsForContext, context.GetType(), type)); diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs index 61126269362cb..f05a8711601aa 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.cs @@ -1000,7 +1000,7 @@ public async Task ObjectToStringFail() { // Baseline string json = @"{""MyDictionary"":{""Key"":""Value""}}"; - JsonSerializer.Deserialize>(json); + await JsonSerializerWrapperForString.DeserializeWrapper>(json); await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(json)); } diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs index 9b7900c692f0c..c1b2fbe0b642c 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs @@ -333,7 +333,7 @@ public async Task ReadGenericICollectionOfGenericICollection() } GenericICollectionWrapper> result2 = - JsonSerializer.Deserialize>>(@"[[""1"",""2""],[""3"",""4""]]"); + await JsonSerializerWrapperForString.DeserializeWrapper>>(@"[[""1"",""2""],[""3"",""4""]]"); expected = 1; foreach (GenericICollectionWrapper ic in result2) diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs index 2fec3ce973839..d15b49359c5ad 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.KeyValuePair.cs @@ -8,20 +8,19 @@ namespace System.Text.Json.Serialization.Tests { -#if !BUILDING_SOURCE_GENERATOR_TESTS public abstract partial class CollectionTests { [Fact] - public async Task ReadSimpleKeyValuePairFail() + public virtual async Task ReadSimpleKeyValuePairPartialData() { // Invalid form: no Value - await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": 123}")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": ""123""}")); // Invalid form: extra property await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": ""Key"", ""Value"": 123, ""Value2"": 456}")); // Invalid form: does not contain both Key and Value properties - await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": ""Key"", ""Val"": 123")); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": ""Key"", ""Val"": 123}")); } [Fact] @@ -302,7 +301,7 @@ public async Task HonorNamingPolicy() } [Fact] - public async Task HonorNamingPolicy_CaseInsensitive() + public virtual async Task HonorNamingPolicy_CaseInsensitive() { const string json = @"{""key"":""Hello, World!"",""value"":1}"; @@ -321,7 +320,7 @@ public async Task HonorNamingPolicy_CaseInsensitive() } [Fact] - public async Task HonorCLRProperties() + public virtual async Task HonorCLRProperties() { var options = new JsonSerializerOptions { @@ -352,7 +351,7 @@ public async Task HonorCLRProperties() await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(json, options)); } - private class LeadingUnderscorePolicy : JsonNamingPolicy + public class LeadingUnderscorePolicy : JsonNamingPolicy { public override string ConvertName(string name) => "_" + name; } @@ -397,19 +396,19 @@ public async Task InvalidPropertyNameFail(Type policyType, string offendingPrope PropertyNamingPolicy = (JsonNamingPolicy)Activator.CreateInstance(policyType) }; - InvalidOperationException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>("", options)); + InvalidOperationException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>("{}", options)); string exAsStr = ex.ToString(); Assert.Contains(offendingProperty, exAsStr); await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(new KeyValuePair("", ""), options)); } - private class KeyNameNullPolicy : JsonNamingPolicy + public class KeyNameNullPolicy : JsonNamingPolicy { public override string ConvertName(string name) => name == "Key" ? null : name; } - private class ValueNameNullPolicy : JsonNamingPolicy + public class ValueNameNullPolicy : JsonNamingPolicy { public override string ConvertName(string name) => name == "Value" ? null : name; } @@ -420,21 +419,31 @@ private class ValueNameNullPolicy : JsonNamingPolicy [InlineData("[")] [InlineData("}")] [InlineData("{")] - [InlineData("{}")] [InlineData("{Key")] [InlineData("{0")] [InlineData(@"{""Random"":")] - [InlineData(@"{""Value"":1}")] [InlineData(@"{null:1}")] [InlineData(@"{""Value"":1,2")] [InlineData(@"{""Value"":1,""Random"":")] - [InlineData(@"{""Key"":1,""Key"":1}")] [InlineData(@"{null:1,""Key"":1}")] + [InlineData(@"{""Value"":1,null:1}")] + public async Task InvalidJsonFail(string json) + { + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(json)); + } + + [Theory] + [InlineData("{}")] + [InlineData(@"{""Value"":1}")] + [InlineData(@"{""Key"":1,""Key"":1}")] [InlineData(@"{""Key"":1,""Key"":2}")] [InlineData(@"{""Value"":1,""Value"":1}")] - [InlineData(@"{""Value"":1,null:1}")] [InlineData(@"{""Value"":1,""Value"":2}")] - public async Task InvalidJsonFail(string json) +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Souce generated code-paths use object converter (not KVP converter), " + + "which doesn't enforce that the JSON contains Key/Value props exactly.")] +#endif + public async Task InvalidJsonFail_WellFormed(string json) { await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(json)); } @@ -442,12 +451,27 @@ public async Task InvalidJsonFail(string json) [Theory] [InlineData(@"{""Key"":""1"",""Value"":2}", "$.Key")] [InlineData(@"{""Key"":1,""Value"":""2""}", "$.Value")] + public async Task JsonPathIsAccurate(string json, string expectedPath) + { + JsonException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(json)); + Assert.Contains(expectedPath, ex.ToString()); + + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(json)); + Assert.Contains(expectedPath, ex.ToString()); + } + + [Theory] [InlineData(@"{""key"":1,""Value"":2}", "$.key")] [InlineData(@"{""Key"":1,""value"":2}", "$.value")] [InlineData(@"{""Extra"":3,""Key"":1,""Value"":2}", "$.Extra")] [InlineData(@"{""Key"":1,""Extra"":3,""Value"":2}", "$.Extra")] [InlineData(@"{""Key"":1,""Value"":2,""Extra"":3}", "$.Extra")] - public async Task JsonPathIsAccurate(string json, string expectedPath) +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Souce generated code-paths use object converter (not KVP converter), " + + "which doesn't enforce that the JSON contains Key/Value props exactly.")] +#endif + public async Task JsonPathIsAccurate_WellFormed(string json, string expectedPath) { JsonException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(json)); Assert.Contains(expectedPath, ex.ToString()); @@ -477,5 +501,4 @@ public async Task JsonPathIsAccurate_PropertyNamingPolicy(string json, string ex Assert.Contains(expectedPath, ex.ToString()); } } -#endif } diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.NonGeneric.Read.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.NonGeneric.Read.cs index 4187cefde1a01..0e98fd000e687 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.NonGeneric.Read.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.NonGeneric.Read.cs @@ -370,7 +370,7 @@ public async Task ReadPrimitiveStack() } Assert.Equal(0, count); - StackWrapper wrapper = JsonSerializer.Deserialize(@"[1,2]"); + StackWrapper wrapper = await JsonSerializerWrapperForString.DeserializeWrapper(@"[1,2]"); expected = 2; foreach (JsonElement i in wrapper) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.AttributePresence.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs similarity index 67% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.AttributePresence.cs rename to src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs index 6082035309e6f..bd6e69b47bb0f 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.AttributePresence.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs @@ -13,7 +13,7 @@ public async Task NonPublicCtors_NotSupported() { async Task RunTestAsync() { - NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}")); + NotSupportedException ex = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{}")); Assert.Contains("JsonConstructorAttribute", ex.ToString()); } @@ -31,8 +31,8 @@ async Task RunTestAsync() [Fact] public async Task SinglePublicParameterizedCtor_SingleParameterlessCtor_NoAttribute_Supported_UseParameterlessCtor() { - var obj1 = await Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); - Assert.Equal(@"{""MyInt"":0,""MyString"":null}", JsonSerializer.Serialize(obj1)); + var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); + Assert.Equal(@"{""MyInt"":0,""MyString"":null}", await JsonSerializerWrapperForString.SerializeWrapper(obj1)); } [Fact] @@ -40,8 +40,8 @@ public async Task MultiplePublicParameterizedCtors_SingleParameterlessCtor_NoAtt { async Task RunTestAsync() { - var obj1 = await Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); - Assert.Equal(@"{""MyInt"":0,""MyString"":null}", JsonSerializer.Serialize(obj1)); + var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); + Assert.Equal(@"{""MyInt"":0,""MyString"":null}", await JsonSerializerWrapperForString.SerializeWrapper(obj1)); } await RunTestAsync(); @@ -53,8 +53,8 @@ public async Task SinglePublicParameterizedCtor_NoPublicParameterlessCtor_NoAttr { async Task RunTestAsync() { - var obj1 = await Serializer.DeserializeWrapper(@"{""MyInt"":1}"); - Assert.Equal(@"{""MyInt"":1}", JsonSerializer.Serialize(obj1)); + var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}"); + Assert.Equal(@"{""MyInt"":1}", await JsonSerializerWrapperForString.SerializeWrapper(obj1)); } await RunTestAsync(); @@ -66,8 +66,8 @@ public async Task SinglePublicParameterizedCtor_NoPublicParameterlessCtor_WithAt { async Task RunTestAsync() { - var obj1 = await Serializer.DeserializeWrapper(@"{""MyInt"":1}"); - Assert.Equal(@"{""MyInt"":1}", JsonSerializer.Serialize(obj1)); + var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1}"); + Assert.Equal(@"{""MyInt"":1}", await JsonSerializerWrapperForString.SerializeWrapper(obj1)); } await RunTestAsync(); @@ -78,47 +78,48 @@ async Task RunTestAsync() [Fact] public Task Class_MultiplePublicParameterizedCtors_NoPublicParameterlessCtor_NoAttribute_NotSupported() { - return Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}")); + return Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}")); } [Fact] public async Task Struct_MultiplePublicParameterizedCtors_NoPublicParameterlessCtor_NoAttribute_Supported_UseParameterlessCtor() { - var obj = await Serializer.DeserializeWrapper(@"{""myInt"":1,""myString"":""1""}"); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""myInt"":1,""myString"":""1""}"); Assert.Equal(0, obj.MyInt); Assert.Null(obj.MyString); - Assert.Equal(@"{""MyInt"":0,""MyString"":null}", JsonSerializer.Serialize(obj)); + Assert.Equal(@"{""MyInt"":0,""MyString"":null}", await JsonSerializerWrapperForString.SerializeWrapper(obj)); } [Fact] public async Task NoPublicParameterlessCtor_MultiplePublicParameterizedCtors_WithAttribute_Supported() { - var obj1 = await Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); + var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); Assert.Equal(1, obj1.MyInt); Assert.Null(obj1.MyString); - Assert.Equal(@"{""MyInt"":1,""MyString"":null}", JsonSerializer.Serialize(obj1)); + Assert.Equal(@"{""MyInt"":1,""MyString"":null}", await JsonSerializerWrapperForString.SerializeWrapper(obj1)); - var obj2 = await Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); + var obj2 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); Assert.Equal(1, obj2.MyInt); Assert.Equal("1", obj2.MyString); - Assert.Equal(@"{""MyInt"":1,""MyString"":""1""}", JsonSerializer.Serialize(obj2)); + Assert.Equal(@"{""MyInt"":1,""MyString"":""1""}", await JsonSerializerWrapperForString.SerializeWrapper(obj2)); } [Fact] public async Task PublicParameterlessCtor_MultiplePublicParameterizedCtors_WithAttribute_Supported() { - var obj = await Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyString"":""1""}"); Assert.Equal(1, obj.MyInt); Assert.Null(obj.MyString); - Assert.Equal(@"{""MyInt"":1,""MyString"":null}", JsonSerializer.Serialize(obj)); + Assert.Equal(@"{""MyInt"":1,""MyString"":null}", await JsonSerializerWrapperForString.SerializeWrapper(obj)); } +#if !BUILDING_SOURCE_GENERATOR_TESTS // These are compile-time warnings from the source generator. [Fact] public async Task MultipleAttributes_NotSupported() { async Task RunTestAsync() { - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{}")); } await RunTestAsync(); @@ -131,13 +132,14 @@ async Task RunTestAsync() await RunTestAsync(); await RunTestAsync(); } +#endif [Fact] public async Task AttributeIgnoredOnIEnumerable() { async Task RunTestAsync() { - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("[]")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("[]")); } await RunTestAsync(); @@ -150,11 +152,11 @@ public async Task Struct_Use_DefaultCtor_ByDefault() string json = @"{""X"":1,""Y"":2}"; // By default, serializer uses default ctor to deserializer structs - var point1 = await Serializer.DeserializeWrapper(json); + var point1 = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(0, point1.X); Assert.Equal(0, point1.Y); - var point2 = await Serializer.DeserializeWrapper(json); + var point2 = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(1, point2.X); Assert.Equal(2, point2.Y); } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs similarity index 78% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs rename to src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs index 58caed5feb1f6..e2275bc7ff7f8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Cache.cs @@ -11,6 +11,9 @@ public abstract partial class ConstructorTests { [Fact] [OuterLoop] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task MultipleThreadsLooping() { const int Iterations = 100; @@ -22,6 +25,9 @@ public async Task MultipleThreadsLooping() } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task MultipleThreads() { // Verify the test class has >32 properties since that is a threshold for using the fallback dictionary. @@ -29,14 +35,14 @@ public async Task MultipleThreads() async Task DeserializeObjectAsync(string json, Type type, JsonSerializerOptions options) { - var obj = await Serializer.DeserializeWrapper(json, type, options); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options); ((ITestClassWithParameterizedCtor)obj).Verify(); } async Task DeserializeObjectMinimalAsync(Type type, JsonSerializerOptions options) { string json = (string)type.GetProperty("s_json_minimal").GetValue(null); - var obj = await Serializer.DeserializeWrapper(json, type, options); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type, options); ((ITestClassWithParameterizedCtor)obj).VerifyMinimal(); }; @@ -52,10 +58,10 @@ async Task DeserializeObjectNormalAsync(Type type, JsonSerializerOptions options await DeserializeObjectAsync(json, type, options); }; - void SerializeObject(Type type, JsonSerializerOptions options) + async Task SerializeObject(Type type, JsonSerializerOptions options) { var obj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance(); - JsonSerializer.Serialize(obj, options); + await JsonSerializerWrapperForString.SerializeWrapper(obj, options); }; async Task RunTestAsync(Type type) @@ -93,13 +99,13 @@ public async Task PropertyCacheWithMinInputsFirst() var options = new JsonSerializerOptions(); string json = "{}"; - await Serializer.DeserializeWrapper(json, options); + await JsonSerializerWrapperForString.DeserializeWrapper(json, options); ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance(); testObj.Verify(); - json = JsonSerializer.Serialize(testObj, options); - testObj = await Serializer.DeserializeWrapper(json, options); + json = await JsonSerializerWrapperForString.SerializeWrapper(testObj, options); + testObj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); testObj.Verify(); } @@ -112,12 +118,12 @@ public async Task PropertyCacheWithMinInputsLast() ClassWithConstructor_SimpleAndComplexParameters testObj = ClassWithConstructor_SimpleAndComplexParameters.GetInstance(); testObj.Verify(); - string json = JsonSerializer.Serialize(testObj, options); - testObj = await Serializer.DeserializeWrapper(json, options); + string json = await JsonSerializerWrapperForString.SerializeWrapper(testObj, options); + testObj = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); testObj.Verify(); json = "{}"; - await Serializer.DeserializeWrapper(json, options); + await JsonSerializerWrapperForString.DeserializeWrapper(json, options); } // Use a common options instance to encourage additional metadata collisions across types. Also since @@ -126,21 +132,24 @@ public async Task PropertyCacheWithMinInputsLast() [Fact] [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", RuntimeConfiguration.Checked)] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task MultipleTypes() { - void Serialize(object[] args) + async Task Serialize(object[] args) { Type type = typeof(T); T localTestObj = (T)Activator.CreateInstance(type, args); ((ITestClass)localTestObj).Initialize(); ((ITestClass)localTestObj).Verify(); - string json = JsonSerializer.Serialize(localTestObj, s_options); + string json = await JsonSerializerWrapperForString.SerializeWrapper(localTestObj, s_options); }; async Task DeserializeAsync(string json) { - ITestClass obj = (ITestClass)await Serializer.DeserializeWrapper(json, s_options); + ITestClass obj = (ITestClass)await JsonSerializerWrapperForString.DeserializeWrapper(json, s_options); obj.Verify(); }; @@ -149,7 +158,7 @@ async Task RunTestAsync(T testObj, object[] args) // Get the test json with the default options to avoid cache pollution of DeserializeAsync() below. ((ITestClass)testObj).Initialize(); ((ITestClass)testObj).Verify(); - string json = JsonSerializer.Serialize(testObj); + string json = await JsonSerializerWrapperForString.SerializeWrapper(testObj); const int ThreadCount = 12; const int ConcurrentTestsCount = 2; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Exceptions.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs similarity index 72% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Exceptions.cs rename to src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs index 6f686e3afa6be..f34687d65bd59 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Exceptions.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Exceptions.cs @@ -13,30 +13,27 @@ public abstract partial class ConstructorTests public async Task MultipleProperties_Cannot_BindTo_TheSame_ConstructorParameter() { InvalidOperationException ex = await Assert.ThrowsAsync( - () => Serializer.DeserializeWrapper("{}")); + () => JsonSerializerWrapperForString.DeserializeWrapper("{}")); string exStr = ex.ToString(); Assert.Contains("'X'", exStr); Assert.Contains("'x'", exStr); - Assert.Contains("(Int32, Int32)", exStr); Assert.Contains("System.Text.Json.Serialization.Tests.Point_MultipleMembers_BindTo_OneConstructorParameter", exStr); ex = await Assert.ThrowsAsync( - () => Serializer.DeserializeWrapper("{}")); + () => JsonSerializerWrapperForString.DeserializeWrapper("{}")); exStr = ex.ToString(); Assert.Contains("'X'", exStr); Assert.Contains("'x'", exStr); - Assert.Contains("(Int32)", exStr); Assert.Contains("Point_MultipleMembers_BindTo_OneConstructorParameter_Variant", exStr); ex = await Assert.ThrowsAsync( - () => Serializer.DeserializeWrapper("{}")); + () => JsonSerializerWrapperForString.DeserializeWrapper("{}")); exStr = ex.ToString(); Assert.Contains("'URL'", exStr); Assert.Contains("'Url'", exStr); - Assert.Contains("(Int32)", exStr); Assert.Contains("Url_BindTo_OneConstructorParameter", exStr); } @@ -44,22 +41,19 @@ public async Task MultipleProperties_Cannot_BindTo_TheSame_ConstructorParameter( public async Task All_ConstructorParameters_MustBindTo_ObjectMembers() { InvalidOperationException ex = await Assert.ThrowsAsync( - () => Serializer.DeserializeWrapper("{}")); + () => JsonSerializerWrapperForString.DeserializeWrapper("{}")); string exStr = ex.ToString(); - Assert.Contains("(Int32, Int32)", exStr); Assert.Contains("System.Text.Json.Serialization.Tests.Point_Without_Members", exStr); ex = await Assert.ThrowsAsync( - () => Serializer.DeserializeWrapper("{}")); + () => JsonSerializerWrapperForString.DeserializeWrapper("{}")); exStr = ex.ToString(); - Assert.Contains("(Int32, Int32)", exStr); Assert.Contains("System.Text.Json.Serialization.Tests.Point_With_MismatchedMembers", exStr); ex = await Assert.ThrowsAsync( - () => Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyPoint"":{}}")); + () => JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyPoint"":{}}")); exStr = ex.ToString(); - Assert.Contains("(Int32, Int32)", exStr); Assert.Contains("System.Text.Json.Serialization.Tests.Point_With_MismatchedMembers", exStr); } @@ -69,7 +63,7 @@ public async Task LeadingReferenceMetadataNotSupported() string json = @"{""$id"":""1"",""Name"":""Jet"",""Manager"":{""$ref"":""1""}}"; // Metadata ignored by default. - var employee = await Serializer.DeserializeWrapper(json); + var employee = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal("Jet", employee.Name); Assert.Null(employee.Manager.Name); @@ -80,14 +74,14 @@ public async Task LeadingReferenceMetadataNotSupported() var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve }; NotSupportedException ex = await Assert.ThrowsAsync( - () => Serializer.DeserializeWrapper(json, options)); + () => JsonSerializerWrapperForString.DeserializeWrapper(json, options)); string exStr = ex.ToString(); Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Employee", exStr); Assert.Contains("$.$id", exStr); } - private class Employee + public class Employee { public string Name { get; } public Employee Manager { get; set; } @@ -105,7 +99,7 @@ public async Task RandomReferenceMetadataNotSupported() // Baseline, preserve ref feature off. - var employee = JsonSerializer.Deserialize(json); + var employee = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal("Jet", employee.Name); @@ -113,20 +107,22 @@ public async Task RandomReferenceMetadataNotSupported() var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve }; - NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(json, options)); + NotSupportedException ex = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(json, options)); string exStr = ex.ToString(); Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Employee", exStr); Assert.Contains("$.$random", exStr); } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task ExtensionDataProperty_CannotBindTo_CtorParam() { - InvalidOperationException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}")); + InvalidOperationException ex = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{}")); string exStr = ex.ToString(); // System.InvalidOperationException: 'The extension data property 'System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement] ExtensionData' on type 'System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam' cannot bind with a parameter in constructor 'Void .ctor(System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement])'.' Assert.Contains("System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement] ExtensionData", exStr); Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Class_ExtData_CtorParam", exStr); - Assert.Contains("(System.Collections.Generic.Dictionary`2[System.String,System.Text.Json.JsonElement])", exStr); } public class Class_ExtData_CtorParam @@ -149,23 +145,23 @@ public async Task DeserializePathForObjectFails() ClassWithUnicodePropertyName obj; // Baseline. - obj = await Serializer.DeserializeWrapper(GoodJson); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(GoodJson); Assert.Equal(1, obj.Property\u04671); - obj = await Serializer.DeserializeWrapper(GoodJsonEscaped); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(GoodJsonEscaped); Assert.Equal(1, obj.Property\u04671); JsonException e; // Exception. - e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(BadJson)); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(BadJson)); Assert.Equal(Expected, e.Path); - e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(BadJsonEscaped)); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(BadJsonEscaped)); Assert.Equal(Expected, e.Path); } - private class ClassWithUnicodePropertyName + public class ClassWithUnicodePropertyName { public int Property\u04671 { get; } // contains a trailing "1" @@ -178,7 +174,7 @@ public ClassWithUnicodePropertyName(int property\u04671) [Fact] public async Task PathForChildPropertyFails() { - JsonException e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Child"":{""MyInt"":bad]}")); + JsonException e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Child"":{""MyInt"":bad]}")); Assert.Equal("$.Child.MyInt", e.Path); } @@ -203,35 +199,35 @@ public class ChildClass [Fact] public async Task PathForChildListFails() { - JsonException e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Child"":{""MyIntArray"":[1, bad]}")); + JsonException e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Child"":{""MyIntArray"":[1, bad]}")); Assert.Contains("$.Child.MyIntArray", e.Path); } [Fact] public async Task PathForChildDictionaryFails() { - JsonException e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Child"":{""MyDictionary"":{""Key"": bad]")); + JsonException e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Child"":{""MyDictionary"":{""Key"": bad]")); Assert.Equal("$.Child.MyDictionary.Key", e.Path); } [Fact] public async Task PathForSpecialCharacterFails() { - JsonException e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Child"":{""MyDictionary"":{""Key1"":{""Children"":[{""MyDictionary"":{""K.e.y"":""")); + JsonException e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Child"":{""MyDictionary"":{""Key1"":{""Children"":[{""MyDictionary"":{""K.e.y"":""")); Assert.Equal("$.Child.MyDictionary.Key1.Children[0].MyDictionary['K.e.y']", e.Path); } [Fact] public async Task PathForSpecialCharacterNestedFails() { - JsonException e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Child"":{""Children"":[{}, {""MyDictionary"":{""K.e.y"": {""MyInt"":bad")); + JsonException e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Child"":{""Children"":[{}, {""MyDictionary"":{""K.e.y"": {""MyInt"":bad")); Assert.Equal("$.Child.Children[1].MyDictionary['K.e.y'].MyInt", e.Path); } [Fact] public async Task EscapingFails() { - JsonException e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{\"A\u0467\":bad}")); + JsonException e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{\"A\u0467\":bad}")); Assert.Equal("$.A\u0467", e.Path); } @@ -249,12 +245,12 @@ public Parameterized_ClassWithUnicodeProperty(int a\u0467) public async Task ExtensionPropertyRoundTripFails() { JsonException e = await Assert.ThrowsAsync(() => - Serializer.DeserializeWrapper(@"{""MyNestedClass"":{""UnknownProperty"":bad}}")); + JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyNestedClass"":{""UnknownProperty"":bad}}")); Assert.Equal("$.MyNestedClass.UnknownProperty", e.Path); } - private class Parameterized_ClassWithExtensionProperty + public class Parameterized_ClassWithExtensionProperty { public SimpleTestClass MyNestedClass { get; } public int MyInt { get; } @@ -277,36 +273,39 @@ public async Task CaseInsensitiveFails() // Baseline (no exception) { - var obj = await Serializer.DeserializeWrapper(@"{""mydecimal"":1}", options); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""mydecimal"":1}", options); Assert.Equal(1, obj.MyDecimal); } { - var obj = await Serializer.DeserializeWrapper(@"{""MYDECIMAL"":1}", options); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MYDECIMAL"":1}", options); Assert.Equal(1, obj.MyDecimal); } JsonException e; - e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""mydecimal"":bad}", options)); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""mydecimal"":bad}", options)); Assert.Equal("$.mydecimal", e.Path); - e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""MYDECIMAL"":bad}", options)); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""MYDECIMAL"":bad}", options)); Assert.Equal("$.MYDECIMAL", e.Path); } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Multi-dim arrays not supported.")] +#endif public async Task ClassWithUnsupportedCollectionTypes() { Exception e; - e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""UnsupportedArray"":[]}")); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""UnsupportedArray"":[]}")); Assert.Contains("System.Int32[,]", e.ToString()); // The exception for element types do not contain the parent type and the property name // since the verification occurs later and is no longer bound to the parent type. Assert.DoesNotContain("ClassWithInvalidArray.UnsupportedArray", e.ToString()); - e = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""UnsupportedDictionary"":{}}")); + e = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""UnsupportedDictionary"":{}}")); Assert.Contains("System.Int32[,]", e.ToString()); Assert.DoesNotContain("ClassWithInvalidDictionary.UnsupportedDictionary", e.ToString()); } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs similarity index 59% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs rename to src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs index 54e407cc9fc27..5f2da8c2caa8c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs @@ -9,56 +9,34 @@ namespace System.Text.Json.Serialization.Tests { - public class ConstructorTests_String : ConstructorTests + public abstract partial class ConstructorTests : SerializerTests { - public ConstructorTests_String() : base(JsonSerializerWrapperForString.StringSerializer) { } - } - - public class ConstructorTests_AsyncStream : ConstructorTests - { - public ConstructorTests_AsyncStream() : base(JsonSerializerWrapperForString.AsyncStreamSerializer) { } - } - - public class ConstructorTests_SyncStream : ConstructorTests - { - public ConstructorTests_SyncStream() : base(JsonSerializerWrapperForString.SyncStreamSerializer) { } - } - - public class ConstructorTests_Span : ConstructorTests - { - public ConstructorTests_Span() : base(JsonSerializerWrapperForString.SpanSerializer) { } - } - - public abstract partial class ConstructorTests - { - private JsonSerializerWrapperForString Serializer { get; } - - public ConstructorTests(JsonSerializerWrapperForString serializer) + public ConstructorTests(JsonSerializerWrapperForString stringSerializer, JsonSerializerWrapperForStream streamSerializer) + : base(stringSerializer, streamSerializer) { - Serializer = serializer; } [Fact] public async Task ReturnNullForNullObjects() { - Assert.Null(await Serializer.DeserializeWrapper("null")); - Assert.Null(await Serializer.DeserializeWrapper("null")); + Assert.Null(await JsonSerializerWrapperForString.DeserializeWrapper("null")); + Assert.Null(await JsonSerializerWrapperForString.DeserializeWrapper("null")); } [Fact] public Task JsonExceptionWhenAssigningNullToStruct() { - return Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("null")); + return Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("null")); } [Fact] public async Task MatchJsonPropertyToConstructorParameters() { - Point_2D point = await Serializer.DeserializeWrapper(@"{""X"":1,""Y"":2}"); + Point_2D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1,""Y"":2}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); - point = await Serializer.DeserializeWrapper(@"{""Y"":2,""X"":1}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Y"":2,""X"":1}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); } @@ -67,98 +45,98 @@ public async Task MatchJsonPropertyToConstructorParameters() public async Task UseDefaultValues_When_NoJsonMatch() { // Using CLR value when `ParameterInfo.DefaultValue` is not set. - Point_2D point = await Serializer.DeserializeWrapper(@"{""x"":1,""y"":2}"); + Point_2D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""x"":1,""y"":2}"); Assert.Equal(0, point.X); Assert.Equal(0, point.Y); - point = await Serializer.DeserializeWrapper(@"{""y"":2,""x"":1}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""y"":2,""x"":1}"); Assert.Equal(0, point.X); Assert.Equal(0, point.Y); - point = await Serializer.DeserializeWrapper(@"{""x"":1,""Y"":2}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""x"":1,""Y"":2}"); Assert.Equal(0, point.X); Assert.Equal(2, point.Y); - point = await Serializer.DeserializeWrapper(@"{""y"":2,""X"":1}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""y"":2,""X"":1}"); Assert.Equal(1, point.X); Assert.Equal(0, point.Y); - point = await Serializer.DeserializeWrapper(@"{""X"":1}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1}"); Assert.Equal(1, point.X); Assert.Equal(0, point.Y); - point = await Serializer.DeserializeWrapper(@"{""Y"":2}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Y"":2}"); Assert.Equal(0, point.X); Assert.Equal(2, point.Y); - point = await Serializer.DeserializeWrapper(@"{""X"":1}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1}"); Assert.Equal(1, point.X); Assert.Equal(0, point.Y); - point = await Serializer.DeserializeWrapper(@"{""Y"":2}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Y"":2}"); Assert.Equal(0, point.X); Assert.Equal(2, point.Y); - point = await Serializer.DeserializeWrapper(@"{}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); Assert.Equal(0, point.X); Assert.Equal(0, point.Y); - point = await Serializer.DeserializeWrapper(@"{""a"":1,""b"":2}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""a"":1,""b"":2}"); Assert.Equal(0, point.X); Assert.Equal(0, point.Y); // Using `ParameterInfo.DefaultValue` when set; using CLR value as fallback. - Point_3D point3d = await Serializer.DeserializeWrapper(@"{""X"":1}"); + Point_3D point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1}"); Assert.Equal(1, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(50, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""y"":2}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""y"":2}"); Assert.Equal(0, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(50, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""Z"":3}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Z"":3}"); Assert.Equal(0, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(3, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""X"":1}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1}"); Assert.Equal(1, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(50, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""Y"":2}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Y"":2}"); Assert.Equal(0, point3d.X); Assert.Equal(2, point3d.Y); Assert.Equal(50, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""Z"":3}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Z"":3}"); Assert.Equal(0, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(3, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""x"":1,""Y"":2}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""x"":1,""Y"":2}"); Assert.Equal(0, point3d.X); Assert.Equal(2, point3d.Y); Assert.Equal(50, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""Z"":3,""y"":2}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Z"":3,""y"":2}"); Assert.Equal(0, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(3, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""x"":1,""Z"":3}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""x"":1,""Z"":3}"); Assert.Equal(0, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(3, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); Assert.Equal(0, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(50, point3d.Z); - point3d = await Serializer.DeserializeWrapper(@"{""a"":1,""b"":2}"); + point3d = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""a"":1,""b"":2}"); Assert.Equal(0, point3d.X); Assert.Equal(0, point3d.Y); Assert.Equal(50, point3d.Z); @@ -169,19 +147,19 @@ public async Task CaseInsensitivityWorks() { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - Point_2D point = await Serializer.DeserializeWrapper(@"{""x"":1,""y"":2}", options); + Point_2D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""x"":1,""y"":2}", options); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); - point = await Serializer.DeserializeWrapper(@"{""y"":2,""x"":1}", options); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""y"":2,""x"":1}", options); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); - point = await Serializer.DeserializeWrapper(@"{""x"":1,""Y"":2}", options); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""x"":1,""Y"":2}", options); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); - point = await Serializer.DeserializeWrapper(@"{""y"":2,""X"":1}", options); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""y"":2,""X"":1}", options); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); } @@ -189,32 +167,32 @@ public async Task CaseInsensitivityWorks() [Fact] public async Task VaryingOrderingOfJson() { - Point_3D point = await Serializer.DeserializeWrapper(@"{""X"":1,""Y"":2,""Z"":3}"); + Point_3D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1,""Y"":2,""Z"":3}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); Assert.Equal(3, point.Z); - point = await Serializer.DeserializeWrapper(@"{""X"":1,""Z"":3,""Y"":2}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1,""Z"":3,""Y"":2}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); Assert.Equal(3, point.Z); - point = await Serializer.DeserializeWrapper(@"{""Y"":2,""Z"":3,""X"":1}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Y"":2,""Z"":3,""X"":1}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); Assert.Equal(3, point.Z); - point = await Serializer.DeserializeWrapper(@"{""Y"":2,""X"":1,""Z"":3}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Y"":2,""X"":1,""Z"":3}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); Assert.Equal(3, point.Z); - point = await Serializer.DeserializeWrapper(@"{""Z"":3,""Y"":2,""X"":1}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Z"":3,""Y"":2,""X"":1}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); Assert.Equal(3, point.Z); - point = await Serializer.DeserializeWrapper(@"{""Z"":3,""X"":1,""Y"":2}"); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Z"":3,""X"":1,""Y"":2}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); Assert.Equal(3, point.Z); @@ -223,7 +201,7 @@ public async Task VaryingOrderingOfJson() [Fact] public async Task AsListElement() { - List list = await Serializer.DeserializeWrapper>(@"[{""Y"":2,""Z"":3,""X"":1},{""Z"":10,""Y"":30,""X"":20}]"); + List list = await JsonSerializerWrapperForString.DeserializeWrapper>(@"[{""Y"":2,""Z"":3,""X"":1},{""Z"":10,""Y"":30,""X"":20}]"); Assert.Equal(1, list[0].X); Assert.Equal(2, list[0].Y); Assert.Equal(3, list[0].Z); @@ -235,7 +213,7 @@ public async Task AsListElement() [Fact] public async Task AsDictionaryValue() { - Dictionary dict = await Serializer.DeserializeWrapper>(@"{""0"":{""Y"":2,""Z"":3,""X"":1},""1"":{""Z"":10,""Y"":30,""X"":20}}"); + Dictionary dict = await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""0"":{""Y"":2,""Z"":3,""X"":1},""1"":{""Z"":10,""Y"":30,""X"":20}}"); Assert.Equal(1, dict["0"].X); Assert.Equal(2, dict["0"].Y); Assert.Equal(3, dict["0"].Z); @@ -247,7 +225,7 @@ public async Task AsDictionaryValue() [Fact] public async Task AsProperty_Of_ObjectWithParameterlessCtor() { - WrapperForPoint_3D obj = await Serializer.DeserializeWrapper(@"{""Point_3D"":{""Y"":2,""Z"":3,""X"":1}}"); + WrapperForPoint_3D obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point_3D"":{""Y"":2,""Z"":3,""X"":1}}"); Point_3D point = obj.Point_3D; Assert.Equal(1, point.X); Assert.Equal(2, point.Y); @@ -257,7 +235,7 @@ public async Task AsProperty_Of_ObjectWithParameterlessCtor() [Fact] public async Task AsProperty_Of_ObjectWithParameterizedCtor() { - ClassWrapperForPoint_3D obj = await Serializer.DeserializeWrapper(@"{""Point3D"":{""Y"":2,""Z"":3,""X"":1}}"); + ClassWrapperForPoint_3D obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3D"":{""Y"":2,""Z"":3,""X"":1}}"); Point_3D point = obj.Point3D; Assert.Equal(1, point.X); Assert.Equal(2, point.Y); @@ -267,7 +245,7 @@ public async Task AsProperty_Of_ObjectWithParameterizedCtor() [Fact] public async Task At_Symbol_As_ParameterNamePrefix() { - ClassWrapper_For_Int_String obj = await Serializer.DeserializeWrapper(@"{""Int"":1,""String"":""1""}"); + ClassWrapper_For_Int_String obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Int"":1,""String"":""1""}"); Assert.Equal(1, obj.Int); Assert.Equal("1", obj.String); } @@ -275,7 +253,7 @@ public async Task At_Symbol_As_ParameterNamePrefix() [Fact] public async Task At_Symbol_As_ParameterNamePrefix_UseDefaultValues() { - ClassWrapper_For_Int_String obj = await Serializer.DeserializeWrapper(@"{""@Int"":1,""@String"":""1""}"); + ClassWrapper_For_Int_String obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""@Int"":1,""@String"":""1""}"); Assert.Equal(0, obj.Int); Assert.Null(obj.String); } @@ -283,10 +261,10 @@ public async Task At_Symbol_As_ParameterNamePrefix_UseDefaultValues() [Fact] public async Task PassDefaultValueToComplexStruct() { - ClassWrapperForPoint_3D obj = await Serializer.DeserializeWrapper(@"{}"); + ClassWrapperForPoint_3D obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); Assert.True(obj.Point3D == default); - ClassWrapper_For_Int_Point_3D_String obj1 = await Serializer.DeserializeWrapper(@"{}"); + ClassWrapper_For_Int_Point_3D_String obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{}"); Assert.Equal(0, obj1.MyInt); Assert.Equal(0, obj1.MyPoint3DStruct.X); Assert.Equal(0, obj1.MyPoint3DStruct.Y); @@ -297,7 +275,7 @@ public async Task PassDefaultValueToComplexStruct() [Fact] public async Task Null_AsArgument_To_ParameterThat_CanBeNull() { - ClassWrapper_For_Int_Point_3D_String obj1 = await Serializer.DeserializeWrapper(@"{""MyInt"":1,""MyPoint3DStruct"":{},""MyString"":null}"); + ClassWrapper_For_Int_Point_3D_String obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":1,""MyPoint3DStruct"":{},""MyString"":null}"); Assert.Equal(1, obj1.MyInt); Assert.Equal(0, obj1.MyPoint3DStruct.X); Assert.Equal(0, obj1.MyPoint3DStruct.Y); @@ -308,32 +286,38 @@ public async Task Null_AsArgument_To_ParameterThat_CanBeNull() [Fact] public async Task Null_AsArgument_To_ParameterThat_CanNotBeNull() { - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""MyInt"":null,""MyString"":""1""}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""MyPoint3DStruct"":null,""MyString"":""1""}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyInt"":null,""MyString"":""1""}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""MyPoint3DStruct"":null,""MyString"":""1""}")); } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task OtherPropertiesAreSet() { - var personClass = await Serializer.DeserializeWrapper(Person_Class.s_json); + var personClass = await JsonSerializerWrapperForString.DeserializeWrapper(Person_Class.s_json); personClass.Verify(); - var personStruct = await Serializer.DeserializeWrapper(Person_Struct.s_json); + var personStruct = await JsonSerializerWrapperForString.DeserializeWrapper(Person_Struct.s_json); personStruct.Verify(); } [Fact] public async Task ExtraProperties_AreIgnored() { - Point_2D point = await Serializer.DeserializeWrapper(@"{ ""x"":1,""y"":2,""b"":3}"); + Point_2D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{ ""x"":1,""y"":2,""b"":3}"); Assert.Equal(0, point.X); Assert.Equal(0, point.Y); } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task ExtraProperties_GoInExtensionData_IfPresent() { - Point_2D_With_ExtData point = await Serializer.DeserializeWrapper(@"{""X"":1,""y"":2,""b"":3}"); + Point_2D_With_ExtData point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1,""y"":2,""b"":3}"); Assert.Equal(1, point.X); Assert.Equal(2, point.ExtensionData["y"].GetInt32()); Assert.Equal(3, point.ExtensionData["b"].GetInt32()); @@ -342,7 +326,7 @@ public async Task ExtraProperties_GoInExtensionData_IfPresent() [Fact] public async Task PropertiesNotSet_WhenJSON_MapsToConstructorParameters() { - var obj = await Serializer.DeserializeWrapper(@"{""X"":1,""Y"":2}"); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1,""Y"":2}"); Assert.Equal(40, obj.X); // Would be 1 if property were set directly after object construction. Assert.Equal(60, obj.Y); // Would be 2 if property were set directly after object construction. } @@ -352,39 +336,39 @@ public async Task IgnoreNullValues_DontSetNull_ToConstructorArguments_ThatCantBe { // Throw JsonException when null applied to types that can't be null. Behavior should align with properties deserialized with setters. - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Int"":null}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Int"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Int"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Int"":null}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""ImmutableArray"":null}")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""ImmutableArray"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""ImmutableArray"":null}")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""ImmutableArray"":null}")); // Throw even when IgnoreNullValues is true for symmetry with property deserialization, // until https://github.com/dotnet/runtime/issues/30795 is addressed. var options = new JsonSerializerOptions { IgnoreNullValues = true }; - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null}", options)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Int"":null}", options)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Int"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""ImmutableArray"":null}", options)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""ImmutableArray"":null}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(@"{""Point3DStruct"":null,""Int"":null,""ImmutableArray"":null}", options)); } [Fact] public async Task NumerousSimpleAndComplexParameters() { - var obj = await Serializer.DeserializeWrapper(ClassWithConstructor_SimpleAndComplexParameters.s_json); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(ClassWithConstructor_SimpleAndComplexParameters.s_json); obj.Verify(); } @@ -411,9 +395,9 @@ public async Task ClassWithPrimitives_Parameterless() point.ThirdDateTime = DateTime.Now; point.FourthDateTime = DateTime.Now.AddHours(1).AddYears(1); - string json = JsonSerializer.Serialize(point); + string json = await JsonSerializerWrapperForString.SerializeWrapper(point); - var deserialized = await Serializer.DeserializeWrapper(json); + var deserialized = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(point.FirstInt, deserialized.FirstInt); Assert.Equal(point.SecondInt, deserialized.SecondInt); Assert.Equal(point.FirstString, deserialized.FirstString); @@ -452,9 +436,9 @@ public async Task ClassWithPrimitives() point.ThirdDateTime = DateTime.Now; point.FourthDateTime = DateTime.Now.AddHours(1).AddYears(1); - string json = JsonSerializer.Serialize(point); + string json = await JsonSerializerWrapperForString.SerializeWrapper(point); - var deserialized = await Serializer.DeserializeWrapper(json); + var deserialized = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(point.FirstInt, deserialized.FirstInt); Assert.Equal(point.SecondInt, deserialized.SecondInt); Assert.Equal(point.FirstString, deserialized.FirstString); @@ -493,10 +477,10 @@ public async Task ClassWithPrimitivesPerf() point.ThirdDateTime = DateTime.Now; point.FourthDateTime = DateTime.Now.AddHours(1).AddYears(1); - string json = JsonSerializer.Serialize(point); + string json = await JsonSerializerWrapperForString.SerializeWrapper(point); - await Serializer.DeserializeWrapper(json); - await Serializer.DeserializeWrapper(json); + await JsonSerializerWrapperForString.DeserializeWrapper(json); + await JsonSerializerWrapperForString.DeserializeWrapper(json); } [Fact] @@ -504,16 +488,16 @@ public async Task TupleDeserializationWorks() { var dont_trim_ctor = typeof(Tuple<,>).GetConstructors(); - var tuple = await Serializer.DeserializeWrapper>(@"{""Item1"":""New York"",""Item2"":32.68}"); + var tuple = await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Item1"":""New York"",""Item2"":32.68}"); Assert.Equal("New York", tuple.Item1); Assert.Equal(32.68, tuple.Item2); - var tupleWrapper = await Serializer.DeserializeWrapper(@"{""Tuple"":{""Item1"":""New York"",""Item2"":32.68}}"); + var tupleWrapper = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Tuple"":{""Item1"":""New York"",""Item2"":32.68}}"); tuple = tupleWrapper.Tuple; Assert.Equal("New York", tuple.Item1); Assert.Equal(32.68, tuple.Item2); - var tupleList = await Serializer.DeserializeWrapper>>(@"[{""Item1"":""New York"",""Item2"":32.68}]"); + var tupleList = await JsonSerializerWrapperForString.DeserializeWrapper>>(@"[{""Item1"":""New York"",""Item2"":32.68}]"); tuple = tupleList[0]; Assert.Equal("New York", tuple.Item1); Assert.Equal(32.68, tuple.Item2); @@ -527,21 +511,23 @@ public async Task TupleDeserialization_MoreThanSevenItems() dont_trim_ctor = typeof(Tuple<,,,,,,,>).GetConstructors(); // Seven is okay - string json = JsonSerializer.Serialize(Tuple.Create(1, 2, 3, 4, 5, 6, 7)); - var obj = await Serializer.DeserializeWrapper>(json); - Assert.Equal(json, JsonSerializer.Serialize(obj)); + string json = await JsonSerializerWrapperForString.SerializeWrapper(Tuple.Create(1, 2, 3, 4, 5, 6, 7)); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper>(json); + Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj)); +#if !BUILDING_SOURCE_GENERATOR_TESTS // Source-gen implementations aren't binding with tuples with more than 7 generic args // More than seven arguments needs special casing and can be revisted. // Newtonsoft.Json fails in the same way. - json = JsonSerializer.Serialize(Tuple.Create(1, 2, 3, 4, 5, 6, 7, 8)); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper>(json)); + json = await JsonSerializerWrapperForString.SerializeWrapper(Tuple.Create(1, 2, 3, 4, 5, 6, 7, 8)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper>(json)); // Invalid JSON representing a tuple with more than seven items yields an ArgumentException from the constructor. // System.ArgumentException : The last element of an eight element tuple must be a Tuple. // We pass the number 8, not a new Tuple(8). // Fixing this needs special casing. Newtonsoft behaves the same way. string invalidJson = @"{""Item1"":1,""Item2"":2,""Item3"":3,""Item4"":4,""Item5"":5,""Item6"":6,""Item7"":7,""Item1"":8}"; - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper>(invalidJson)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper>(invalidJson)); +#endif } [Fact] @@ -550,9 +536,9 @@ public async Task TupleDeserialization_DefaultValuesUsed_WhenJsonMissing() { // Seven items; only three provided. string input = @"{""Item2"":""2"",""Item3"":3,""Item6"":6}"; - var obj = await Serializer.DeserializeWrapper>(input); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper>(input); - string serialized = JsonSerializer.Serialize(obj); + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj); Assert.Contains(@"""Item1"":0", serialized); Assert.Contains(@"""Item2"":""2""", serialized); Assert.Contains(@"""Item3"":3", serialized); @@ -561,7 +547,7 @@ public async Task TupleDeserialization_DefaultValuesUsed_WhenJsonMissing() Assert.Contains(@"""Item6"":6", serialized); Assert.Contains(@"""Item7"":{", serialized); - serialized = JsonSerializer.Serialize(obj.Item7); + serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj.Item7); Assert.Contains(@"""X"":0", serialized); Assert.Contains(@"""Y"":0", serialized); Assert.Contains(@"""Z"":0", serialized); @@ -570,10 +556,13 @@ public async Task TupleDeserialization_DefaultValuesUsed_WhenJsonMissing() // System.ArgumentException : The last element of an eight element tuple must be a Tuple. // We pass the number 8, not a new Tuple(default(int)). // Fixing this needs special casing. Newtonsoft behaves the same way. - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper>(input)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper>(input)); } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs full SimpleTestClass support.")] +#endif public async Task TupleDeserializationWorks_ClassWithParameterizedCtor() { string classJson = ClassWithConstructor_SimpleAndComplexParameters.s_json; @@ -589,7 +578,7 @@ public async Task TupleDeserializationWorks_ClassWithParameterizedCtor() string complexTupleJson = sb.ToString(); - var complexTuple = await Serializer.DeserializeWrapper).GetConstructors(); - var complexTuple = await Serializer.DeserializeWrapper(serialized); + Point_3D point = await JsonSerializerWrapperForString.DeserializeWrapper(serialized); Assert.Equal(10, point.X); Assert.Equal(6, point.Y); Assert.Equal(50, point.Z); - serialized = JsonSerializer.Serialize(new[] { new Point_3D(10, 6) }); + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new[] { new Point_3D(10, 6) }); - point = (await Serializer.DeserializeWrapper(serialized))[0]; + point = (await JsonSerializerWrapperForString.DeserializeWrapper(serialized))[0]; Assert.Equal(10, point.X); Assert.Equal(6, point.Y); Assert.Equal(50, point.Z); - serialized = JsonSerializer.Serialize(new WrapperForPoint_3D { Point_3D = new Point_3D(10, 6) }); + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new WrapperForPoint_3D { Point_3D = new Point_3D(10, 6) }); - point = (await Serializer.DeserializeWrapper(serialized)).Point_3D; + point = (await JsonSerializerWrapperForString.DeserializeWrapper(serialized)).Point_3D; Assert.Equal(10, point.X); Assert.Equal(6, point.Y); Assert.Equal(50, point.Z); @@ -673,23 +665,23 @@ public async Task NoConstructorHandlingWhenObjectHasConverter() var options = new JsonSerializerOptions(); options.Converters.Add(new ConverterForPoint3D()); - serialized = JsonSerializer.Serialize(new Point_3D(10, 6)); + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new Point_3D(10, 6)); - point = await Serializer.DeserializeWrapper(serialized, options); + point = await JsonSerializerWrapperForString.DeserializeWrapper(serialized, options); Assert.Equal(4, point.X); Assert.Equal(4, point.Y); Assert.Equal(4, point.Z); - serialized = JsonSerializer.Serialize(new[] { new Point_3D(10, 6) }); + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new[] { new Point_3D(10, 6) }); - point = (await Serializer.DeserializeWrapper(serialized, options))[0]; + point = (await JsonSerializerWrapperForString.DeserializeWrapper(serialized, options))[0]; Assert.Equal(4, point.X); Assert.Equal(4, point.Y); Assert.Equal(4, point.Z); - serialized = JsonSerializer.Serialize(new WrapperForPoint_3D { Point_3D = new Point_3D(10, 6) }); + serialized = await JsonSerializerWrapperForString.SerializeWrapper(new WrapperForPoint_3D { Point_3D = new Point_3D(10, 6) }); - point = (await Serializer.DeserializeWrapper(serialized, options)).Point_3D; + point = (await JsonSerializerWrapperForString.DeserializeWrapper(serialized, options)).Point_3D; Assert.Equal(4, point.X); Assert.Equal(4, point.Y); Assert.Equal(4, point.Z); @@ -699,7 +691,7 @@ public async Task NoConstructorHandlingWhenObjectHasConverter() public async Task ConstructorHandlingHonorsCustomConverters() { // Baseline, use internal converters for primitives - Point_2D point = await Serializer.DeserializeWrapper(@"{""X"":2,""Y"":3}"); + Point_2D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":2,""Y"":3}"); Assert.Equal(2, point.X); Assert.Equal(3, point.Y); @@ -707,7 +699,7 @@ public async Task ConstructorHandlingHonorsCustomConverters() var options = new JsonSerializerOptions(); options.Converters.Add(new ConverterForInt32()); - point = await Serializer.DeserializeWrapper(@"{""X"":2,""Y"":3}", options); + point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":2,""Y"":3}", options); Assert.Equal(25, point.X); Assert.Equal(25, point.X); } @@ -728,7 +720,7 @@ async Task RunTestAsync() string input = sb.ToString(); - object obj = await Serializer.DeserializeWrapper(input); + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(input); for (int i = 0; i < 64; i++) { Assert.Equal(i, (int)typeof(T).GetProperty($"Int{i}").GetValue(obj)); @@ -766,17 +758,11 @@ async Task RunTestAsync() sb.Append("Int32"); sb.Append(")"); - string ctorAsString = sb.ToString(); - - NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(input)); - string strEx = ex.ToString(); - Assert.Contains(ctorAsString, strEx); - Assert.Contains(type.ToString(), strEx); + NotSupportedException ex = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper(input)); + Assert.Contains(type.ToString(), ex.ToString()); - ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}")); - strEx = ex.ToString(); - Assert.Contains(ctorAsString, strEx); - Assert.Contains(type.ToString(), strEx); + ex = await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{}")); + Assert.Contains(type.ToString(), ex.ToString()); } await RunTestAsync(); @@ -786,14 +772,14 @@ async Task RunTestAsync() [Fact] public async Task Deserialize_ObjectWith_Ctor_With_65_Params_IfNull() { - Assert.Null(await Serializer.DeserializeWrapper("null")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("null")); + Assert.Null(await JsonSerializerWrapperForString.DeserializeWrapper("null")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("null")); } [Fact] public async Task Escaped_ParameterNames_Work() { - Point_2D point = await Serializer.DeserializeWrapper(@"{""\u0058"":1,""\u0059"":2}"); + Point_2D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""\u0058"":1,""\u0059"":2}"); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); } @@ -801,12 +787,15 @@ public async Task Escaped_ParameterNames_Work() [Fact] public async Task LastParameterWins() { - Point_2D point = await Serializer.DeserializeWrapper(@"{""X"":1,""Y"":2,""X"":4}"); + Point_2D point = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""X"":1,""Y"":2,""X"":4}"); Assert.Equal(4, point.X); // Not 1. Assert.Equal(2, point.Y); } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task LastParameterWins_DoesNotGoToExtensionData() { string json = @"{ @@ -818,7 +807,7 @@ public async Task LastParameterWins_DoesNotGoToExtensionData() ""Id"":""63cf821d-fd47-4782-8345-576d9228a534"" }"; - Parameterized_Person person = await Serializer.DeserializeWrapper(json); + Parameterized_Person person = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal("Jet", person.FirstName); Assert.Equal("Doe", person.LastName); Assert.Equal("63cf821d-fd47-4782-8345-576d9228a534", person.Id.ToString()); @@ -829,23 +818,26 @@ public async Task LastParameterWins_DoesNotGoToExtensionData() [Fact] public async Task BitVector32_UsesStructDefaultCtor_MultipleParameterizedCtor() { - string serialized = JsonSerializer.Serialize(new BitVector32(1)); - Assert.Equal(0, (await Serializer.DeserializeWrapper(serialized)).Data); + string serialized = await JsonSerializerWrapperForString.SerializeWrapper(new BitVector32(1)); + Assert.Equal(0, (await JsonSerializerWrapperForString.DeserializeWrapper(serialized)).Data); } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task HonorExtensionDataGeneric() { - var obj1 = await Serializer.DeserializeWrapper(@"{""key"": ""value""}"); + var obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""key"": ""value""}"); Assert.Equal("value", obj1.ExtensionData["key"].GetString()); - var obj2 = await Serializer.DeserializeWrapper(@"{""key"": ""value""}"); + var obj2 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""key"": ""value""}"); Assert.Equal("value", ((JsonElement)obj2.ExtensionData["key"]).GetString()); - var obj3 = await Serializer.DeserializeWrapper(@"{""key"": ""value""}"); + var obj3 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""key"": ""value""}"); Assert.Equal("value", obj3.ExtensionData["key"].GetString()); - var obj4 = await Serializer.DeserializeWrapper(@"{""key"": ""value""}"); + var obj4 = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""key"": ""value""}"); Assert.Equal("value", ((JsonElement)obj4.ExtensionData["key"]).GetString()); } @@ -854,24 +846,24 @@ public async Task ArgumentDeserialization_Honors_JsonInclude() { Point_MembersHave_JsonInclude point = new Point_MembersHave_JsonInclude(1, 2,3); - string json = JsonSerializer.Serialize(point); + string json = await JsonSerializerWrapperForString.SerializeWrapper(point); Assert.Contains(@"""X"":1", json); Assert.Contains(@"""Y"":2", json); //We should add another test for non-public members //when https://github.com/dotnet/runtime/issues/31511 is implemented Assert.Contains(@"""Z"":3", json); - point = await Serializer.DeserializeWrapper(json); + point = await JsonSerializerWrapperForString.DeserializeWrapper(json); point.Verify(); } [Fact] public async Task ArgumentDeserialization_Honors_JsonNumberHandling() { - ClassWithFiveArgs_MembersHave_JsonNumberHandlingAttributes obj = await Serializer.DeserializeWrapper(ClassWithFiveArgs_MembersHave_JsonNumberHandlingAttributes.s_json); + ClassWithFiveArgs_MembersHave_JsonNumberHandlingAttributes obj = await JsonSerializerWrapperForString.DeserializeWrapper(ClassWithFiveArgs_MembersHave_JsonNumberHandlingAttributes.s_json); obj.Verify(); - string json = JsonSerializer.Serialize(obj); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj); Assert.Contains(@"""A"":1", json); Assert.Contains(@"""B"":""NaN""", json); Assert.Contains(@"""C"":2", json); @@ -884,11 +876,11 @@ public async Task ArgumentDeserialization_Honors_JsonPropertyName() { Point_MembersHave_JsonPropertyName point = new Point_MembersHave_JsonPropertyName(1, 2); - string json = JsonSerializer.Serialize(point); + string json = await JsonSerializerWrapperForString.SerializeWrapper(point); Assert.Contains(@"""XValue"":1", json); Assert.Contains(@"""YValue"":2", json); - point = await Serializer.DeserializeWrapper(json); + point = await JsonSerializerWrapperForString.DeserializeWrapper(json); point.Verify(); } @@ -898,10 +890,10 @@ public async Task ArgumentDeserialization_Honors_JsonPropertyName_CaseInsensitiv string json = @"{""XVALUE"":1,""yvalue"":2}"; // Without case insensitivity, there's no match. - Point_MembersHave_JsonPropertyName point = await Serializer.DeserializeWrapper(json); + Point_MembersHave_JsonPropertyName point = await JsonSerializerWrapperForString.DeserializeWrapper(json); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - point = await Serializer.DeserializeWrapper(json, options); + point = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); } @@ -909,14 +901,14 @@ public async Task ArgumentDeserialization_Honors_JsonPropertyName_CaseInsensitiv [Fact] public async Task ArgumentDeserialization_Honors_ConverterOnProperty() { - var point = await Serializer.DeserializeWrapper(Point_MembersHave_JsonConverter.s_json); + var point = await JsonSerializerWrapperForString.DeserializeWrapper(Point_MembersHave_JsonConverter.s_json); point.Verify(); } [Fact] public async Task ArgumentDeserialization_Honors_JsonIgnore() { - var point = await Serializer.DeserializeWrapper(Point_MembersHave_JsonIgnore.s_json); + var point = await JsonSerializerWrapperForString.DeserializeWrapper(Point_MembersHave_JsonIgnore.s_json); point.Verify(); } @@ -928,14 +920,14 @@ public async Task ArgumentDeserialization_UseNamingPolicy_ToMatch() PropertyNamingPolicy = new LowerCaseNamingPolicy() }; - string json = JsonSerializer.Serialize(new Point_ExtendedPropNames(1, 2), options); + string json = await JsonSerializerWrapperForString.SerializeWrapper(new Point_ExtendedPropNames(1, 2), options); // If we don't use naming policy, then we can't match serialized properties to constructor parameters on deserialization. - var point = await Serializer.DeserializeWrapper(json); + var point = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(0, point.XValue); Assert.Equal(0, point.YValue); - point = await Serializer.DeserializeWrapper(json, options); + point = await JsonSerializerWrapperForString.DeserializeWrapper(json, options); Assert.Equal(1, point.XValue); Assert.Equal(2, point.YValue); } @@ -951,7 +943,7 @@ public async Task ArgumentDeserialization_UseNamingPolicy_ToMatch_CaseInsensitiv string json = @"{""x_VaLUE"":1,""Y_vALue"":2}"; // If we don't use case sensitivity, then we can't match serialized properties to constructor parameters on deserialization. - Point_ExtendedPropNames point = await Serializer.DeserializeWrapper(json, options1); + Point_ExtendedPropNames point = await JsonSerializerWrapperForString.DeserializeWrapper(json, options1); Assert.Equal(0, point.XValue); Assert.Equal(0, point.YValue); @@ -961,7 +953,7 @@ public async Task ArgumentDeserialization_UseNamingPolicy_ToMatch_CaseInsensitiv PropertyNameCaseInsensitive = true, }; - point = await Serializer.DeserializeWrapper(json, options2); + point = await JsonSerializerWrapperForString.DeserializeWrapper(json, options2); Assert.Equal(1, point.XValue); Assert.Equal(2, point.YValue); } @@ -974,19 +966,19 @@ public async Task ArgumentDeserialization_UseNamingPolicy_InvalidPolicyFails() PropertyNamingPolicy = new NullNamingPolicy() }; - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}", options)); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{}", options)); } [Fact] public async Task ComplexJson_As_LastCtorArg() { - Point_With_Array obj1 = await Serializer.DeserializeWrapper(Point_With_Array.s_json); + Point_With_Array obj1 = await JsonSerializerWrapperForString.DeserializeWrapper(Point_With_Array.s_json); ((ITestClass)obj1).Verify(); - Point_With_Dictionary obj2 = await Serializer.DeserializeWrapper(Point_With_Dictionary.s_json); + Point_With_Dictionary obj2 = await JsonSerializerWrapperForString.DeserializeWrapper(Point_With_Dictionary.s_json); ((ITestClass)obj2).Verify(); - Point_With_Object obj3 = await Serializer.DeserializeWrapper(Point_With_Object.s_json); + Point_With_Object obj3 = await JsonSerializerWrapperForString.DeserializeWrapper(Point_With_Object.s_json); ((ITestClass)obj3).Verify(); } @@ -1008,7 +1000,7 @@ public async Task NumerousPropertiesWork() string json = sb.ToString(); - var point = await Serializer.DeserializeWrapper(json); + var point = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(1, point.X); Assert.Equal(2, point.Y); Assert.Equal(66, point.Z); @@ -1021,9 +1013,9 @@ public async Task ArgumentStateNotOverwritten() ClassWithNestedClass obj1 = new ClassWithNestedClass(myClass: obj, myPoint: new Point_2D_Struct_WithAttribute(1, 2)); ClassWithNestedClass obj2 = new ClassWithNestedClass(myClass: obj1, myPoint: new Point_2D_Struct_WithAttribute(3, 4)); - string json = JsonSerializer.Serialize(obj2); + string json = await JsonSerializerWrapperForString.SerializeWrapper(obj2); - obj2 = await Serializer.DeserializeWrapper(json); + obj2 = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(3, obj2.MyPoint.X); Assert.Equal(4, obj2.MyPoint.Y); @@ -1041,9 +1033,9 @@ public async Task ArgumentStateNotOverwritten() [Fact] public async Task FourArgsWork() { - string json = JsonSerializer.Serialize(new StructWithFourArgs(1, 2, 3, 4)); + string json = await JsonSerializerWrapperForString.SerializeWrapper(new StructWithFourArgs(1, 2, 3, 4)); - var obj = await Serializer.DeserializeWrapper(json); + var obj = await JsonSerializerWrapperForString.DeserializeWrapper(json); Assert.Equal(1, obj.W); Assert.Equal(2, obj.X); Assert.Equal(3, obj.Y); @@ -1053,15 +1045,15 @@ public async Task FourArgsWork() [Fact] public async Task InvalidJsonFails() { - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{1")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{x")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{{")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{true")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{1")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{x")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{{")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{true")); // Also test deserialization of objects with parameterless ctors - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{1")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{x")); - await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{true")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{1")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{x")); + await Assert.ThrowsAsync(() => JsonSerializerWrapperForString.DeserializeWrapper("{true")); } [Fact] @@ -1103,40 +1095,40 @@ public void AnonymousObject_NamingPolicy() Assert.Equal(5, objType.GetProperty("Prop").GetValue(newObj)); } - private record MyRecord(int Prop); + public record MyRecord(int Prop); [Fact] - public void Record() + public async Task Record() { // 'Prop' property binds with a ctor arg called 'Prop'. - MyRecord obj = JsonSerializer.Deserialize("{}"); + MyRecord obj = await JsonSerializerWrapperForString.DeserializeWrapper("{}"); Assert.Equal(0, obj.Prop); - obj = JsonSerializer.Deserialize(@"{""Prop"":5}"); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""Prop"":5}"); Assert.Equal(5, obj.Prop); } - private record AgeRecord(int age) + public record AgeRecord(int age) { public string Age { get; set; } = age.ToString(); } [Fact] - public void RecordWithSamePropertyNameDifferentTypes() + public async Task RecordWithSamePropertyNameDifferentTypes() { - AgeRecord obj = JsonSerializer.Deserialize(@"{""age"":1}"); + AgeRecord obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""age"":1}"); Assert.Equal(1, obj.age); } - private record MyRecordWithUnboundCtorProperty(int IntProp1, int IntProp2) + public record MyRecordWithUnboundCtorProperty(int IntProp1, int IntProp2) { public string StringProp { get; set; } } [Fact] - public void RecordWithAdditionalProperty() + public async Task RecordWithAdditionalProperty() { - MyRecordWithUnboundCtorProperty obj = JsonSerializer.Deserialize( + MyRecordWithUnboundCtorProperty obj = await JsonSerializerWrapperForString.DeserializeWrapper( @"{""IntProp1"":1,""IntProp2"":2,""StringProp"":""hello""}"); Assert.Equal(1, obj.IntProp1); @@ -1147,14 +1139,14 @@ public void RecordWithAdditionalProperty() } [Fact] - public void Record_NamingPolicy() + public async Task Record_NamingPolicy() { const string Json = @"{""prop"":5}"; // 'Prop' property binds with a ctor arg called 'Prop'. // Verify no match if no naming policy - MyRecord obj = JsonSerializer.Deserialize(Json); + MyRecord obj = await JsonSerializerWrapperForString.DeserializeWrapper(Json); Assert.Equal(0, obj.Prop); var options = new JsonSerializerOptions @@ -1163,11 +1155,11 @@ public void Record_NamingPolicy() }; // Verify match with naming policy - obj = JsonSerializer.Deserialize(Json, options); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(Json, options); Assert.Equal(5, obj.Prop); } - private class AgePoco + public class AgePoco { public AgePoco(int age) { @@ -1179,36 +1171,36 @@ public AgePoco(int age) } [Fact] - public void PocoWithSamePropertyNameDifferentTypes() + public async Task PocoWithSamePropertyNameDifferentTypes() { - AgePoco obj = JsonSerializer.Deserialize(@"{""age"":1}"); + AgePoco obj = await JsonSerializerWrapperForString.DeserializeWrapper(@"{""age"":1}"); Assert.Equal(1, obj.age); } [Theory] [InlineData(typeof(TypeWithGuid))] [InlineData(typeof(TypeWithNullableGuid))] - public void DefaultForValueTypeCtorParam(Type type) + public async Task DefaultForValueTypeCtorParam(Type type) { string json = @"{""MyGuid"":""edc421bf-782a-4a95-ad67-3d73b5d7db6f""}"; - object obj = JsonSerializer.Deserialize(json, type); - Assert.Equal(json, JsonSerializer.Serialize(obj)); + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type); + Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj)); json = @"{}"; - obj = JsonSerializer.Deserialize(json, type); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, type); var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; - Assert.Equal(json, JsonSerializer.Serialize(obj, options)); + Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); } - private class TypeWithGuid + public class TypeWithGuid { public Guid MyGuid { get; } public TypeWithGuid(Guid myGuid = default) => MyGuid = myGuid; } - private struct TypeWithNullableGuid + public struct TypeWithNullableGuid { public Guid? MyGuid { get; } @@ -1217,20 +1209,20 @@ private struct TypeWithNullableGuid } [Fact] - public void DefaultForReferenceTypeCtorParam() + public async Task DefaultForReferenceTypeCtorParam() { string json = @"{""MyUri"":""http://hello""}"; - object obj = JsonSerializer.Deserialize(json, typeof(TypeWithUri)); - Assert.Equal(json, JsonSerializer.Serialize(obj)); + object obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, typeof(TypeWithUri)); + Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj)); json = @"{}"; - obj = JsonSerializer.Deserialize(json, typeof(TypeWithUri)); + obj = await JsonSerializerWrapperForString.DeserializeWrapper(json, typeof(TypeWithUri)); var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; - Assert.Equal(json, JsonSerializer.Serialize(obj, options)); + Assert.Equal(json, await JsonSerializerWrapperForString.SerializeWrapper(obj, options)); } - private class TypeWithUri + public class TypeWithUri { public Uri MyUri { get; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Stream.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs similarity index 94% rename from src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Stream.cs rename to src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs index 8f0f166012947..ac5cd60933bf9 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests/ConstructorTests.Stream.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.Stream.cs @@ -12,6 +12,9 @@ public abstract partial class ConstructorTests { [Fact] [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", RuntimeConfiguration.Checked)] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task ReadSimpleObjectAsync() { async Task RunTestAsync(byte[] testData) @@ -23,7 +26,7 @@ async Task RunTestAsync(byte[] testData) DefaultBufferSize = 1 }; - var obj = await JsonSerializer.DeserializeAsync(stream, options); + var obj = await JsonSerializerWrapperForStream.DeserializeWrapper(stream, options); ((ITestClass)obj).Verify(); } } @@ -59,6 +62,9 @@ async Task RunTestAsync(byte[] testData) } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task ReadSimpleObjectWithTrailingTriviaAsync() { async Task RunTestAsync(string testData) @@ -72,7 +78,7 @@ async Task RunTestAsync(string testData) ReadCommentHandling = JsonCommentHandling.Skip, }; - var obj = await JsonSerializer.DeserializeAsync(stream, options); + var obj = await JsonSerializerWrapperForStream.DeserializeWrapper(stream, options); ((ITestClass)obj).Verify(); } } @@ -128,7 +134,7 @@ async Task RunTestAsync() DefaultBufferSize = 1 }; - await Assert.ThrowsAsync(async () => await JsonSerializer.DeserializeAsync(stream, options)); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForStream.DeserializeWrapper(stream, options)); } using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("{}"))) @@ -138,7 +144,7 @@ async Task RunTestAsync() DefaultBufferSize = 1 }; - await Assert.ThrowsAsync(async () => await JsonSerializer.DeserializeAsync(stream, options)); + await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForStream.DeserializeWrapper(stream, options)); } } @@ -151,6 +157,9 @@ async Task RunTestAsync() } [Fact] +#if BUILDING_SOURCE_GENERATOR_TESTS + [ActiveIssue("Needs JsonExtensionData support.")] +#endif public async Task ExerciseStreamCodePaths() { static string GetPropertyName(int index) => @@ -207,7 +216,7 @@ static byte[] GeneratePayload(int i, string value) DefaultBufferSize = 1 }; - ClassWithStrings obj = await JsonSerializer.DeserializeAsync(stream, options); + ClassWithStrings obj = await JsonSerializerWrapperForStream.DeserializeWrapper(stream, options); obj.Verify(value); } } diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForStream.cs b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForStream.cs index d3e9994c6e77c..7b068bd5808a6 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForStream.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapperForStream.cs @@ -12,11 +12,11 @@ namespace System.Text.Json.Serialization.Tests /// public abstract partial class JsonSerializerWrapperForStream { - protected internal abstract Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions options = null); - protected internal abstract Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions options = null); + protected internal abstract Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions? options = null); + protected internal abstract Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions? options = null); protected internal abstract Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo); - protected internal abstract Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null); - protected internal abstract Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null); + protected internal abstract Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null); + protected internal abstract Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions? options = null); protected internal abstract Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs index 9eaf4a4beab28..72af4d22fe3fa 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs @@ -67,14 +67,13 @@ public JsonTypeInfo JsonMessage { if (_JsonMessage == null) { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo( - Options, - createObjectFunc: static () => new JsonMessage(), - propInitFunc: null, - default, - serializeFunc: JsonMessageSerialize); + JsonObjectInfoValues objectInfo = new() + { + ObjectCreator = static () => new JsonMessage(), + SerializeHandler = JsonMessageSerialize + }; - _JsonMessage = objectInfo; + _JsonMessage = JsonMetadataServices.CreateObjectInfo(Options, objectInfo); } return _JsonMessage; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs index 79d4378203b55..49deae9acadd7 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs @@ -59,6 +59,18 @@ static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInst new RemoteInvokeOptions() { ExpectedExitCode = 0 }).Dispose(); } + [Fact] + public static void SupportsPositionalRecords() + { + Person person = new(FirstName: "Jane", LastName: "Doe"); + + byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(person, PersonJsonContext.Default.Person); + + person = JsonSerializer.Deserialize(utf8Json, PersonJsonContext.Default.Person); + Assert.Equal("Jane", person.FirstName); + Assert.Equal("Doe", person.LastName); + } + [JsonSerializable(typeof(JsonMessage))] internal partial class NestedContext : JsonSerializerContext { } @@ -68,5 +80,14 @@ public partial class NestedPublicContext : JsonSerializerContext [JsonSerializable(typeof(JsonMessage))] protected internal partial class NestedProtectedInternalClass : JsonSerializerContext { } } + + internal record Person(string FirstName, string LastName); + + [JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] + [JsonSerializable(typeof(Person))] + internal partial class PersonJsonContext : JsonSerializerContext + { + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs index 8831e42a66f87..ca941bea91590 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs @@ -491,8 +491,9 @@ public virtual void ParameterizedConstructor() Assert.Contains(@"""High"":1", json); Assert.Contains(@"""Low"":2", json); - // Deserialization not supported for now. - Assert.Throws(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable)); + HighLowTempsImmutable obj = JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable); + Assert.Equal(1, obj.High); + Assert.Equal(2, obj.Low); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs index 8d581aed66ea4..4c9abd3f44005 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs @@ -9,6 +9,8 @@ using System.Collections.Specialized; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Tests; +using System.Threading.Tasks; +using Xunit; namespace System.Text.Json.SourceGeneration.Tests { @@ -20,10 +22,81 @@ public CollectionTests_Metadata() } protected CollectionTests_Metadata(JsonSerializerWrapperForString serializerWrapper) - : base(serializerWrapper, new StreamSerializerWrapper()) + : base(serializerWrapper, null) { } + [Fact] + public override async Task ReadSimpleKeyValuePairPartialData() + { + KeyValuePair kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": ""123""}"); + Assert.Equal("123", kvp.Key); + Assert.Equal(0, kvp.Value); + + kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": ""Key"", ""Value"": 123, ""Value2"": 456}"); + Assert.Equal("Key", kvp.Key); + Assert.Equal(123, kvp.Value); + + kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(@"{""Key"": ""Key"", ""Val"": 123}"); + Assert.Equal("Key", kvp.Key); + Assert.Equal(0, kvp.Value); + } + + [Fact] + public override async Task HonorCLRProperties() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = new LeadingUnderscorePolicy() // Key -> _Key, Value -> _Value + }; + + // Since object converter (not KVP converter) is used, payloads not compliant with naming policy won't yield matches. + string json = @"{""Key"":""Hello, World!"",""Value"":1}"; + KeyValuePair kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(json, options); + Assert.Null(kvp.Key); + Assert.Equal(0, kvp.Value); + + // "Key" and "Value" matching is case sensitive. + json = @"{""key"":""Hello, World!"",""value"":1}"; + kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(json, options); + Assert.Null(kvp.Key); + Assert.Equal(0, kvp.Value); + + // "Key" and "Value" matching is case sensitive, even when case insensitivity is on. + // Case sensitivity only applies to the result of converting the CLR property names + // (Key -> _Key, Value -> _Value) with the naming policy. + options = new JsonSerializerOptions + { + PropertyNamingPolicy = new LeadingUnderscorePolicy(), + PropertyNameCaseInsensitive = true + }; + + kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(json, options); + Assert.Null(kvp.Key); + Assert.Equal(0, kvp.Value); + } + + [Fact] + public override async Task HonorNamingPolicy_CaseInsensitive() + { + const string json = @"{""key"":""Hello, World!"",""value"":1}"; + + // Baseline - with case-sensitive matching, the payload doesn't have mapping properties. + KeyValuePair kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(json); + Assert.Null(kvp.Key); + Assert.Equal(0, kvp.Value); + + // Test - with case-insensitivity on, we have property matches. + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + kvp = await JsonSerializerWrapperForString.DeserializeWrapper>(json, options); + Assert.Equal("Hello, World!", kvp.Key); + Assert.Equal(1, kvp.Value); + } + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ConcurrentDictionary))] [JsonSerializable(typeof(ConcurrentQueue))] @@ -373,6 +446,22 @@ protected CollectionTests_Metadata(JsonSerializerWrapperForString serializerWrap [JsonSerializable(typeof(ReadOnlyWrapperForIDictionary))] [JsonSerializable(typeof(ReadOnlyStringToStringIDictionaryWrapper))] [JsonSerializable(typeof(Dictionary[]))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(SimpleClassWithKeyValuePairs))] + [JsonSerializable(typeof(KeyNameNullPolicy))] + [JsonSerializable(typeof(ValueNameNullPolicy))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(StackWrapper))] internal sealed partial class CollectionTestsContext_Metadata : JsonSerializerContext { } @@ -733,6 +822,22 @@ public CollectionTests_Default() [JsonSerializable(typeof(ReadOnlyWrapperForIDictionary))] [JsonSerializable(typeof(ReadOnlyStringToStringIDictionaryWrapper))] [JsonSerializable(typeof(Dictionary[]))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(SimpleClassWithKeyValuePairs))] + [JsonSerializable(typeof(KeyNameNullPolicy))] + [JsonSerializable(typeof(ValueNameNullPolicy))] + [JsonSerializable(typeof(KeyValuePair))] + [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(StackWrapper))] internal sealed partial class CollectionTestsContext_Default : JsonSerializerContext { } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs new file mode 100644 index 0000000000000..01a7e4d0e39f9 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs @@ -0,0 +1,244 @@ +// 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.Generic; +using System.Collections.Specialized; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Tests; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public partial class ConstructorTests_Metadata : ConstructorTests + { + public ConstructorTests_Metadata() + : this( + new StringSerializerWrapper(ConstructorTestsContext_Metadata.Default, (options) => new ConstructorTestsContext_Metadata(options)), + new StreamSerializerWrapper(ConstructorTestsContext_Metadata.Default, (options) => new ConstructorTestsContext_Metadata(options))) + { + } + + protected ConstructorTests_Metadata(JsonSerializerWrapperForString stringWrapper, JsonSerializerWrapperForStream streamWrapper) + : base(stringWrapper, streamWrapper) + { + } + + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(PrivateParameterlessCtor))] + [JsonSerializable(typeof(InternalParameterlessCtor))] + [JsonSerializable(typeof(ProtectedParameterlessCtor))] + [JsonSerializable(typeof(PrivateParameterizedCtor))] + [JsonSerializable(typeof(InternalParameterizedCtor))] + [JsonSerializable(typeof(ProtectedParameterizedCtor))] + [JsonSerializable(typeof(PrivateParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(InternalParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(ProtectedParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(SinglePublicParameterizedCtor))] + [JsonSerializable(typeof(SingleParameterlessCtor_MultiplePublicParameterizedCtor))] + [JsonSerializable(typeof(SingleParameterlessCtor_MultiplePublicParameterizedCtor_Struct))] + [JsonSerializable(typeof(PublicParameterizedCtor))] + [JsonSerializable(typeof(PrivateParameterlessConstructor_PublicParameterizedCtor))] + [JsonSerializable(typeof(PublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(Struct_PublicParameterizedConstructor_WithAttribute))] + [JsonSerializable(typeof(PrivateParameterlessConstructor_PublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor_Struct))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor_WithAttribute_Struct))] + [JsonSerializable(typeof(ParameterlessCtor_MultiplePublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(Parameterized_StackWrapper))] + [JsonSerializable(typeof(Parameterized_WrapperForICollection))] + [JsonSerializable(typeof(Point_2D_Struct))] + [JsonSerializable(typeof(Point_2D_Struct_WithAttribute))] + [JsonSerializable(typeof(ClassWithConstructor_SimpleAndComplexParameters))] + [JsonSerializable(typeof(Person_Class))] + [JsonSerializable(typeof(Point_2D))] + [JsonSerializable(typeof(Point_MultipleMembers_BindTo_OneConstructorParameter))] + [JsonSerializable(typeof(Point_MultipleMembers_BindTo_OneConstructorParameter_Variant))] + [JsonSerializable(typeof(Url_BindTo_OneConstructorParameter))] + [JsonSerializable(typeof(Point_Without_Members))] + [JsonSerializable(typeof(Point_With_MismatchedMembers))] + [JsonSerializable(typeof(WrapperFor_Point_With_MismatchedMembers))] + [JsonSerializable(typeof(Employee))] + [JsonSerializable(typeof(Class_ExtData_CtorParam))] + [JsonSerializable(typeof(ClassWithUnicodePropertyName))] + [JsonSerializable(typeof(RootClass))] + [JsonSerializable(typeof(Parameterized_ClassWithUnicodeProperty))] + [JsonSerializable(typeof(Parameterized_ClassWithExtensionProperty))] + [JsonSerializable(typeof(Point_3D))] + [JsonSerializable(typeof(Point_2D_With_ExtData))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(WrapperForPoint_3D))] + [JsonSerializable(typeof(ClassWrapperForPoint_3D))] + [JsonSerializable(typeof(ClassWrapper_For_Int_String))] + [JsonSerializable(typeof(ClassWrapper_For_Int_Point_3D_String))] + [JsonSerializable(typeof(Person_Class))] + [JsonSerializable(typeof(Person_Struct))] + [JsonSerializable(typeof(Point_CtorsIgnoreJson))] + [JsonSerializable(typeof(NullArgTester))] + [JsonSerializable(typeof(NullArgTester_Mutable))] + [JsonSerializable(typeof(Parameterless_ClassWithPrimitives))] + [JsonSerializable(typeof(Parameterized_ClassWithPrimitives_3Args))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(TupleWrapper))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Point_3D[]))] + [JsonSerializable(typeof(Struct_With_Ctor_With_64_Params))] + [JsonSerializable(typeof(Class_With_Ctor_With_64_Params))] + [JsonSerializable(typeof(Class_With_Ctor_With_65_Params))] + [JsonSerializable(typeof(Struct_With_Ctor_With_65_Params))] + [JsonSerializable(typeof(Parameterized_Person))] + [JsonSerializable(typeof(BitVector32))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_GenericDictionary_JsonElementExt))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_GenericDictionary_ObjectExt))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_JsonElementExt))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_ObjectExt))] + [JsonSerializable(typeof(Point_MembersHave_JsonInclude))] + [JsonSerializable(typeof(ClassWithFiveArgs_MembersHave_JsonNumberHandlingAttributes))] + [JsonSerializable(typeof(Point_MembersHave_JsonPropertyName))] + [JsonSerializable(typeof(Point_MembersHave_JsonConverter))] + [JsonSerializable(typeof(Point_MembersHave_JsonIgnore))] + [JsonSerializable(typeof(Point_ExtendedPropNames))] + [JsonSerializable(typeof(Point_With_Array))] + [JsonSerializable(typeof(Point_With_Dictionary))] + [JsonSerializable(typeof(Point_With_Object))] + [JsonSerializable(typeof(Point_With_Property))] + [JsonSerializable(typeof(ClassWithNestedClass))] + [JsonSerializable(typeof(StructWithFourArgs))] + [JsonSerializable(typeof(TypeWithGuid))] + [JsonSerializable(typeof(TypeWithNullableGuid))] + [JsonSerializable(typeof(TypeWithUri))] + [JsonSerializable(typeof(Parameterized_IndexViewModel_Immutable))] + [JsonSerializable(typeof(Parameterized_Person_ObjExtData))] + [JsonSerializable(typeof(ClassWithStrings))] + [JsonSerializable(typeof(Point_3D_Struct))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Employee))] + [JsonSerializable(typeof(AgePoco))] + [JsonSerializable(typeof(MyRecordWithUnboundCtorProperty))] + [JsonSerializable(typeof(MyRecord))] + [JsonSerializable(typeof(AgeRecord))] + [JsonSerializable(typeof(JsonElement))] + internal sealed partial class ConstructorTestsContext_Metadata : JsonSerializerContext + { + } + } + + public partial class ConstructorTests_Default : ConstructorTests_Metadata + { + public ConstructorTests_Default() + : base( + new StringSerializerWrapper(ConstructorTestsContext_Default.Default, (options) => new ConstructorTestsContext_Default(options)), + new StreamSerializerWrapper(ConstructorTestsContext_Default.Default, (options) => new ConstructorTestsContext_Default(options))) + { + } + + [JsonSerializable(typeof(PrivateParameterlessCtor))] + [JsonSerializable(typeof(InternalParameterlessCtor))] + [JsonSerializable(typeof(ProtectedParameterlessCtor))] + [JsonSerializable(typeof(PrivateParameterizedCtor))] + [JsonSerializable(typeof(InternalParameterizedCtor))] + [JsonSerializable(typeof(ProtectedParameterizedCtor))] + [JsonSerializable(typeof(PrivateParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(InternalParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(ProtectedParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(SinglePublicParameterizedCtor))] + [JsonSerializable(typeof(SingleParameterlessCtor_MultiplePublicParameterizedCtor))] + [JsonSerializable(typeof(SingleParameterlessCtor_MultiplePublicParameterizedCtor_Struct))] + [JsonSerializable(typeof(PublicParameterizedCtor))] + [JsonSerializable(typeof(PrivateParameterlessConstructor_PublicParameterizedCtor))] + [JsonSerializable(typeof(PublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(Struct_PublicParameterizedConstructor_WithAttribute))] + [JsonSerializable(typeof(PrivateParameterlessConstructor_PublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor_Struct))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(MultiplePublicParameterizedCtor_WithAttribute_Struct))] + [JsonSerializable(typeof(ParameterlessCtor_MultiplePublicParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(Parameterized_StackWrapper))] + [JsonSerializable(typeof(Parameterized_WrapperForICollection))] + [JsonSerializable(typeof(Point_2D_Struct))] + [JsonSerializable(typeof(Point_2D_Struct_WithAttribute))] + [JsonSerializable(typeof(ClassWithConstructor_SimpleAndComplexParameters))] + [JsonSerializable(typeof(Person_Class))] + [JsonSerializable(typeof(Point_2D))] + [JsonSerializable(typeof(Point_MultipleMembers_BindTo_OneConstructorParameter))] + [JsonSerializable(typeof(Point_MultipleMembers_BindTo_OneConstructorParameter_Variant))] + [JsonSerializable(typeof(Url_BindTo_OneConstructorParameter))] + [JsonSerializable(typeof(Point_Without_Members))] + [JsonSerializable(typeof(Point_With_MismatchedMembers))] + [JsonSerializable(typeof(WrapperFor_Point_With_MismatchedMembers))] + [JsonSerializable(typeof(Employee))] + [JsonSerializable(typeof(Class_ExtData_CtorParam))] + [JsonSerializable(typeof(ClassWithUnicodePropertyName))] + [JsonSerializable(typeof(RootClass))] + [JsonSerializable(typeof(Parameterized_ClassWithUnicodeProperty))] + [JsonSerializable(typeof(Parameterized_ClassWithExtensionProperty))] + [JsonSerializable(typeof(Point_3D))] + [JsonSerializable(typeof(Point_2D_With_ExtData))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(WrapperForPoint_3D))] + [JsonSerializable(typeof(ClassWrapperForPoint_3D))] + [JsonSerializable(typeof(ClassWrapper_For_Int_String))] + [JsonSerializable(typeof(ClassWrapper_For_Int_Point_3D_String))] + [JsonSerializable(typeof(Person_Class))] + [JsonSerializable(typeof(Person_Struct))] + [JsonSerializable(typeof(Point_CtorsIgnoreJson))] + [JsonSerializable(typeof(NullArgTester))] + [JsonSerializable(typeof(NullArgTester_Mutable))] + [JsonSerializable(typeof(Parameterless_ClassWithPrimitives))] + [JsonSerializable(typeof(Parameterized_ClassWithPrimitives_3Args))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(TupleWrapper))] + [JsonSerializable(typeof(List>))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Point_3D[]))] + [JsonSerializable(typeof(Struct_With_Ctor_With_64_Params))] + [JsonSerializable(typeof(Class_With_Ctor_With_64_Params))] + [JsonSerializable(typeof(Class_With_Ctor_With_65_Params))] + [JsonSerializable(typeof(Struct_With_Ctor_With_65_Params))] + [JsonSerializable(typeof(Parameterized_Person))] + [JsonSerializable(typeof(BitVector32))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_GenericDictionary_JsonElementExt))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_GenericDictionary_ObjectExt))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_JsonElementExt))] + [JsonSerializable(typeof(SimpleClassWithParameterizedCtor_Derived_GenericIDictionary_ObjectExt))] + [JsonSerializable(typeof(Point_MembersHave_JsonInclude))] + [JsonSerializable(typeof(ClassWithFiveArgs_MembersHave_JsonNumberHandlingAttributes))] + [JsonSerializable(typeof(Point_MembersHave_JsonPropertyName))] + [JsonSerializable(typeof(Point_MembersHave_JsonConverter))] + [JsonSerializable(typeof(Point_MembersHave_JsonIgnore))] + [JsonSerializable(typeof(Point_ExtendedPropNames))] + [JsonSerializable(typeof(Point_With_Array))] + [JsonSerializable(typeof(Point_With_Dictionary))] + [JsonSerializable(typeof(Point_With_Object))] + [JsonSerializable(typeof(Point_With_Property))] + [JsonSerializable(typeof(ClassWithNestedClass))] + [JsonSerializable(typeof(StructWithFourArgs))] + [JsonSerializable(typeof(TypeWithGuid))] + [JsonSerializable(typeof(TypeWithNullableGuid))] + [JsonSerializable(typeof(TypeWithUri))] + [JsonSerializable(typeof(Parameterized_IndexViewModel_Immutable))] + [JsonSerializable(typeof(Parameterized_Person_ObjExtData))] + [JsonSerializable(typeof(ClassWithStrings))] + [JsonSerializable(typeof(Point_3D_Struct))] + [JsonSerializable(typeof(Tuple))] + [JsonSerializable(typeof(Employee))] + [JsonSerializable(typeof(AgePoco))] + [JsonSerializable(typeof(MyRecordWithUnboundCtorProperty))] + [JsonSerializable(typeof(MyRecord))] + [JsonSerializable(typeof(AgeRecord))] + [JsonSerializable(typeof(JsonElement))] + internal sealed partial class ConstructorTestsContext_Default : JsonSerializerContext + { + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs index 6f832228b68e6..9e53057495050 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.cs @@ -122,7 +122,33 @@ protected internal override Task DeserializeWrapper(string json, Type ty internal sealed class StreamSerializerWrapper : JsonSerializerWrapperForStream { - protected internal override Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null) => throw new NotImplementedException(); + private readonly JsonSerializerContext _defaultContext; + private readonly Func _customContextCreator; + + public StreamSerializerWrapper(JsonSerializerContext defaultContext, Func customContextCreator) + { + _defaultContext = defaultContext ?? throw new ArgumentNullException(nameof(defaultContext)); + _customContextCreator = customContextCreator ?? throw new ArgumentNullException(nameof(defaultContext)); + } + + protected internal override async Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null) + { + if (options != null) + { + return await Deserialize(utf8Json, options); + } + + JsonTypeInfo typeInfo = (JsonTypeInfo)_defaultContext.GetTypeInfo(typeof(T)); + return await JsonSerializer.DeserializeAsync(utf8Json, typeInfo); + } + + private async Task Deserialize(Stream utf8Json, JsonSerializerOptions options) + { + JsonSerializerContext context = _customContextCreator(new JsonSerializerOptions(options)); + JsonTypeInfo typeInfo = (JsonTypeInfo)context.GetTypeInfo(typeof(T)); + return await JsonSerializer.DeserializeAsync(utf8Json, typeInfo); + } + protected internal override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null) => throw new NotImplementedException(); protected internal override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) => throw new NotImplementedException(); protected internal override Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions options = null) => throw new NotImplementedException(); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj index 9b8dc0b1592d7..3fe38b92f5084 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -33,6 +33,11 @@ + + + + + @@ -59,6 +64,7 @@ + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests.cs new file mode 100644 index 0000000000000..4afd69c801a41 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ConstructorTests.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json.Serialization.Tests +{ + public class ConstructorTests_String : ConstructorTests + { + public ConstructorTests_String() + : base(JsonSerializerWrapperForString.StringSerializer, JsonSerializerWrapperForStream.SyncStreamSerializer) + { } + } + + public class ConstructorTests_AsyncStream : ConstructorTests + { + public ConstructorTests_AsyncStream() + : base(JsonSerializerWrapperForString.AsyncStreamSerializer, JsonSerializerWrapperForStream.AsyncStreamSerializer) { } + } + + public class ConstructorTests_SyncStream : ConstructorTests + { + public ConstructorTests_SyncStream() + : base(JsonSerializerWrapperForString.SyncStreamSerializer, JsonSerializerWrapperForStream.SyncStreamSerializer) { } + } + + public class ConstructorTests_Span : ConstructorTests + { + public ConstructorTests_Span() + : base(JsonSerializerWrapperForString.SpanSerializer, JsonSerializerWrapperForStream.SyncStreamSerializer) { } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs index 9346a64572ad4..ccfda7413369e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs @@ -606,9 +606,7 @@ public static void TypeWithBadCtorNoProps(Type type) // Each constructor parameter must bind to an object property or field. InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Deserialize("{}", type)); - string exAsStr = ex.ToString(); - Assert.Contains(typeof(SerializationInfo).FullName, exAsStr); - Assert.Contains(typeof(StreamingContext).FullName, exAsStr); + Assert.Contains(type.FullName, ex.ToString()); } [Theory] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs index 0603e82b47cad..aeb4c30fd2cab 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerApiValidation.cs @@ -66,12 +66,7 @@ internal partial class MyDummyContext : JsonSerializerContext public override JsonTypeInfo? GetTypeInfo(Type type) => throw new NotImplementedException(); } - private JsonTypeInfo myDummyTypeInfo = JsonMetadataServices.CreateObjectInfo( - new JsonSerializerOptions(), - createObjectFunc: static () => throw new NotImplementedException(), - propInitFunc: null, - default, - serializeFunc: (Utf8JsonWriter writer, MyPoco value) => throw new NotImplementedException()); + private JsonTypeInfo myDummyTypeInfo = GetTypeInfo(); private JsonSerializerWrapperForString Serializer { get; } @@ -80,6 +75,17 @@ public JsonSerializerApiValidation(JsonSerializerWrapperForString serializer) Serializer = serializer; } + private static JsonTypeInfo GetTypeInfo() + { + JsonObjectInfoValues objectInfo = new() + { + ObjectCreator = static () => throw new NotImplementedException(), + SerializeHandler = (Utf8JsonWriter writer, MyPoco value) => throw new NotImplementedException() + }; + + return JsonMetadataServices.CreateObjectInfo(new JsonSerializerOptions(), objectInfo); + } + [Fact] public async Task DeserializeNullException() { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 0f7121cf6e87e..adf5503a96e75 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -33,6 +33,11 @@ + + + + + @@ -96,11 +101,7 @@ - - - - - +