From 4680404456d04dabba5803b24e3ecda7211011fd Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Sun, 16 May 2021 21:32:55 -0700 Subject: [PATCH 1/8] Add JSON source-gen mode that emits serialization logic --- .../Attributes => Common}/JsonAttribute.cs | 7 +- .../JsonCamelCaseNamingPolicy.cs | 0 .../Common/JsonKnownNamingPolicy.cs | 26 + .../JsonNamingPolicy.cs | 9 +- .../Common/JsonSerializerOptionsAttribute.cs | 53 + .../Common/JsonSourceGenerationMode.cs | 42 + .../gen/ContextGenerationSpec.cs | 34 + .../gen/JsonSourceGenerator.Emitter.cs | 983 ++++++++++++------ .../gen/JsonSourceGenerator.Parser.cs | 354 +++++-- .../gen/JsonSourceGenerator.cs | 25 +- ...yMetadata.cs => PropertyGenerationSpec.cs} | 19 +- .../gen/Reflection/TypeExtensions.cs | 2 + .../gen/SourceGenerationSpec.cs | 31 + .../System.Text.Json.SourceGeneration.csproj | 12 +- .../gen/TypeGenerationSpec.cs | 105 ++ .../System.Text.Json/gen/TypeMetadata.cs | 82 -- .../System.Text.Json/ref/System.Text.Json.cs | 38 +- .../src/System.Text.Json.csproj | 14 +- .../Attributes/JsonSerializableAttribute.cs | 7 +- .../Object/ObjectDefaultConverter.cs | 28 +- .../Object/ObjectSourceGenConverter.cs | 46 - .../Converters/Value/SourceGenConverter.cs | 87 ++ .../Serialization/JsonDefaultNamingPolicy.cs | 10 - .../JsonResumableConverterOfT.cs | 2 - .../JsonSerializer.Write.Helpers.cs | 19 +- .../Serialization/JsonSerializerContext.cs | 22 +- .../Serialization/JsonStringEnumConverter.cs | 2 +- .../JsonMetadataServices.Collections.cs | 31 +- .../Metadata/JsonMetadataServices.cs | 13 +- .../Metadata/JsonTypeInfo.Cache.cs | 9 +- .../Metadata/JsonTypeInfoInternalOfT.cs | 76 +- .../Serialization/Metadata/JsonTypeInfoOfT.cs | 13 + .../tests/Common/JsonTestHelper.cs | 71 ++ ...ourceGeneratorTests.cs => ContextTests.cs} | 216 ++-- .../MetadataAndSerializationContextTests.cs | 57 + .../MetadataContextTests.cs | 56 + .../MixedModeContextTests.cs | 169 +++ .../SerializationContextTests.cs | 268 +++++ ...em.Text.Json.SourceGeneration.Tests.csproj | 7 +- .../TestClasses.cs | 16 - .../CompilationHelper.cs | 12 +- .../JsonSourceGeneratorDiagnosticsTests.cs | 2 - .../JsonSourceGeneratorTests.cs | 48 +- .../TypeWrapperTests.cs | 16 +- .../System.Text.Json.Tests/JsonTestHelper.cs | 61 +- .../System.Text.Json.Tests.csproj | 1 + 46 files changed, 2380 insertions(+), 821 deletions(-) rename src/libraries/System.Text.Json/{src/System/Text/Json/Serialization/Attributes => Common}/JsonAttribute.cs (70%) rename src/libraries/System.Text.Json/{src/System/Text/Json/Serialization => Common}/JsonCamelCaseNamingPolicy.cs (100%) create mode 100644 src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs rename src/libraries/System.Text.Json/{src/System/Text/Json/Serialization => Common}/JsonNamingPolicy.cs (88%) create mode 100644 src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs create mode 100644 src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs create mode 100644 src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs rename src/libraries/System.Text.Json/gen/{PropertyMetadata.cs => PropertyGenerationSpec.cs} (80%) create mode 100644 src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs create mode 100644 src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs delete mode 100644 src/libraries/System.Text.Json/gen/TypeMetadata.cs delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectSourceGenConverter.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs create mode 100644 src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs rename src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/{JsonSourceGeneratorTests.cs => ContextTests.cs} (70%) create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonAttribute.cs b/src/libraries/System.Text.Json/Common/JsonAttribute.cs similarity index 70% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonAttribute.cs rename to src/libraries/System.Text.Json/Common/JsonAttribute.cs index ca6c5ba4966f71..93ca20be75c133 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonAttribute.cs +++ b/src/libraries/System.Text.Json/Common/JsonAttribute.cs @@ -6,5 +6,10 @@ namespace System.Text.Json.Serialization /// /// The base class of serialization attributes. /// - public abstract class JsonAttribute : Attribute { } +#if BUILDING_SOURCE_GENERATOR + internal +#else + public +#endif + abstract class JsonAttribute : Attribute { } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonCamelCaseNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonCamelCaseNamingPolicy.cs similarity index 100% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonCamelCaseNamingPolicy.cs rename to src/libraries/System.Text.Json/Common/JsonCamelCaseNamingPolicy.cs diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs new file mode 100644 index 00000000000000..ec4325968247be --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs @@ -0,0 +1,26 @@ +// 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 +{ + /// + /// The to be used at run-time. + /// +#if BUILDING_SOURCE_GENERATOR + internal +#else + public +#endif + enum JsonKnownNamingPolicy + { + /// + /// Specifies that JSON property names should not be converted. + /// + Unspecified = 0, + + /// + /// Speciies that the built-in be used to convert JSON property names. + /// + BuiltInCamelCase = 1 + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs similarity index 88% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs rename to src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs index 30971aecce26b4..ee0a86c2321bc8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs @@ -6,7 +6,12 @@ namespace System.Text.Json /// /// Determines the naming policy used to convert a string-based name to another format, such as a camel-casing format. /// - public abstract class JsonNamingPolicy +#if BUILDING_SOURCE_GENERATOR + internal +#else + public +#endif + abstract class JsonNamingPolicy { /// /// Initializes a new instance of . @@ -18,8 +23,6 @@ protected JsonNamingPolicy() { } /// public static JsonNamingPolicy CamelCase { get; } = new JsonCamelCaseNamingPolicy(); - internal static JsonNamingPolicy Default { get; } = new JsonDefaultNamingPolicy(); - /// /// When overridden in a derived class, converts the specified name according to the policy. /// diff --git a/src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs new file mode 100644 index 00000000000000..a1b4d099e32ef8 --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs @@ -0,0 +1,53 @@ +// 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 +{ + /// + /// Instructs the System.Text.Json source generator to assume the specified + /// options will be used at run-time via . + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +#if BUILDING_SOURCE_GENERATOR + internal +#else + public +#endif + class JsonSerializerOptionsAttribute : JsonAttribute + { + /// + /// Specifies the default ignore condition. + /// + public JsonIgnoreCondition DefaultIgnoreCondition { get; set; } + + /// + /// Specifies whether to ignore read-only fields. + /// + public bool IgnoreReadOnlyFields { get; set; } + + /// + /// Specifies whether to ignore read-only properties. + /// + public bool IgnoreReadOnlyProperties { get; set; } + + /// + /// Specifies whether to ignore custom converters provided at run-time. + /// + public bool IgnoreRuntimeCustomConverters { get; set; } + + /// + /// Specifies whether to include fields for serialization and deserialization. + /// + public bool IncludeFields { get; set; } + + /// + /// Specifies a built-in naming polices to convert JSON property names with. + /// + public JsonKnownNamingPolicy NamingPolicy { get; set; } + + /// + /// Specifies whether JSON output should be pretty-printed. + /// + public bool WriteIndented { get; set; } + } +} diff --git a/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs b/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs new file mode 100644 index 00000000000000..b46292baaeec9b --- /dev/null +++ b/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.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 +{ + /// + /// The generation mode for the System.Text.Json source generator. + /// + [Flags] +#if BUILDING_SOURCE_GENERATOR + internal +#else + public +#endif + enum JsonSourceGenerationMode + { + /// + /// Instructs the JSON source generator to generate serialization logic and type metadata to fallback to + /// when the run-time options are not compatible with the indicated . + /// + /// + /// This mode supports all features. + /// + MetadataAndSerialization = 0, + + /// + /// Instructs the JSON source generator to generate type-metadata initialization logic. + /// + /// + /// This mode supports all features. + /// + Metadata = 1, + + /// + /// Instructs the JSON source generator to generate serialization logic. + /// + /// + /// This mode supports only a subset of features. + /// + Serialization = 2 + } +} diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs new file mode 100644 index 00000000000000..6d690af0e4c698 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs @@ -0,0 +1,34 @@ +// 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; + +namespace System.Text.Json.SourceGeneration +{ + /// + /// Represents the set of input types and options needed to provide an + /// implementation for a user-provided JsonSerializerContext-derived type. + /// + internal sealed class ContextGenerationSpec + { + public JsonSerializerOptionsAttribute SerializerOptions { get; init; } + + public Type ContextType { get; init; } + + public List? RootSerializableTypes { get; init; } + + /// + /// Types that we have initiated serialization metadata generation for. A type may be discoverable in the object graph, + /// but not reachable for serialization (e.g. it is [JsonIgnore]'d); thus we maintain a separate cache. + /// + public HashSet TypesWithMetadataGenerated { get; } = new(); + + /// + /// Cache of runtime property names (statically determined) found accross the object graph of the JsonSerializerContext. + /// + public HashSet RuntimePropertyNames { get; } = new(); + + public string ContextTypeRef => $"global::{ContextType.FullName}"; + } +} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index a176de61b9a2f1..69ba1ddd3c4efc 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -3,9 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Linq; +using System.Reflection; using System.Text.Json.Serialization; -using System.Text.Json.SourceGeneration.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -13,20 +12,49 @@ namespace System.Text.Json.SourceGeneration { public sealed partial class JsonSourceGenerator { - private sealed class Emitter + private sealed partial class Emitter { + // Literals in generated source private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter"; - - private const string JsonContextDeclarationSource = "internal partial class JsonContext : JsonSerializerContext"; - private const string OptionsInstanceVariableName = "Options"; - - private const string PropInitFuncVarName = "PropInitFunc"; - - private const string JsonMetadataServicesClassName = "JsonMetadataServices"; - + private const string PropInitMethodNameSuffix = "PropInit"; + private const string SerializeMethodNameSuffix = "Serialize"; private const string CreateValueInfoMethodName = "CreateValueInfo"; - + private const string DefaultOptionsStaticVarName = "s_defaultOptions"; + private const string DefaultContextBackingStaticVarName = "s_defaultContext"; + private const string WriterVarName = "writer"; + private const string ValueVarName = "value"; + + private static AssemblyName _assemblyName = typeof(Emitter).Assembly.GetName(); + private static readonly string s_generatedCodeAttributeSource = $@" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{_assemblyName.Name}"", ""{_assemblyName.Version}"")]"; + + // global::fully.qualified.name for referenced types + private const string ActionTypeRef = "global::System.Action"; + private const string ArrayTypeRef = "global::System.Array"; + private const string InvalidOperationExceptionTypeRef = "global::System.InvalidOperationException"; + private const string TypeTypeRef = "global::System.Type"; + private const string UnsafeTypeRef = "global::System.CompilerServices.Unsafe"; + private const string NullableTypeRef = "global::System.Nullable"; + private const string IListTypeRef = "global::System.Collections.Generic.IList"; + private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair"; + private const string ListTypeRef = "global::System.Collections.Generic.List"; + private const string DictionaryTypeRef = "global::System.Collections.Generic.Dictionary"; + private const string JsonEncodedTextTypeRef = "global::System.Text.Json.JsonEncodedText"; + private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy"; + private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer"; + private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions"; + private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter"; + private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter"; + private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory"; + private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition"; + 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 JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo"; + private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo"; + + // Diagnostic descriptors private const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration"; private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor( @@ -45,109 +73,120 @@ private sealed class Emitter defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - private readonly string _generationNamespace; - - // TODO (https://github.com/dotnet/runtime/issues/52218): consider public option for this. - // Converter-honoring logic generation can be simplified - // if we don't plan to have a feature around this. - private readonly bool _honorRuntimeProvidedCustomConverters = true; - private readonly GeneratorExecutionContext _executionContext; - /// - /// Types that we have initiated serialization metadata generation for. A type may be discoverable in the object graph, - /// but not reachable for serialization (e.g. it is [JsonIgnore]'d); thus we maintain a separate cache. - /// - private readonly HashSet _typesWithMetadataGenerated = new(); + private ContextGenerationSpec _currentContext = new(); - /// - /// Types that were specified with System.Text.Json.Serialization.JsonSerializableAttribute. - /// - private readonly Dictionary _rootSerializableTypes = null!; + private readonly SourceGenerationSpec _generationSpec = null!; - public Emitter(in GeneratorExecutionContext executionContext, Dictionary rootSerializableTypes) + public Emitter(in GeneratorExecutionContext executionContext, SourceGenerationSpec generationSpec) { _executionContext = executionContext; - _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration"; - _rootSerializableTypes = rootSerializableTypes; + _generationSpec = generationSpec; } public void Emit() { - foreach (KeyValuePair pair in _rootSerializableTypes) + foreach (ContextGenerationSpec contextGenerationSpec in _generationSpec.ContextGenerationSpecList) { - TypeMetadata typeMetadata = pair.Value; - GenerateTypeMetadata(typeMetadata); + _currentContext = contextGenerationSpec; + + foreach (TypeGenerationSpec typeGenerationSpec in _currentContext.RootSerializableTypes) + { + GenerateTypeInfo(typeGenerationSpec); + } + + string contextName = _currentContext.ContextType.Name; + + // Add root context implementation. + AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(), isRootContextDef: true); + + // Add GetJsonTypeInfo override implementation. + AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation()); + + // Add property name initialization. + AddSource($"{contextName}.PropertyNames.g.cs", GetPropertyNameInitialization()); } + } + + private void AddSource(string fileName, string source, bool isRootContextDef = false) + { + string? generatedCodeAttributeSource = isRootContextDef ? s_generatedCodeAttributeSource : null; + + string wrappedSource = $@"// - // Add base default instance source. - _executionContext.AddSource("JsonContext.g.cs", SourceText.From(GetBaseJsonContextImplementation(), Encoding.UTF8)); +namespace {_currentContext.ContextType.Namespace} +{{{generatedCodeAttributeSource} + {IndentSource(source, numIndentations: 1)} +}} +"; - // Add GetJsonTypeInfo override implementation. - _executionContext.AddSource("JsonContext.GetJsonTypeInfo.g.cs", SourceText.From(GetGetTypeInfoImplementation(), Encoding.UTF8)); + _executionContext.AddSource(fileName, SourceText.From(wrappedSource, Encoding.UTF8)); } - private void GenerateTypeMetadata(TypeMetadata typeMetadata) + private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) { - Debug.Assert(typeMetadata != null); + Debug.Assert(typeGenerationSpec != null); + + HashSet typesWithMetadata = _currentContext.TypesWithMetadataGenerated; - if (_typesWithMetadataGenerated.Contains(typeMetadata)) + if (typesWithMetadata.Contains(typeGenerationSpec)) { return; } - _typesWithMetadataGenerated.Add(typeMetadata); + typesWithMetadata.Add(typeGenerationSpec); string source; - switch (typeMetadata.ClassType) + switch (typeGenerationSpec.ClassType) { case ClassType.KnownType: { - source = GenerateForTypeWithKnownConverter(typeMetadata); + source = GenerateForTypeWithKnownConverter(typeGenerationSpec); } break; case ClassType.TypeWithDesignTimeProvidedCustomConverter: { - source = GenerateForTypeWithUnknownConverter(typeMetadata); + source = GenerateForTypeWithUnknownConverter(typeGenerationSpec); } break; case ClassType.Nullable: { - source = GenerateForNullable(typeMetadata); + source = GenerateForNullable(typeGenerationSpec); - GenerateTypeMetadata(typeMetadata.NullableUnderlyingTypeMetadata); + GenerateTypeInfo(typeGenerationSpec.NullableUnderlyingTypeMetadata); } break; case ClassType.Enum: { - source = GenerateForEnum(typeMetadata); + source = GenerateForEnum(typeGenerationSpec); } break; case ClassType.Enumerable: { - source = GenerateForCollection(typeMetadata); + source = GenerateForCollection(typeGenerationSpec); - GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); + GenerateTypeInfo(typeGenerationSpec.CollectionValueTypeMetadata); } break; case ClassType.Dictionary: { - source = GenerateForCollection(typeMetadata); + source = GenerateForCollection(typeGenerationSpec); - GenerateTypeMetadata(typeMetadata.CollectionKeyTypeMetadata); - GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); + GenerateTypeInfo(typeGenerationSpec.CollectionKeyTypeMetadata); + GenerateTypeInfo(typeGenerationSpec.CollectionValueTypeMetadata); } break; case ClassType.Object: { - source = GenerateForObject(typeMetadata); + source = GenerateForObject(typeGenerationSpec); - if (typeMetadata.PropertiesMetadata != null) + if (typeGenerationSpec.PropertiesMetadata != null) { - foreach (PropertyMetadata metadata in typeMetadata.PropertiesMetadata) + foreach (PropertyGenerationSpec metadata in typeGenerationSpec.PropertiesMetadata) { - GenerateTypeMetadata(metadata.TypeMetadata); + GenerateTypeInfo(metadata.TypeGenerationSpec); } } } @@ -155,7 +194,7 @@ private void GenerateTypeMetadata(TypeMetadata typeMetadata) case ClassType.TypeUnsupportedBySourceGen: { _executionContext.ReportDiagnostic( - Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeMetadata.CompilableName })); + Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeGenerationSpec.TypeRef })); return; } default: @@ -166,109 +205,109 @@ private void GenerateTypeMetadata(TypeMetadata typeMetadata) try { - _executionContext.AddSource($"{typeMetadata.FriendlyName}.cs", SourceText.From(source, Encoding.UTF8)); + AddSource($"{_currentContext.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs", source); } catch (ArgumentException) { - _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeMetadata.FriendlyName })); + _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeGenerationSpec.TypeInfoPropertyName })); } } - private string GenerateForTypeWithKnownConverter(TypeMetadata typeMetadata) + private string GenerateForTypeWithKnownConverter(TypeGenerationSpec typeMetadata) { - string typeCompilableName = typeMetadata.CompilableName; - string typeFriendlyName = typeMetadata.FriendlyName; + string typeCompilableName = typeMetadata.TypeRef; + string typeFriendlyName = typeMetadata.TypeInfoPropertyName; - string metadataInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesClassName}.{typeFriendlyName}Converter);"; + string metadataInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesTypeRef}.{typeFriendlyName}Converter);"; return GenerateForType(typeMetadata, metadataInitSource); } - private string GenerateForTypeWithUnknownConverter(TypeMetadata typeMetadata) + private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetadata) { - string typeCompilableName = typeMetadata.CompilableName; - string typeFriendlyName = typeMetadata.FriendlyName; + string typeCompilableName = typeMetadata.TypeRef; + string typeFriendlyName = typeMetadata.TypeInfoPropertyName; StringBuilder sb = new(); // TODO (https://github.com/dotnet/runtime/issues/52218): consider moving this verification source to common helper. - string metadataInitSource = $@"JsonConverter converter = {typeMetadata.ConverterInstantiationLogic}; - Type typeToConvert = typeof({typeCompilableName}); + string metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic}; + {TypeTypeRef} typeToConvert = typeof({typeCompilableName}); if (!converter.CanConvert(typeToConvert)) {{ - Type underlyingType = Nullable.GetUnderlyingType(typeToConvert); + Type underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert); if (underlyingType != null && converter.CanConvert(underlyingType)) {{ - JsonConverter actualConverter = converter; + {JsonConverterTypeRef} actualConverter = converter; - if (converter is JsonConverterFactory converterFactory) + if (converter is {JsonConverterFactoryTypeRef} converterFactory) {{ actualConverter = converterFactory.CreateConverter(underlyingType, {OptionsInstanceVariableName}); - if (actualConverter == null || actualConverter is JsonConverterFactory) + if (actualConverter == null || actualConverter is {JsonConverterFactoryTypeRef}) {{ - throw new InvalidOperationException($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value.""); + throw new {InvalidOperationExceptionTypeRef}($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value.""); }} }} // Allow nullable handling to forward to the underlying type's converter. - converter = {JsonMetadataServicesClassName}.GetNullableConverter<{typeCompilableName}>((JsonConverter<{typeCompilableName}>)actualConverter); + converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(({JsonConverterTypeRef}<{typeCompilableName}>)actualConverter); }} else {{ - throw new InvalidOperationException($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); + throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); }} }} - _{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);"; + _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);"; return GenerateForType(typeMetadata, metadataInitSource); } - private string GenerateForNullable(TypeMetadata typeMetadata) + private string GenerateForNullable(TypeGenerationSpec typeMetadata) { - string typeCompilableName = typeMetadata.CompilableName; - string typeFriendlyName = typeMetadata.FriendlyName; + string typeCompilableName = typeMetadata.TypeRef; + string typeFriendlyName = typeMetadata.TypeInfoPropertyName; - TypeMetadata? underlyingTypeMetadata = typeMetadata.NullableUnderlyingTypeMetadata; + TypeGenerationSpec? underlyingTypeMetadata = typeMetadata.NullableUnderlyingTypeMetadata; Debug.Assert(underlyingTypeMetadata != null); - string underlyingTypeCompilableName = underlyingTypeMetadata.CompilableName; - string underlyingTypeFriendlyName = underlyingTypeMetadata.FriendlyName; + string underlyingTypeCompilableName = underlyingTypeMetadata.TypeRef; + string underlyingTypeFriendlyName = underlyingTypeMetadata.TypeInfoPropertyName; string underlyingTypeInfoNamedArg = underlyingTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen ? "underlyingTypeInfo: null" : $"underlyingTypeInfo: {underlyingTypeFriendlyName}"; - string metadataInitSource = @$"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}( + string metadataInitSource = @$"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}( {OptionsInstanceVariableName}, - {JsonMetadataServicesClassName}.GetNullableConverter<{underlyingTypeCompilableName}>({underlyingTypeInfoNamedArg})); + {JsonMetadataServicesTypeRef}.GetNullableConverter<{underlyingTypeCompilableName}>({underlyingTypeInfoNamedArg})); "; return GenerateForType(typeMetadata, metadataInitSource); } - private string GenerateForEnum(TypeMetadata typeMetadata) + private string GenerateForEnum(TypeGenerationSpec typeMetadata) { - string typeCompilableName = typeMetadata.CompilableName; - string typeFriendlyName = typeMetadata.FriendlyName; + string typeCompilableName = typeMetadata.TypeRef; + string typeFriendlyName = typeMetadata.TypeInfoPropertyName; - string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, JsonMetadataServices.GetEnumConverter<{typeCompilableName}>({OptionsInstanceVariableName}));"; + string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeCompilableName}>({OptionsInstanceVariableName}));"; return GenerateForType(typeMetadata, metadataInitSource); } - private string GenerateForCollection(TypeMetadata typeMetadata) + private string GenerateForCollection(TypeGenerationSpec typeGenerationSpec) { - string typeCompilableName = typeMetadata.CompilableName; - string typeFriendlyName = typeMetadata.FriendlyName; + string typeCompilableName = typeGenerationSpec.TypeRef; + string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName; // Key metadata - TypeMetadata? collectionKeyTypeMetadata = typeMetadata.CollectionKeyTypeMetadata; - Debug.Assert(!(typeMetadata.CollectionType == CollectionType.Dictionary && collectionKeyTypeMetadata == null)); - string? keyTypeCompilableName = collectionKeyTypeMetadata?.CompilableName; - string? keyTypeReadableName = collectionKeyTypeMetadata?.FriendlyName; + TypeGenerationSpec? collectionKeyTypeMetadata = typeGenerationSpec.CollectionKeyTypeMetadata; + Debug.Assert(!(typeGenerationSpec.CollectionType == CollectionType.Dictionary && collectionKeyTypeMetadata == null)); + string? keyTypeCompilableName = collectionKeyTypeMetadata?.TypeRef; + string? keyTypeReadableName = collectionKeyTypeMetadata?.TypeInfoPropertyName; string? keyTypeMetadataPropertyName; - if (typeMetadata.ClassType != ClassType.Dictionary) + if (typeGenerationSpec.ClassType != ClassType.Dictionary) { keyTypeMetadataPropertyName = "null"; } @@ -280,102 +319,237 @@ private string GenerateForCollection(TypeMetadata typeMetadata) } // Value metadata - TypeMetadata? collectionValueTypeMetadata = typeMetadata.CollectionValueTypeMetadata; + TypeGenerationSpec? collectionValueTypeMetadata = typeGenerationSpec.CollectionValueTypeMetadata; Debug.Assert(collectionValueTypeMetadata != null); - string valueTypeCompilableName = collectionValueTypeMetadata.CompilableName; - string valueTypeReadableName = collectionValueTypeMetadata.FriendlyName; + string valueTypeCompilableName = collectionValueTypeMetadata.TypeRef; + string valueTypeReadableName = collectionValueTypeMetadata.TypeInfoPropertyName; string valueTypeMetadataPropertyName = collectionValueTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen ? "null" : $"this.{valueTypeReadableName}"; - string numberHandlingArg = $"{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}"; + string numberHandlingArg = $"{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}"; + + string serializeMethodName = $"{typeFriendlyName}{SerializeMethodNameSuffix}"; + string serializeFuncNamedArg; + + CollectionType collectionType = typeGenerationSpec.CollectionType; + + string? serializeFuncSource; + if (!typeGenerationSpec.GenerateSerializationLogic) + { + serializeFuncSource = null; + serializeFuncNamedArg = "serializeFunc: null"; + } + else + { + bool canBeNull = typeGenerationSpec.CanBeNull; + + switch (collectionType) + { + case CollectionType.Array: + serializeFuncSource = GenerateFastPathFuncForEnumerable(typeCompilableName, serializeMethodName, canBeNull, isArray: true, collectionValueTypeMetadata); + break; + case CollectionType.List: + serializeFuncSource = GenerateFastPathFuncForEnumerable(typeCompilableName, serializeMethodName, canBeNull, isArray: false, collectionValueTypeMetadata); + break; + case CollectionType.Dictionary: + serializeFuncSource = GenerateFastPathFuncForDictionary(typeCompilableName, serializeMethodName, canBeNull, collectionKeyTypeMetadata, collectionValueTypeMetadata); + break; + default: + serializeFuncSource = null; + break; + } + + serializeFuncNamedArg = $"serializeFunc: {serializeMethodName}"; + } - CollectionType collectionType = typeMetadata.CollectionType; string collectionTypeInfoValue = collectionType switch { - CollectionType.Array => $"{JsonMetadataServicesClassName}.CreateArrayInfo<{valueTypeCompilableName}>({OptionsInstanceVariableName}, {valueTypeMetadataPropertyName}, {numberHandlingArg})", - CollectionType.List => $"{JsonMetadataServicesClassName}.CreateListInfo<{typeCompilableName}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new System.Collections.Generic.List<{valueTypeCompilableName}>(), {valueTypeMetadataPropertyName}, {numberHandlingArg})", - CollectionType.Dictionary => $"{JsonMetadataServicesClassName}.CreateDictionaryInfo<{typeCompilableName}, {keyTypeCompilableName!}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new System.Collections.Generic.Dictionary<{keyTypeCompilableName}, {valueTypeCompilableName}>(), {keyTypeMetadataPropertyName!}, {valueTypeMetadataPropertyName}, {numberHandlingArg})", + CollectionType.Array => $"{JsonMetadataServicesTypeRef}.CreateArrayInfo<{valueTypeCompilableName}>({OptionsInstanceVariableName}, {valueTypeMetadataPropertyName}, {numberHandlingArg}, {serializeFuncNamedArg})", + CollectionType.List => $"{JsonMetadataServicesTypeRef}.CreateListInfo<{typeCompilableName}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new {ListTypeRef}<{valueTypeCompilableName}>(), {valueTypeMetadataPropertyName}, {numberHandlingArg}, {serializeFuncNamedArg})", + CollectionType.Dictionary => $"{JsonMetadataServicesTypeRef}.CreateDictionaryInfo<{typeCompilableName}, {keyTypeCompilableName!}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new {DictionaryTypeRef}<{keyTypeCompilableName}, {valueTypeCompilableName}>(), {keyTypeMetadataPropertyName!}, {valueTypeMetadataPropertyName}, {numberHandlingArg}, {serializeFuncNamedArg})", _ => throw new NotSupportedException() }; string metadataInitSource = @$"_{typeFriendlyName} = {collectionTypeInfoValue};"; - return GenerateForType(typeMetadata, metadataInitSource); + + return GenerateForType(typeGenerationSpec, metadataInitSource, serializeFuncSource); } - private string GenerateForObject(TypeMetadata typeMetadata) + private string GenerateFastPathFuncForEnumerable(string typeInfoRef, string serializeMethodName, bool canBeNull, bool isArray, TypeGenerationSpec valueTypeGenerationSpec) { - string typeCompilableName = typeMetadata.CompilableName; - string typeFriendlyName = typeMetadata.FriendlyName; + string? writerMethodToCall = GetWriterSerializationMethod(valueTypeGenerationSpec.Type); + string valueToWrite = $"{ValueVarName}[i]"; + string lengthPropName = isArray ? "Length" : "Count"; + + string serializationLogic; + if (writerMethodToCall != null) + { + serializationLogic = $"{writerMethodToCall}Value({valueToWrite});"; + } + else + { + serializationLogic = GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic); + } + + string fastPathLogic = $@"{WriterVarName}.WriteStartArray(); + + for (int i = 0; i < {ValueVarName}.{lengthPropName}; i++) + {{ + {serializationLogic} + }} + + {WriterVarName}.WriteEndArray();"; + + return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, fastPathLogic, canBeNull); + } + + private string GenerateFastPathFuncForDictionary( + string typeInfoRef, + string serializeMethodName, + bool canBeNull, + TypeGenerationSpec keyTypeGenerationSpec, + TypeGenerationSpec valueTypeGenerationSpec) + { + string? writerMethodToCall = GetWriterSerializationMethod(valueTypeGenerationSpec.Type); + const string pairVarName = "pair"; + string keyToWrite = $"{pairVarName}.Key"; + string valueToWrite = $"{pairVarName}.Value"; + + string serializationLogic; + if (writerMethodToCall != null) + { + serializationLogic = $"{writerMethodToCall}({keyToWrite}, {valueToWrite})"; + } + else + { + serializationLogic = $@"{WriterVarName}.WritePropertyName({keyToWrite}); + {GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic)}"; + } + + string fastPathLogic = $@"{WriterVarName}.WriteStartObject(); + + foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName}) + {{ + {serializationLogic} + }} + + {WriterVarName}.WriteEndObject();"; + + return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, fastPathLogic, canBeNull); + } + + private string GenerateForObject(TypeGenerationSpec typeMetadata) + { + string typeCompilableName = typeMetadata.TypeRef; + string typeFriendlyName = typeMetadata.TypeInfoPropertyName; string createObjectFuncTypeArg = typeMetadata.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor - ? $"createObjectFunc: static () => new {typeMetadata.CompilableName}()" + ? $"createObjectFunc: static () => new {typeMetadata.TypeRef}()" : "createObjectFunc: null"; - List? properties = typeMetadata.PropertiesMetadata; + List? properties = typeMetadata.PropertiesMetadata; StringBuilder sb = new(); - sb.Append($@"JsonTypeInfo<{typeCompilableName}> objectInfo = {JsonMetadataServicesClassName}.CreateObjectInfo<{typeCompilableName}>(); - _{typeFriendlyName} = objectInfo; + sb.Append($@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>(); + _{typeFriendlyName} = objectInfo; "); - string propInitFuncVarName = $"{typeFriendlyName}{PropInitFuncVarName}"; + string propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}"; + string? propMetadataInitFuncSource = null; + string propMetadataInitFuncNamedArg; - sb.Append($@" - {JsonMetadataServicesClassName}.InitializeObjectInfo( - objectInfo, - {OptionsInstanceVariableName}, - {createObjectFuncTypeArg}, - {propInitFuncVarName}, - {GetNumberHandlingAsStr(typeMetadata.NumberHandling)});"); + string serializeMethodName = $"{typeFriendlyName}{SerializeMethodNameSuffix}"; + string? serializeFuncSource = null; + string serializeFuncNamedArg; + + if (typeMetadata.GenerateMetadata) + { + propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, properties); + propMetadataInitFuncNamedArg = $@"propInitFunc: {propInitMethodName}"; + } + else + { + propMetadataInitFuncNamedArg = @"propInitFunc: null"; + } + + if (typeMetadata.GenerateSerializationLogic) + { + serializeFuncSource = GenerateFastPathFuncForObject(typeCompilableName, serializeMethodName, typeMetadata.CanBeNull, properties); + serializeFuncNamedArg = $@"serializeFunc: {serializeMethodName}"; + } + else + { + serializeFuncNamedArg = @"serializeFunc: null"; + } - string metadataInitSource = sb.ToString(); - string? propInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitFuncVarName, properties); + sb.Append($@" + {JsonMetadataServicesTypeRef}.InitializeObjectInfo( + objectInfo, + {OptionsInstanceVariableName}, + {createObjectFuncTypeArg}, + {propMetadataInitFuncNamedArg}, + {GetNumberHandlingAsStr(typeMetadata.NumberHandling)}, + {serializeFuncNamedArg});"); + + string objectInfoInitSource = sb.ToString(); + + string additionalSource; + if (propMetadataInitFuncSource == null || serializeFuncSource == null) + { + additionalSource = propMetadataInitFuncSource ?? serializeFuncSource; + } + else + { + additionalSource = @$"{propMetadataInitFuncSource}{serializeFuncSource}"; + } - return GenerateForType(typeMetadata, metadataInitSource, propInitFuncSource); + return GenerateForType(typeMetadata, objectInfoInitSource, additionalSource); } private string GeneratePropMetadataInitFunc( bool declaringTypeIsValueType, - string propInitFuncVarName, - List? properties) + string propInitMethodName, + List? properties) { const string PropVarName = "properties"; const string JsonContextVarName = "jsonContext"; - const string JsonPropertyInfoTypeName = "JsonPropertyInfo"; string propertyArrayInstantiationValue = properties == null - ? $"System.Array.Empty<{JsonPropertyInfoTypeName}>()" - : $"new {JsonPropertyInfoTypeName}[{properties.Count}]"; + ? $"{ArrayTypeRef}.Empty<{JsonPropertyInfoTypeRef}>()" + : $"new {JsonPropertyInfoTypeRef}[{properties.Count}]"; + + string contextTypeRef = _currentContext.ContextTypeRef; StringBuilder sb = new(); sb.Append($@" - private static {JsonPropertyInfoTypeName}[] {propInitFuncVarName}(JsonSerializerContext context) - {{ - JsonContext {JsonContextVarName} = (JsonContext)context; - JsonSerializerOptions options = context.Options; - {JsonPropertyInfoTypeName}[] {PropVarName} = {propertyArrayInstantiationValue}; + private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerContextTypeRef} context) + {{ + {contextTypeRef} {JsonContextVarName} = ({contextTypeRef})context; + {JsonSerializerOptionsTypeRef} options = context.Options; + + {JsonPropertyInfoTypeRef}[] {PropVarName} = {propertyArrayInstantiationValue}; "); if (properties != null) { for (int i = 0; i < properties.Count; i++) { - PropertyMetadata memberMetadata = properties[i]; + PropertyGenerationSpec memberMetadata = properties[i]; - TypeMetadata memberTypeMetadata = memberMetadata.TypeMetadata; + TypeGenerationSpec memberTypeMetadata = memberMetadata.TypeGenerationSpec; string clrPropertyName = memberMetadata.ClrName; - string declaringTypeCompilableName = memberMetadata.DeclaringTypeCompilableName; + string declaringTypeCompilableName = memberMetadata.DeclaringTypeRef; string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen ? "null" - : $"{JsonContextVarName}.{memberTypeMetadata.FriendlyName}"; + : $"{JsonContextVarName}.{memberTypeMetadata.TypeInfoPropertyName}"; string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}"; @@ -383,15 +557,15 @@ private string GeneratePropMetadataInitFunc( ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}""" : "jsonPropertyName: null"; - string getterNamedArg = memberMetadata.HasGetter + string getterNamedArg = memberMetadata.CanUseGetter ? $"getter: static (obj) => {{ return (({declaringTypeCompilableName})obj).{clrPropertyName}; }}" : "getter: null"; string setterNamedArg; - if (memberMetadata.HasSetter) + if (memberMetadata.CanUseSetter) { string propMutation = declaringTypeIsValueType - ? @$"{{ Unsafe.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}" + ? @$"{{ {UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}" : $@"{{ (({declaringTypeCompilableName})obj).{clrPropertyName} = value; }}"; setterNamedArg = $"setter: static (obj, value) => {propMutation}"; @@ -401,7 +575,7 @@ private string GeneratePropMetadataInitFunc( setterNamedArg = "setter: null"; } - JsonIgnoreCondition? ignoreCondition = memberMetadata.IgnoreCondition; + JsonIgnoreCondition? ignoreCondition = memberMetadata.DefaultIgnoreCondition; string ignoreConditionNamedArg = ignoreCondition.HasValue ? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}" : "ignoreCondition: default"; @@ -410,180 +584,419 @@ private string GeneratePropMetadataInitFunc( ? "converter: null" : $"converter: {memberMetadata.ConverterInstantiationLogic}"; - string memberTypeCompilableName = memberTypeMetadata.CompilableName; + string memberTypeCompilableName = memberTypeMetadata.TypeRef; sb.Append($@" - {PropVarName}[{i}] = {JsonMetadataServicesClassName}.CreatePropertyInfo<{memberTypeCompilableName}>( - options, - isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()}, - declaringType: typeof({memberMetadata.DeclaringTypeCompilableName}), - {typeTypeInfoNamedArg}, - {converterNamedArg}, - {getterNamedArg}, - {setterNamedArg}, - {ignoreConditionNamedArg}, - numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, - propertyName: ""{clrPropertyName}"", - {jsonPropertyNameNamedArg}); - "); + {PropVarName}[{i}] = {JsonMetadataServicesTypeRef}.CreatePropertyInfo<{memberTypeCompilableName}>( + options, + isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()}, + declaringType: typeof({memberMetadata.DeclaringTypeRef}), + {typeTypeInfoNamedArg}, + {converterNamedArg}, + {getterNamedArg}, + {setterNamedArg}, + {ignoreConditionNamedArg}, + numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, + propertyName: ""{clrPropertyName}"", + {jsonPropertyNameNamedArg}); + "); } } sb.Append(@$" - return {PropVarName}; - }}"); + return {PropVarName}; + }}"); return sb.ToString(); } - private string GenerateForType(TypeMetadata typeMetadata, string metadataInitSource, string? additionalSource = null) + private string GenerateFastPathFuncForObject( + string typeInfoTypeRef, + string serializeMethodName, + bool canBeNull, + List? properties) + { + string methodSignature = $"private static void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {typeInfoTypeRef} {ValueVarName})"; + + JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions; + + // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation. + string[] runtimePropNames = GetRuntimePropNames(properties, options.NamingPolicy); + _currentContext.RuntimePropertyNames.UnionWith(runtimePropNames); + + StringBuilder sb = new(); + + // Begin method definition + sb.Append($@"{WriterVarName}.WriteStartObject();"); + + if (properties != null) + { + // Provide generation logic for each prop. + for (int i = 0; i < properties.Count; i++) + { + PropertyGenerationSpec propertySpec = properties[i]; + TypeGenerationSpec propertyTypeSpec = propertySpec.TypeGenerationSpec; + + if (propertyTypeSpec.ClassType == ClassType.TypeUnsupportedBySourceGen) + { + continue; + } + + if (propertySpec.IsReadOnly) + { + if (propertySpec.IsProperty) + { + if (options.IgnoreReadOnlyProperties) + { + continue; + } + } + else if (options.IgnoreReadOnlyFields) + { + continue; + } + } + + if (!propertySpec.IsProperty && !propertySpec.HasJsonInclude && !options.IncludeFields) + { + continue; + } + + Type propertyType = propertyTypeSpec.Type; + string propName = $"{runtimePropNames[i]}PropName"; + string propValue = $"{ValueVarName}.{propertySpec.ClrName}"; + string methodArgs = $"{propName}, {propValue}"; + + string? methodToCall = GetWriterSerializationMethod(propertyType); + + if (propertyType == _generationSpec.CharType) + { + methodArgs = $"{methodArgs}.ToString()"; + } + + string serializationLogic; + + if (methodToCall != null) + { + serializationLogic = $@" + {methodToCall}({methodArgs});"; + } + else + { + serializationLogic = $@" + {WriterVarName}.WritePropertyName({propName}); + {GetSerializeLogicForNonPrimitiveType(propertyTypeSpec.TypeInfoPropertyName, propValue, propertyTypeSpec.GenerateSerializationLogic)}"; + } + + JsonIgnoreCondition ignoreCondition = propertySpec.DefaultIgnoreCondition ?? options.DefaultIgnoreCondition; + DefaultCheckType defaultCheckType; + bool typeCanBeNull = propertyTypeSpec.CanBeNull; + + switch (ignoreCondition) + { + case JsonIgnoreCondition.WhenWritingNull: + defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.None; + break; + case JsonIgnoreCondition.WhenWritingDefault: + defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default; + break; + default: + defaultCheckType = DefaultCheckType.None; + break; + } + + sb.Append(WrapSerializationLogicInDefaultCheckIfRequired(serializationLogic, propValue, defaultCheckType)); + } + } + + // End method definition + sb.Append($@" + + {WriterVarName}.WriteEndObject();"); + + return GenerateFastPathFuncForType(serializeMethodName, typeInfoTypeRef, sb.ToString(), canBeNull); + } + + private string? GetWriterSerializationMethod(Type type) + { + string? methodToCall; + if (_generationSpec.IsStringBasedType(type)) + { + methodToCall = $"{WriterVarName}.WriteString"; + } + else if (type == _generationSpec.BooleanType) + { + methodToCall = $"{WriterVarName}.WriteBoolean"; + } + else if (type == _generationSpec.ByteArrayType) + { + methodToCall = $"{WriterVarName}.WriteBase64String"; + } + else if (type == _generationSpec.CharType) + { + methodToCall = $"{WriterVarName}.WriteString"; + } + else if (_generationSpec.IsNumberType(type)) + { + methodToCall = $"{WriterVarName}.WriteNumber"; + } + else + { + methodToCall = null; + } + + return methodToCall; + } + + private string GenerateFastPathFuncForType(string serializeMethodName, string typeInfoTypeRef, string serializationLogic, bool canBeNull) + { + return $@" + + private static void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {typeInfoTypeRef} {ValueVarName}) + {{ + {GetEarlyNullCheckSource(canBeNull)} + {serializationLogic} + }}"; + } + + private string GetEarlyNullCheckSource(bool canBeNull) + { + return canBeNull + ? $@"if ({ValueVarName} == null) + {{ + {WriterVarName}.WriteNullValue(); + return; + }} +" + : null; + } + + private string GetSerializeLogicForNonPrimitiveType(string typeInfoPropertyName, string valueToWrite, bool serializationLogicGenerated) + { + string typeInfoRef = $"{_currentContext.ContextTypeRef}.Default.{typeInfoPropertyName}"; + + if (serializationLogicGenerated) + { + return $"{typeInfoPropertyName}{SerializeMethodNameSuffix}({WriterVarName}, {valueToWrite});"; + } + + return $"{JsonSerializerTypeRef}.Serialize({WriterVarName}, {valueToWrite}, {typeInfoRef});"; + } + + private enum DefaultCheckType { - string typeCompilableName = typeMetadata.CompilableName; - string typeFriendlyName = typeMetadata.FriendlyName; + None, + Null, + Default, + } - return @$"{GetUsingStatementsString(typeMetadata)} + private string WrapSerializationLogicInDefaultCheckIfRequired(string serializationLogic, string propValue, DefaultCheckType defaultCheckType) + { + if (defaultCheckType == DefaultCheckType.None) + { + return serializationLogic; + } -namespace {_generationNamespace} + string defaultLiteral = defaultCheckType == DefaultCheckType.Null ? "null" : "default"; + return $@" + if ({propValue} != {defaultLiteral}) + {{{serializationLogic} + }}"; + } + + private string[] GetRuntimePropNames(List? properties, JsonKnownNamingPolicy namingPolicy) + { + if (properties == null) + { + return Array.Empty(); + } + + int propCount = properties.Count; + string[] runtimePropNames = new string[propCount]; + + // Compute JsonEncodedText values to represent each property name. This gives the best throughput performance + for (int i = 0; i < propCount; i++) + { + PropertyGenerationSpec propertySpec = properties[i]; + + string propName = DetermineRuntimePropName(propertySpec.ClrName, propertySpec.JsonPropertyName, namingPolicy); + Debug.Assert(propName != null); + + runtimePropNames[i] = propName; + } + + return runtimePropNames; + } + + private string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy) + { + string runtimePropName; + + if (jsonPropName != null) + { + runtimePropName = jsonPropName; + } + else if (namingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase) + { + runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName); + } + else + { + runtimePropName = clrPropName; + } + + return runtimePropName; + } + + private string GenerateForType(TypeGenerationSpec typeMetadata, string metadataInitSource, string? additionalSource = null) + { + string typeCompilableName = typeMetadata.TypeRef; + string typeFriendlyName = typeMetadata.TypeInfoPropertyName; + string typeInfoPropertyTypeRef = $"{JsonTypeInfoTypeRef}<{typeCompilableName}>"; + + return @$"{GetJsonContextDeclarationSource()} {{ - {JsonContextDeclarationSource} + private {typeInfoPropertyTypeRef} _{typeFriendlyName}; + public {typeInfoPropertyTypeRef} {typeFriendlyName} {{ - private JsonTypeInfo<{typeCompilableName}> _{typeFriendlyName}; - public JsonTypeInfo<{typeCompilableName}> {typeFriendlyName} + get {{ - get + if (_{typeFriendlyName} == null) {{ - if (_{typeFriendlyName} == null) - {{ - {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))} - }} - - return _{typeFriendlyName}; + {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))} }} - }}{additionalSource} - }} -}} -"; + + return _{typeFriendlyName}; + }} + }}{additionalSource} +}}"; } private string WrapWithCheckForCustomConverterIfRequired(string source, string typeCompilableName, string typeFriendlyName, string numberHandlingNamedArg) { - if (!_honorRuntimeProvidedCustomConverters) + if (_currentContext.SerializerOptions.IgnoreRuntimeCustomConverters) { return source; } - return @$"JsonConverter customConverter; - if ({OptionsInstanceVariableName}.Converters.Count > 0 && (customConverter = {RuntimeCustomConverterFetchingMethodName}(typeof({typeCompilableName}))) != null) - {{ - _{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, customConverter); - }} - else - {{ - {source.Replace(Environment.NewLine, $"{Environment.NewLine} ")} - }}"; + return @$"{JsonConverterTypeRef} customConverter; + if ({OptionsInstanceVariableName}.Converters.Count > 0 && (customConverter = {RuntimeCustomConverterFetchingMethodName}(typeof({typeCompilableName}))) != null) + {{ + _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, customConverter); + }} + else + {{ + {IndentSource(source, numIndentations: 1)} + }}"; } - private string GetBaseJsonContextImplementation() + private string GetRootJsonContextImplementation() { + string contextTypeRef = _currentContext.ContextTypeRef; + string contextTypeName = _currentContext.ContextType.Name; + StringBuilder sb = new(); - sb.Append(@$"using System.Text.Json; -using System.Text.Json.Serialization; -namespace {_generationNamespace} + sb.Append(@$"{GetJsonContextDeclarationSource()} {{ - {JsonContextDeclarationSource} - {{ - private static JsonContext s_default; - public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions()); + {GetLogicForDefaultSerializerOptionsInit()} - public JsonContext() : base(null) - {{ - }} + private static {contextTypeRef} {DefaultContextBackingStaticVarName}; + public static {contextTypeRef} Default => {DefaultContextBackingStaticVarName} ??= new {contextTypeRef}(new {JsonSerializerOptionsTypeRef}({DefaultOptionsStaticVarName})); - public JsonContext(JsonSerializerOptions options) : base(options) - {{ - }} + public {contextTypeName}() : base(null, {DefaultOptionsStaticVarName}) + {{ + }} - {GetFetchLogicForRuntimeSpecifiedCustomConverter()} + public {contextTypeName}({JsonSerializerOptionsTypeRef} options) : base(options, {DefaultOptionsStaticVarName}) + {{ }} -}} -"); + + {GetFetchLogicForRuntimeSpecifiedCustomConverter()} +}}"); return sb.ToString(); } private string GetFetchLogicForRuntimeSpecifiedCustomConverter() { - if (!_honorRuntimeProvidedCustomConverters) + if (_currentContext.SerializerOptions.IgnoreRuntimeCustomConverters) { return ""; } // TODO (https://github.com/dotnet/runtime/issues/52218): use a dictionary if count > ~15. - return @$"private JsonConverter {RuntimeCustomConverterFetchingMethodName}(System.Type type) + return @$"private {JsonConverterTypeRef} {RuntimeCustomConverterFetchingMethodName}({TypeTypeRef} type) + {{ + {IListTypeRef}<{JsonConverterTypeRef}> converters = {OptionsInstanceVariableName}.Converters; + + for (int i = 0; i < converters.Count; i++) {{ - System.Collections.Generic.IList converters = {OptionsInstanceVariableName}.Converters; + {JsonConverterTypeRef} converter = converters[i]; - for (int i = 0; i < converters.Count; i++) + if (converter.CanConvert(type)) {{ - JsonConverter converter = converters[i]; - - if (converter.CanConvert(type)) + if (converter is {JsonConverterFactoryTypeRef} factory) {{ - if (converter is JsonConverterFactory factory) + converter = factory.CreateConverter(type, {OptionsInstanceVariableName}); + if (converter == null || converter is {JsonConverterFactoryTypeRef}) {{ - converter = factory.CreateConverter(type, {OptionsInstanceVariableName}); - if (converter == null || converter is JsonConverterFactory) - {{ - throw new System.InvalidOperationException($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance.""); - }} + throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance.""); }} - - return converter; }} + + return converter; }} + }} - return null; - }}"; + return null; + }}"; } - private string GetGetTypeInfoImplementation() + private string GetLogicForDefaultSerializerOptionsInit() { - StringBuilder sb = new(); + JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions; - HashSet usingStatements = new(); + string? namingPolicyInit = options.NamingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase + ? $@" + PropertyNamingPolicy = {JsonNamingPolicyTypeRef}.CamelCase" + : null; - foreach (TypeMetadata metadata in _rootSerializableTypes.Values) - { - usingStatements.UnionWith(GetUsingStatements(metadata)); - } + return $@"private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ get; }} = new {JsonSerializerOptionsTypeRef}() + {{ + DefaultIgnoreCondition = {JsonIgnoreConditionTypeRef}.{options.DefaultIgnoreCondition}, + IgnoreReadOnlyFields = {options.IgnoreReadOnlyFields.ToString().ToLowerInvariant()}, + IgnoreReadOnlyProperties = {options.IgnoreReadOnlyProperties.ToString().ToLowerInvariant()}, + IncludeFields = {options.IncludeFields.ToString().ToLowerInvariant()}, + WriteIndented = {options.WriteIndented.ToString().ToLowerInvariant()},{namingPolicyInit} + }};"; + } - sb.Append(@$"{GetUsingStatementsString(usingStatements)} + private string GetGetTypeInfoImplementation() + { + StringBuilder sb = new(); -namespace {_generationNamespace} + sb.Append(@$"{GetJsonContextDeclarationSource()} {{ - {JsonContextDeclarationSource} - {{ - public override JsonTypeInfo GetTypeInfo(System.Type type) - {{"); + public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type) + {{"); // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64. - foreach (TypeMetadata metadata in _rootSerializableTypes.Values) + foreach (TypeGenerationSpec metadata in _currentContext.RootSerializableTypes) { if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) { sb.Append($@" - if (type == typeof({metadata.Type.GetUniqueCompilableTypeName()})) - {{ - return this.{metadata.FriendlyName}; - }} + if (type == typeof({metadata.TypeRef})) + {{ + return this.{metadata.TypeInfoPropertyName}; + }} "); } } sb.Append(@" - return null!; - } + return null!; } } "); @@ -591,86 +1004,38 @@ public override JsonTypeInfo GetTypeInfo(System.Type type) return sb.ToString(); } - private static string GetUsingStatementsString(TypeMetadata typeMetadata) + private string GetPropertyNameInitialization() { - HashSet usingStatements = GetUsingStatements(typeMetadata); - return GetUsingStatementsString(usingStatements); - } + // Ensure metadata for types has already occured. + Debug.Assert(!( + _currentContext.TypesWithMetadataGenerated.Count == 0 + && _currentContext.RuntimePropertyNames.Count > 0)); - private static string GetUsingStatementsString(HashSet usingStatements) - { - string[] usingsArr = usingStatements.ToArray(); - Array.Sort(usingsArr); - return string.Join("\n", usingsArr); - } - - private static HashSet GetUsingStatements(TypeMetadata typeMetadata) - { - HashSet usingStatements = new(); - - // Add library usings. - usingStatements.Add(FormatAsUsingStatement("System.Runtime.CompilerServices")); - usingStatements.Add(FormatAsUsingStatement("System.Text.Json")); - usingStatements.Add(FormatAsUsingStatement("System.Text.Json.Serialization")); - usingStatements.Add(FormatAsUsingStatement("System.Text.Json.Serialization.Metadata")); + StringBuilder sb = new(); - // Add imports to root type. - usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace)); + sb.Append(@$"{GetJsonContextDeclarationSource()} +{{"); - switch (typeMetadata.ClassType) + foreach (string propName in _currentContext.RuntimePropertyNames) { - case ClassType.Nullable: - { - AddUsingStatementsForType(typeMetadata.NullableUnderlyingTypeMetadata!); - } - break; - case ClassType.Enumerable: - { - AddUsingStatementsForType(typeMetadata.CollectionValueTypeMetadata); - } - break; - case ClassType.Dictionary: - { - AddUsingStatementsForType(typeMetadata.CollectionKeyTypeMetadata); - AddUsingStatementsForType(typeMetadata.CollectionValueTypeMetadata); - } - break; - case ClassType.Object: - { - if (typeMetadata.PropertiesMetadata != null) - { - foreach (PropertyMetadata metadata in typeMetadata.PropertiesMetadata) - { - AddUsingStatementsForType(metadata.TypeMetadata); - } - } - } - break; - default: - break; + sb.Append($@" + private static {JsonEncodedTextTypeRef} {propName}PropName = {JsonEncodedTextTypeRef}.Encode(""{propName}"");"); } - void AddUsingStatementsForType(TypeMetadata typeMetadata) - { - usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace)); + sb.Append(@" +}"); - if (typeMetadata.CollectionKeyTypeMetadata != null) - { - Debug.Assert(typeMetadata.CollectionValueTypeMetadata != null); - usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionKeyTypeMetadata.Type.Namespace)); - } + return sb.ToString(); + } - if (typeMetadata.CollectionValueTypeMetadata != null) - { - usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionValueTypeMetadata.Type.Namespace)); - } - } + private string GetJsonContextDeclarationSource() => $"internal partial class {_currentContext.ContextType.Name} : {JsonSerializerContextTypeRef}"; - return usingStatements; + private static string IndentSource(string source, int numIndentations) + { + Debug.Assert(numIndentations >= 0); + return source.Replace(Environment.NewLine, $"{Environment.NewLine}{new string(' ', 4 * numIndentations)}"); // 4 spaces per indentation. } - private static string FormatAsUsingStatement(string @namespace) => $"using {@namespace};"; - private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) => numberHandling.HasValue ? $"(JsonNumberHandling){(int)numberHandling.Value}" diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 86a7043b67f18a..356ee9b0213baf 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -57,13 +57,16 @@ private sealed class Parser /// /// Type information for member types in input object graphs. /// - private readonly Dictionary _typeMetadataCache = new(); + private readonly Dictionary _typeGenerationSpecCache = new(); public Parser(Compilation compilation) { _compilation = compilation; _metadataLoadContext = new MetadataLoadContextInternal(compilation); + TypeExtensions.NullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>)); + TypeExtensions.ObjectArrayType = _metadataLoadContext.Resolve(typeof(object[])); + _ienumerableType = _metadataLoadContext.Resolve(typeof(IEnumerable)); _listOfTType = _metadataLoadContext.Resolve(typeof(List<>)); _dictionaryType = _metadataLoadContext.Resolve(typeof(Dictionary<,>)); @@ -81,109 +84,286 @@ public Parser(Compilation compilation) PopulateKnownTypes(); } - public Dictionary? GetRootSerializableTypes(List compilationUnits) + public SourceGenerationSpec? GetGenerationSpec(List classDeclarationSyntaxList) { - TypeExtensions.NullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>)); + INamedTypeSymbol jsonSerializerContextSymbol = _compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext"); + INamedTypeSymbol jsonSerializableAttributeSymbol = _compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); + INamedTypeSymbol jsonSerializerOptionsAttributeSymbol = _compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerOptionsAttribute"); - const string JsonSerializableAttributeName = "System.Text.Json.Serialization.JsonSerializableAttribute"; - INamedTypeSymbol jsonSerializableAttribute = _compilation.GetTypeByMetadataName(JsonSerializableAttributeName); - if (jsonSerializableAttribute == null) + if (jsonSerializerContextSymbol == null || jsonSerializableAttributeSymbol == null || jsonSerializerOptionsAttributeSymbol == null) { return null; } - // Discover serializable types indicated by JsonSerializableAttribute. - Dictionary? rootTypes = null; + List? contextGenSpecList = null; - foreach (CompilationUnitSyntax compilationUnit in compilationUnits) + foreach (ClassDeclarationSyntax classDeclarationSyntax in classDeclarationSyntaxList) { - SemanticModel compilationSemanticModel = _compilation.GetSemanticModel(compilationUnit.SyntaxTree); + CompilationUnitSyntax compilationUnitSyntax = classDeclarationSyntax.FirstAncestorOrSelf(); + SemanticModel compilationSemanticModel = _compilation.GetSemanticModel(compilationUnitSyntax.SyntaxTree); + + if (!IsConfigurableJsonSerializerContext(classDeclarationSyntax, jsonSerializerContextSymbol, compilationSemanticModel)) + { + continue; + } - foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists) + List? rootTypes = null; + JsonSerializerOptionsAttribute? options = null; + + foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) { AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First(); IMethodSymbol attributeSymbol = compilationSemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; - - if (attributeSymbol == null || !jsonSerializableAttribute.Equals(attributeSymbol.ContainingType, SymbolEqualityComparer.Default)) + if (attributeSymbol == null) { - // Not the right attribute. continue; } - // Get JsonSerializableAttribute arguments. - IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); + INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType; - ITypeSymbol? typeSymbol = null; - string? typeInfoPropertyName = null; - - int i = 0; - foreach (AttributeArgumentSyntax node in attributeArguments) + if (jsonSerializableAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) { - if (i == 0) + TypeGenerationSpec? metadata = GetRootSerializableType(compilationSemanticModel, attributeSyntax); + if (metadata != null) { - TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax; - if (typeNode != null) - { - ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); - typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType; - } + (rootTypes ??= new List()).Add(metadata); } - else if (i == 1) - { - // Obtain the optional TypeInfoPropertyName string property on the attribute, if present. - SyntaxNode? typeInfoPropertyNameNode = node.ChildNodes().ElementAtOrDefault(1); - if (typeInfoPropertyNameNode != null) - { - typeInfoPropertyName = typeInfoPropertyNameNode.GetFirstToken().ValueText; - } - } - - i++; } + else if (jsonSerializerOptionsAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) + { + options = GetSerializerOptions(attributeSyntax); + } + } + + if (rootTypes == null) + { + // No types were indicated with [JsonSerializable] + continue; + } + + ITypeSymbol contextTypeSymbol = (ITypeSymbol)compilationSemanticModel.GetDeclaredSymbol(classDeclarationSyntax); + Debug.Assert(contextTypeSymbol != null); + + contextGenSpecList ??= new List(); + contextGenSpecList.Add(new ContextGenerationSpec + { + SerializerOptions = options ?? new JsonSerializerOptionsAttribute(), + ContextType = contextTypeSymbol.AsType(_metadataLoadContext), + RootSerializableTypes = rootTypes, + }); + + // Clear the cache of generated metadata between the processing of context classes. + _typeGenerationSpecCache.Clear(); + } + + if (contextGenSpecList == null) + { + return null; + } + + return new SourceGenerationSpec + { + ContextGenerationSpecList = contextGenSpecList, + BooleanType = _booleanType, + ByteArrayType = _byteArrayType, + CharType = _charType, + DateTimeType = _dateTimeType, + DateTimeOffsetType = _dateTimeOffsetType, + GuidType = _guidType, + StringType = _stringType, + NumberTypes = _numberTypes, + }; + } + + // Returns true if a given type + // - derives from JsonSerializerContext + // - is partial + // - has no members + private bool IsConfigurableJsonSerializerContext( + ClassDeclarationSyntax classDeclarationSyntax, + INamedTypeSymbol jsonSerializerContextSymbol, + SemanticModel compilationSemanticModel) + { + SeparatedSyntaxList? baseTypeSyntaxList = classDeclarationSyntax.BaseList?.Types; + if (baseTypeSyntaxList == null) + { + return false; + } + + INamedTypeSymbol? match = null; + + foreach (BaseTypeSyntax baseTypeSyntax in baseTypeSyntaxList) + { + INamedTypeSymbol? candidate = compilationSemanticModel.GetSymbolInfo(baseTypeSyntax.Type).Symbol as INamedTypeSymbol; + if (candidate != null && jsonSerializerContextSymbol.Equals(candidate, SymbolEqualityComparer.Default)) + { + match = candidate; + break; + } + } + + if (match == null) + { + return false; + } + + bool isPartial = classDeclarationSyntax.Modifiers.Where(token => token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword)).FirstOrDefault() != default; + if (!isPartial) + { + return false; + } - if (typeSymbol == null) + return classDeclarationSyntax.Members.Count() == 0; + } + + private TypeGenerationSpec? GetRootSerializableType(SemanticModel compilationSemanticModel, AttributeSyntax attributeSyntax) + { + IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); + + ITypeSymbol? typeSymbol = null; + string? typeInfoPropertyName = null; + JsonSourceGenerationMode generationMode = default; + + bool seenFirstArg = false; + foreach (AttributeArgumentSyntax node in attributeArguments) + { + if (!seenFirstArg) + { + TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax; + if (typeNode != null) { - continue; + ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); + typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType; } + seenFirstArg = true; + } + else + { + IEnumerable childNodes = node.ChildNodes(); - Type type = new TypeWrapper(typeSymbol, _metadataLoadContext); - if (type.Namespace == "") + NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax; + Debug.Assert(propertyNameNode != null); + + SyntaxNode? propertyValueMode = childNodes.ElementAtOrDefault(1); + if (propertyNameNode.Name.Identifier.ValueText == "TypeInfoPropertyName") { - // typeof() reference where the type's name isn't fully qualified. - // The compilation is not valid and the user needs to fix their code. - // The compiler will notify the user so we don't have to. - return null; + typeInfoPropertyName = propertyValueMode.GetFirstToken().ValueText; } + else + { + Debug.Assert(propertyNameNode.Name.Identifier.ValueText == "GenerationMode"); + generationMode = (JsonSourceGenerationMode)Enum.Parse(typeof(JsonSourceGenerationMode), propertyValueMode.GetLastToken().ValueText); + } + } + } + + if (typeSymbol == null) + { + return null; + } - rootTypes ??= new Dictionary(); - rootTypes[type.FullName] = GetOrAddTypeMetadata(type, typeInfoPropertyName); + Type type = typeSymbol.AsType(_metadataLoadContext); + if (type.Namespace == "") + { + // typeof() reference where the type's name isn't fully qualified. + // The compilation is not valid and the user needs to fix their code. + // The compiler will notify the user so we don't have to. + return null; + } + + TypeGenerationSpec typeGenerationSpec = GetOrAddTypeGenerationSpec(type); + + if (typeInfoPropertyName != null) + { + typeGenerationSpec.TypeInfoPropertyName = typeInfoPropertyName; + } + + ClassType classType = typeGenerationSpec.ClassType; + CollectionType collectionType = typeGenerationSpec.CollectionType; + switch (generationMode) + { + case JsonSourceGenerationMode.MetadataAndSerialization: + break; + case JsonSourceGenerationMode.Metadata: + typeGenerationSpec.GenerateSerializationLogic = false; + break; + case JsonSourceGenerationMode.Serialization: + typeGenerationSpec.GenerateMetadata = false; + break; + default: + throw new InvalidOperationException(); + } + + return typeGenerationSpec; + } + + private static JsonSerializerOptionsAttribute? GetSerializerOptions(AttributeSyntax attributeSyntax) + { + IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); + + JsonSerializerOptionsAttribute options = new(); + + foreach (AttributeArgumentSyntax node in attributeArguments) + { + IEnumerable childNodes = node.ChildNodes(); + + NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax; + Debug.Assert(propertyNameNode != null); + + SyntaxNode? propertyValueNode = childNodes.ElementAtOrDefault(1); + string propertyValueStr = propertyValueNode.GetLastToken().ValueText; + + switch (propertyNameNode.Name.Identifier.ValueText) + { + case "DefaultIgnoreCondition": + options.DefaultIgnoreCondition = (JsonIgnoreCondition)Enum.Parse(typeof(JsonIgnoreCondition), propertyValueStr); + break; + case "IgnoreReadOnlyFields": + options.IgnoreReadOnlyFields = bool.Parse(propertyValueStr); + break; + case "IgnoreReadOnlyProperties": + options.IgnoreReadOnlyProperties = bool.Parse(propertyValueStr); + break; + case "IgnoreRuntimeCustomConverters": + options.IgnoreRuntimeCustomConverters = bool.Parse(propertyValueStr); + break; + case "IncludeFields": + options.IncludeFields = bool.Parse(propertyValueStr); + break; + case "NamingPolicy": + options.NamingPolicy = (JsonKnownNamingPolicy)Enum.Parse(typeof(JsonKnownNamingPolicy), propertyValueStr); + break; + case "WriteIndented": + options.WriteIndented = bool.Parse(propertyValueStr); + break; + default: + throw new InvalidOperationException(); } } - return rootTypes; + return options; } - private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyName = null) + private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type) { - if (_typeMetadataCache.TryGetValue(type, out TypeMetadata? typeMetadata)) + if (_typeGenerationSpecCache.TryGetValue(type, out TypeGenerationSpec? typeMetadata)) { return typeMetadata!; } // Add metadata to cache now to prevent stack overflow when the same type is found somewhere else in the object graph. typeMetadata = new(); - _typeMetadataCache[type] = typeMetadata; + _typeGenerationSpecCache[type] = typeMetadata; ClassType classType; Type? collectionKeyType = null; Type? collectionValueType = null; Type? nullableUnderlyingType = null; - List? propertiesMetadata = null; + List? propertiesMetadata = null; CollectionType collectionType = CollectionType.NotApplicable; ObjectConstructionStrategy constructionStrategy = default; JsonNumberHandling? numberHandling = null; - bool containsOnlyPrimitives = true; bool foundDesignTimeCustomConverter = false; string? converterInstatiationLogic = null; @@ -281,7 +461,7 @@ private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyNam foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) { - PropertyMetadata metadata = GetPropertyMetadata(propertyInfo); + PropertyGenerationSpec metadata = GetPropertyGenerationSpec(propertyInfo); // Ignore indexers. if (propertyInfo.GetIndexParameters().Length > 0) @@ -291,22 +471,17 @@ private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyNam string key = metadata.JsonPropertyName ?? metadata.ClrName; - if (metadata.HasGetter || metadata.HasSetter) + if (metadata.CanUseGetter || metadata.CanUseSetter) { (propertiesMetadata ??= new()).Add(metadata); } - - if (containsOnlyPrimitives && !IsPrimitive(propertyInfo.PropertyType)) - { - containsOnlyPrimitives = false; - } } foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) { - PropertyMetadata metadata = GetPropertyMetadata(fieldInfo); + PropertyGenerationSpec metadata = GetPropertyGenerationSpec(fieldInfo); - if (metadata.HasGetter || metadata.HasSetter) + if (metadata.CanUseGetter || metadata.CanUseSetter) { (propertiesMetadata ??= new()).Add(metadata); } @@ -315,25 +490,24 @@ private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyNam } typeMetadata.Initialize( - compilableName: type.GetUniqueCompilableTypeName(), - friendlyName: typeInfoPropertyName ?? type.GetFriendlyTypeName(), + typeRef: type.GetUniqueCompilableTypeName(), + typeInfoPropertyName: type.GetFriendlyTypeName(), type, classType, isValueType: type.IsValueType, numberHandling, propertiesMetadata, collectionType, - collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeMetadata(collectionKeyType) : null, - collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeMetadata(collectionValueType) : null, + collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType) : null, + collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType) : null, constructionStrategy, - nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeMetadata(nullableUnderlyingType) : null, - converterInstatiationLogic, - containsOnlyPrimitives); + nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeGenerationSpec(nullableUnderlyingType) : null, + converterInstatiationLogic); return typeMetadata; } - private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) + private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo) { IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo); @@ -399,8 +573,9 @@ private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) } Type memberCLRType; - bool hasGetter; - bool hasSetter; + bool isReadOnly; + bool canUseGetter; + bool canUseSetter; bool getterIsVirtual = false; bool setterIsVirtual = false; @@ -409,10 +584,10 @@ private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) case PropertyInfo propertyInfo: { MethodInfo setMethod = propertyInfo.SetMethod; - memberCLRType = propertyInfo.PropertyType; - hasGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude); - hasSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly(); + isReadOnly = setMethod == null; + canUseGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude); + canUseSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly(); getterIsVirtual = propertyInfo.GetMethod?.IsVirtual == true; setterIsVirtual = propertyInfo.SetMethod?.IsVirtual == true; } @@ -420,30 +595,31 @@ private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) case FieldInfo fieldInfo: { Debug.Assert(fieldInfo.IsPublic); - memberCLRType = fieldInfo.FieldType; - hasGetter = true; - hasSetter = !fieldInfo.IsInitOnly; + isReadOnly = fieldInfo.IsInitOnly; + canUseGetter = true; + canUseSetter = !isReadOnly; } break; default: throw new InvalidOperationException(); } - return new PropertyMetadata + return new PropertyGenerationSpec { ClrName = memberInfo.Name, IsProperty = memberInfo.MemberType == MemberTypes.Property, JsonPropertyName = jsonPropertyName, - HasGetter = hasGetter, - HasSetter = hasSetter, + IsReadOnly = isReadOnly, + CanUseGetter = canUseGetter, + CanUseSetter = canUseSetter, GetterIsVirtual = getterIsVirtual, SetterIsVirtual = setterIsVirtual, - IgnoreCondition = ignoreCondition, + DefaultIgnoreCondition = ignoreCondition, NumberHandling = numberHandling, HasJsonInclude = hasJsonInclude, - TypeMetadata = GetOrAddTypeMetadata(memberCLRType), - DeclaringTypeCompilableName = memberInfo.DeclaringType.GetUniqueCompilableTypeName(), + TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType), + DeclaringTypeRef = $"global::{memberInfo.DeclaringType.GetUniqueCompilableTypeName()}", ConverterInstantiationLogic = converterInstantiationLogic }; } @@ -458,7 +634,8 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor, return null; } - Type converterType = new TypeWrapper((ITypeSymbol)attributeData.ConstructorArguments[0].Value, _metadataLoadContext); + ITypeSymbol converterTypeSymbol = (ITypeSymbol)attributeData.ConstructorArguments[0].Value; + Type converterType = converterTypeSymbol.AsType(_metadataLoadContext); if (converterType == null || converterType.GetConstructor(Type.EmptyTypes) == null || converterType.IsNestedPrivate) { @@ -509,9 +686,6 @@ private void PopulateKnownTypes() _knownTypes.Add(_metadataLoadContext.Resolve(typeof(Version))); } - - private bool IsPrimitive(Type type) - => _knownTypes.Contains(type) && type != _uriType && type != _versionType; } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs index eb23225f37f295..d12d5ee5edb8f7 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs @@ -21,8 +21,8 @@ public sealed partial class JsonSourceGenerator : ISourceGenerator /// /// Helper for unit tests. /// - public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Key, p => p.Value.Type); - private Dictionary? _rootTypes; + public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type); + private List? _rootTypes; /// /// Registers a syntax resolver to receive compilation units. @@ -40,31 +40,32 @@ public void Initialize(GeneratorInitializationContext context) public void Execute(GeneratorExecutionContext executionContext) { SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver; - List compilationUnits = receiver.CompilationUnits; - if (compilationUnits == null) + List? contextClasses = receiver.ClassDeclarationSyntaxList; + if (contextClasses == null) { return; } Parser parser = new(executionContext.Compilation); - _rootTypes = parser.GetRootSerializableTypes(receiver.CompilationUnits); - - if (_rootTypes != null) + SourceGenerationSpec? spec = parser.GetGenerationSpec(receiver.ClassDeclarationSyntaxList); + if (spec != null) { - Emitter emitter = new(executionContext, _rootTypes); + _rootTypes = spec.ContextGenerationSpecList[0].RootSerializableTypes; + + Emitter emitter = new(executionContext, spec); emitter.Emit(); } } - internal sealed class SyntaxReceiver : ISyntaxReceiver + private sealed class SyntaxReceiver : ISyntaxReceiver { - public List? CompilationUnits { get; private set; } + public List? ClassDeclarationSyntaxList { get; private set; } public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - if (syntaxNode is CompilationUnitSyntax compilationUnit) + if (syntaxNode is ClassDeclarationSyntax cds) { - (CompilationUnits ??= new List()).Add(compilationUnit); + (ClassDeclarationSyntaxList ??= new List()).Add(cds); } } } diff --git a/src/libraries/System.Text.Json/gen/PropertyMetadata.cs b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs similarity index 80% rename from src/libraries/System.Text.Json/gen/PropertyMetadata.cs rename to src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs index 68a5a408e83131..5a9db6b60318fa 100644 --- a/src/libraries/System.Text.Json/gen/PropertyMetadata.cs +++ b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.SourceGeneration { [DebuggerDisplay("Name={Name}, Type={TypeMetadata}")] - internal class PropertyMetadata + internal class PropertyGenerationSpec { /// /// The CLR name of the property. @@ -24,17 +24,22 @@ internal class PropertyMetadata /// public string? JsonPropertyName { get; init; } + /// + /// Whether the property has a set method. + /// + public bool IsReadOnly { get; init; } + /// /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified) /// getter that can be referenced in generated source code. /// - public bool HasGetter { get; init; } + public bool CanUseGetter { get; init; } /// /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified) /// setter that can be referenced in generated source code. /// - public bool HasSetter { get; init; } + public bool CanUseSetter { get; init; } public bool GetterIsVirtual { get; init; } @@ -43,7 +48,7 @@ internal class PropertyMetadata /// /// The for the property. /// - public JsonIgnoreCondition? IgnoreCondition { get; init; } + public JsonIgnoreCondition? DefaultIgnoreCondition { get; init; } /// /// The for the property. @@ -56,14 +61,14 @@ internal class PropertyMetadata public bool HasJsonInclude { get; init; } /// - /// Metadata for the property's type. + /// Generation specification for the property's type. /// - public TypeMetadata TypeMetadata { get; init; } + public TypeGenerationSpec TypeGenerationSpec { get; init; } /// /// Compilable name of the property's declaring type. /// - public string DeclaringTypeCompilableName { get; init; } + public string DeclaringTypeRef { get; init; } /// /// Source code to instantiate design-time specified custom converter. diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs index 4bfb71a9ce8717..365b48e2fdada1 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs @@ -40,6 +40,8 @@ private static string GetFriendlyTypeName(string compilableName) public static Type NullableOfTType { get; set; } + public static Type ObjectArrayType { get; set; } + public static bool IsNullableValueType(this Type type, out Type? underlyingType) { Debug.Assert(NullableOfTType != null); diff --git a/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs b/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs new file mode 100644 index 00000000000000..f8bf0b19ca57ad --- /dev/null +++ b/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs @@ -0,0 +1,31 @@ +// 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.Text; + +namespace System.Text.Json.SourceGeneration +{ + internal class SourceGenerationSpec + { + public List ContextGenerationSpecList { get; init; } + + #region Known types + public Type BooleanType { get; init; } + public Type ByteArrayType { get; init; } + public Type CharType { get; init; } + public Type DateTimeType { get; init; } + public Type DateTimeOffsetType { get; init; } + public Type GuidType { get; init; } + public Type StringType { get; init; } + + public HashSet NumberTypes { private get; init; } + + public bool IsStringBasedType(Type type) + => type == StringType || type == DateTimeType || type == DateTimeOffsetType || type == GuidType; + + public bool IsNumberType(Type type) => NumberTypes.Contains(type); + #endregion + } +} 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 74dd28a5b00a35..5ed7281c65d432 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 @@ -22,15 +22,21 @@ + + + + + + - + @@ -44,6 +50,8 @@ - + + + diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs new file mode 100644 index 00000000000000..6133e595cbb762 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -0,0 +1,105 @@ +// 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.Text.Json.Serialization; +using System.Text.Json.SourceGeneration.Reflection; + +namespace System.Text.Json.SourceGeneration +{ + [DebuggerDisplay("Type={Type}, ClassType={ClassType}")] + internal class TypeGenerationSpec + { + private bool _hasBeenInitialized; + + /// + /// Fully qualified assembly name, prefaced with "global::", e.g. global::System.Numerics.BigInteger. + /// + public string TypeRef { get; private set; } + + /// + /// The name of the public JsonTypeInfo property for this type on the generated context class. + /// For example, if the context class is named MyJsonContext, and the value of this property is JsonMessage; + /// then users will call MyJsonContext.JsonMessage to access generated metadata for the type. + /// + public string TypeInfoPropertyName { get; set; } + + public bool GenerateMetadata { get; set; } = true; + + private bool? _generateSerializationLogic; + public bool GenerateSerializationLogic + { + get => _generateSerializationLogic ??= FastPathIsSupported(); + set => _generateSerializationLogic = value; + } + + public Type Type { get; private set; } + + public ClassType ClassType { get; private set; } + + public bool IsValueType { get; private set; } + + public bool CanBeNull { get; private set; } + + public JsonNumberHandling? NumberHandling { get; private set; } + + public List? PropertiesMetadata { get; private set; } + + public CollectionType CollectionType { get; private set; } + + public TypeGenerationSpec? CollectionKeyTypeMetadata { get; private set; } + + public TypeGenerationSpec? CollectionValueTypeMetadata { get; private set; } + + public ObjectConstructionStrategy ConstructionStrategy { get; private set; } + + public TypeGenerationSpec? NullableUnderlyingTypeMetadata { get; private set; } + + public string? ConverterInstantiationLogic { get; private set; } + + public void Initialize( + string typeRef, + string typeInfoPropertyName, + Type type, + ClassType classType, + bool isValueType, + JsonNumberHandling? numberHandling, + List? propertiesMetadata, + CollectionType collectionType, + TypeGenerationSpec? collectionKeyTypeMetadata, + TypeGenerationSpec? collectionValueTypeMetadata, + ObjectConstructionStrategy constructionStrategy, + TypeGenerationSpec? nullableUnderlyingTypeMetadata, + string? converterInstantiationLogic) + { + if (_hasBeenInitialized) + { + throw new InvalidOperationException("Type metadata has already been initialized."); + } + + _hasBeenInitialized = true; + + TypeRef = $"global::{typeRef}"; + TypeInfoPropertyName = typeInfoPropertyName; + Type = type; + ClassType = classType; + IsValueType = isValueType; + CanBeNull = !isValueType || nullableUnderlyingTypeMetadata != null; + NumberHandling = numberHandling; + PropertiesMetadata = propertiesMetadata; + CollectionType = collectionType; + CollectionKeyTypeMetadata = collectionKeyTypeMetadata; + CollectionValueTypeMetadata = collectionValueTypeMetadata; + ConstructionStrategy = constructionStrategy; + NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata; + ConverterInstantiationLogic = converterInstantiationLogic; + } + + public bool FastPathIsSupported() + => Type != TypeExtensions.ObjectArrayType && (ClassType == ClassType.Object || + CollectionType == CollectionType.Dictionary || + CollectionType == CollectionType.Array || + CollectionType == CollectionType.List); + } +} diff --git a/src/libraries/System.Text.Json/gen/TypeMetadata.cs b/src/libraries/System.Text.Json/gen/TypeMetadata.cs deleted file mode 100644 index 7f9ea6385f2150..00000000000000 --- a/src/libraries/System.Text.Json/gen/TypeMetadata.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Text.Json.Serialization; - -namespace System.Text.Json.SourceGeneration -{ - [DebuggerDisplay("Type={Type}, ClassType={ClassType}")] - internal class TypeMetadata - { - private bool _hasBeenInitialized; - - public string CompilableName { get; private set; } - - public string FriendlyName { get; private set; } - - public Type Type { get; private set; } - - public ClassType ClassType { get; private set; } - - public bool IsValueType { get; private set; } - - public JsonNumberHandling? NumberHandling { get; private set; } - - public List? PropertiesMetadata { get; private set; } - - public CollectionType CollectionType { get; private set; } - - public TypeMetadata? CollectionKeyTypeMetadata { get; private set; } - - public TypeMetadata? CollectionValueTypeMetadata { get; private set; } - - public ObjectConstructionStrategy ConstructionStrategy { get; private set; } - - public TypeMetadata? NullableUnderlyingTypeMetadata { get; private set; } - - public string? ConverterInstantiationLogic { get; private set; } - - public bool ContainsOnlyPrimitives { get; private set; } - - public void Initialize( - string compilableName, - string friendlyName, - Type type, - ClassType classType, - bool isValueType, - JsonNumberHandling? numberHandling, - List? propertiesMetadata, - CollectionType collectionType, - TypeMetadata? collectionKeyTypeMetadata, - TypeMetadata? collectionValueTypeMetadata, - ObjectConstructionStrategy constructionStrategy, - TypeMetadata? nullableUnderlyingTypeMetadata, - string? converterInstantiationLogic, - bool containsOnlyPrimitives) - { - if (_hasBeenInitialized) - { - throw new InvalidOperationException("Type metadata has already been initialized."); - } - - _hasBeenInitialized = true; - - CompilableName = compilableName; - FriendlyName = friendlyName; - Type = type; - ClassType = classType; - IsValueType = isValueType; - NumberHandling = numberHandling; - PropertiesMetadata = propertiesMetadata; - CollectionType = collectionType; - CollectionKeyTypeMetadata = collectionKeyTypeMetadata; - CollectionValueTypeMetadata = collectionValueTypeMetadata; - ConstructionStrategy = constructionStrategy; - NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata; - ConverterInstantiationLogic = converterInstantiationLogic; - ContainsOnlyPrimitives = containsOnlyPrimitives; - } - } -} 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 29c08c9ba1f7af..30c58ce17638d5 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -777,6 +777,11 @@ public sealed partial class JsonIncludeAttribute : System.Text.Json.Serializatio { public JsonIncludeAttribute() { } } + public enum JsonKnownNamingPolicy + { + Unspecified = 0, + BuiltInCamelCase = 1, + } [System.FlagsAttribute] public enum JsonNumberHandling { @@ -797,18 +802,38 @@ public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Seriali public JsonPropertyNameAttribute(string name) { } public string Name { get { throw null; } } } - [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=true)] + [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)] public sealed partial class JsonSerializableAttribute : System.Text.Json.Serialization.JsonAttribute { public JsonSerializableAttribute(System.Type type) { } public string? TypeInfoPropertyName { get { throw null; } set { } } + public System.Text.Json.Serialization.JsonSourceGenerationMode GenerationMode { get { throw null; } set { } } } public abstract partial class JsonSerializerContext { - protected JsonSerializerContext(System.Text.Json.JsonSerializerOptions? options) { } + protected JsonSerializerContext(System.Text.Json.JsonSerializerOptions? instanceOptions, System.Text.Json.JsonSerializerOptions? defaultOptions) { } public System.Text.Json.JsonSerializerOptions Options { get { throw null; } } public abstract System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(System.Type type); } + [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false)] + public partial class JsonSerializerOptionsAttribute : System.Text.Json.Serialization.JsonAttribute + { + public JsonSerializerOptionsAttribute() { } + public System.Text.Json.Serialization.JsonIgnoreCondition DefaultIgnoreCondition { get { throw null; } set { } } + public bool IgnoreReadOnlyFields { get { throw null; } set { } } + public bool IgnoreReadOnlyProperties { get { throw null; } set { } } + public bool IgnoreRuntimeCustomConverters { get { throw null; } set { } } + public bool IncludeFields { get { throw null; } set { } } + public System.Text.Json.Serialization.JsonKnownNamingPolicy NamingPolicy { get { throw null; } set { } } + public bool WriteIndented { get { throw null; } set { } } + } + [System.FlagsAttribute] + public enum JsonSourceGenerationMode + { + MetadataAndSerialization = 0, + Metadata = 1, + Serialization = 2, + } public sealed partial class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory { public JsonStringEnumConverter() { } @@ -870,15 +895,15 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.JsonConverter UInt64Converter { get { throw null; } } public static System.Text.Json.Serialization.JsonConverter UriConverter { get { throw null; } } public static System.Text.Json.Serialization.JsonConverter VersionConverter { get { throw null; } } - public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateArrayInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) { throw null; } - public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { 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) where TCollection : System.Collections.Generic.List { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateArrayInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { 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() where T : notnull { throw null; } public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, 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, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } 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 static void InitializeObjectInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo info, System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Func propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where T : notnull { } + public static void InitializeObjectInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo info, System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Func? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where T : notnull { } } public abstract partial class JsonPropertyInfo { @@ -891,5 +916,6 @@ internal JsonTypeInfo() { } public abstract partial class JsonTypeInfo : System.Text.Json.Serialization.Metadata.JsonTypeInfo { internal JsonTypeInfo() { } + public System.Action? Serialize { get { throw null; } } } } 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 90c3695734ca76..d9e132bd02a92b 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -1,4 +1,4 @@ - + true $(NetCoreAppCurrent);netstandard2.0;netcoreapp3.0;net461 @@ -22,8 +22,14 @@ + + + + + + @@ -78,7 +84,6 @@ - @@ -87,7 +92,7 @@ - + @@ -168,15 +173,12 @@ - - - diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs index 9b580248fb0c82..d11ce9e8dba700 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs @@ -9,7 +9,7 @@ namespace System.Text.Json.Serialization /// Instructs the System.Text.Json source generator to generate source code to help optimize performance /// when serializing and deserializing instances of the specified type and types in its object graph. /// - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class JsonSerializableAttribute : JsonAttribute { /// @@ -21,6 +21,11 @@ public sealed class JsonSerializableAttribute : JsonAttribute /// public string? TypeInfoPropertyName { get; set; } + /// + /// Determines what the source generator should generate for the type. + /// + public JsonSourceGenerationMode GenerationMode { get; set; } + /// /// Initializes a new instance of with the specified type. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs index 63f48da966f212..234c2c2fe076e5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs @@ -15,6 +15,8 @@ internal class ObjectDefaultConverter : JsonObjectConverter where T : notn { internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value) { + JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; + object obj; if (state.UseFastPath) @@ -26,12 +28,12 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert); } - if (state.Current.JsonTypeInfo.CreateObject == null) + if (jsonTypeInfo.CreateObject == null) { - ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(state.Current.JsonTypeInfo.Type, ref reader, ref state); + ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(jsonTypeInfo.Type, ref reader, ref state); } - obj = state.Current.JsonTypeInfo.CreateObject!()!; + obj = jsonTypeInfo.CreateObject!()!; // Process all properties. while (true) @@ -98,12 +100,12 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, if (state.Current.ObjectState < StackFrameObjectState.CreatedObject) { - if (state.Current.JsonTypeInfo.CreateObject == null) + if (jsonTypeInfo.CreateObject == null) { - ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(state.Current.JsonTypeInfo.Type, ref reader, ref state); + ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(jsonTypeInfo.Type, ref reader, ref state); } - obj = state.Current.JsonTypeInfo.CreateObject!()!; + obj = jsonTypeInfo.CreateObject!()!; state.Current.ReturnValue = obj; state.Current.ObjectState = StackFrameObjectState.CreatedObject; @@ -216,7 +218,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, // Check if we are trying to build the sorted cache. if (state.Current.PropertyRefCache != null) { - state.Current.JsonTypeInfo.UpdateSortedPropertyCache(ref state.Current); + jsonTypeInfo.UpdateSortedPropertyCache(ref state.Current); } value = (T)obj; @@ -224,12 +226,14 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, return true; } - internal override bool OnTryWrite( + internal sealed override bool OnTryWrite( Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) { + JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; + // Minimize boxing for structs by only boxing once here object objectValue = value!; @@ -244,10 +248,10 @@ internal override bool OnTryWrite( } } - JsonPropertyInfo? dataExtensionProperty = state.Current.JsonTypeInfo.DataExtensionProperty; + JsonPropertyInfo? dataExtensionProperty = jsonTypeInfo.DataExtensionProperty; int propertyCount = 0; - JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonTypeInfo.PropertyCacheArray; + JsonPropertyInfo[]? propertyCacheArray = jsonTypeInfo.PropertyCacheArray; if (propertyCacheArray != null) { propertyCount = propertyCacheArray.Length; @@ -302,10 +306,10 @@ internal override bool OnTryWrite( state.Current.ProcessedStartToken = true; } - JsonPropertyInfo? dataExtensionProperty = state.Current.JsonTypeInfo.DataExtensionProperty; + JsonPropertyInfo? dataExtensionProperty = jsonTypeInfo.DataExtensionProperty; int propertyCount = 0; - JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonTypeInfo.PropertyCacheArray; + JsonPropertyInfo[]? propertyCacheArray = jsonTypeInfo.PropertyCacheArray; if (propertyCacheArray != null) { propertyCount = propertyCacheArray.Length; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectSourceGenConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectSourceGenConverter.cs deleted file mode 100644 index 70f377d358cc19..00000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectSourceGenConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Serialization.Metadata; - -namespace System.Text.Json.Serialization.Converters -{ - /// - /// Implementation of JsonObjectConverter{T} for source-generated converters. - /// - internal sealed class ObjectSourceGenConverter : ObjectDefaultConverter where T : notnull - { - internal override bool OnTryRead( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options, - ref ReadStack state, - [MaybeNullWhen(false)] out T value) - { - JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; - if (jsonTypeInfo.PropertyCache == null) - { - jsonTypeInfo.InitializeDeserializePropCache(); - } - - return base.OnTryRead(ref reader, typeToConvert, options, ref state, out value); - } - - internal override bool OnTryWrite( - Utf8JsonWriter writer, - T value, - JsonSerializerOptions options, - ref WriteStack state) - { - JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; - if (jsonTypeInfo.PropertyCacheArray == null) - { - jsonTypeInfo.InitializeSerializePropCache(); - } - - return base.OnTryWrite(writer, value, options, ref state); - } - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs new file mode 100644 index 00000000000000..35e8f6283f3af9 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs @@ -0,0 +1,87 @@ +// 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.Text.Json.Serialization.Metadata; + +namespace System.Text.Json.Serialization.Converters +{ + /// + /// Provides a mechanism to invoke "fast-path" serialization logic via + /// . This type holds an optional + /// reference to an actual for the type + /// , to provide a fallback when the fast path cannot be used. + /// + /// The type to converter + internal class SourceGenConverter : JsonResumableConverter + { + private readonly Func> _converterCreator; + + 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. + private JsonConverter Converter + { + get + { + _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? ElementType => _elementType; + + public SourceGenConverter(Func> converterCreator, ConverterStrategy converterStrategy, Type? keyType, Type? elementType) + { + _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) + { + jsonTypeInfo.InitializeDeserializePropCache(); + } + + return Converter.OnTryRead(ref reader, typeToConvert, options, ref state, out value); + } + + internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) + { + JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; + + if (!state.SupportContinuation && jsonTypeInfo is JsonTypeInfo info && info.UseFastPathOnWrite) + { + Debug.Assert(info.Serialize != null); + info.Serialize(writer, value); + return true; + } + + if (_converterStrategy == ConverterStrategy.Object && jsonTypeInfo.PropertyCacheArray == null) + { + jsonTypeInfo.InitializeSerializePropCache(); + } + + return Converter.OnTryWrite(writer, value, options, ref state); + } + + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs deleted file mode 100644 index 300096f5d0047d..00000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Text.Json -{ - internal sealed class JsonDefaultNamingPolicy : JsonNamingPolicy - { - public override string ConvertName(string name) => name; - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs index e1d7eb14c6b0f8..ccf639bdec1793 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs @@ -1,8 +1,6 @@ // 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.CodeAnalysis; - namespace System.Text.Json.Serialization { /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs index f212494f81ec7a..16d00359e7ab4a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs @@ -41,15 +41,22 @@ private static bool WriteCore( private static void WriteUsingMetadata(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo) { - WriteStack state = default; - state.Initialize(jsonTypeInfo, supportContinuation: false); + if (jsonTypeInfo is JsonTypeInfo typedInfo && typedInfo.UseFastPathOnWrite) + { + typedInfo.Serialize!(writer, value); + } + else + { + WriteStack state = default; + state.Initialize(jsonTypeInfo, supportContinuation: false); - JsonConverter converter = jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase; - Debug.Assert(converter != null); + JsonConverter converter = jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase; + Debug.Assert(converter != null); - Debug.Assert(jsonTypeInfo.Options != null); + Debug.Assert(jsonTypeInfo.Options != null); - WriteCore(converter, writer, value, jsonTypeInfo.Options, ref state); + WriteCore(converter, writer, value, jsonTypeInfo.Options, ref state); + } } private static Type GetRuntimeType(in TValue value) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs index 12dd5952f03be5..009f38d108778a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs @@ -35,25 +35,33 @@ public JsonSerializerOptions Options } } + /// + /// The default run-time options for the context. It's values are defined at design-time via . + /// + internal JsonSerializerOptions? DefaultOptions { get; } + /// /// Creates an instance of and binds it with the indicated . /// - /// The run-time provided options for the context instance. + /// The run-time provided options for the context instance. + /// The default run-time options for the context. It's values are defined at design-time via . /// - /// If no options are passed, then no options are set until the context is bound using , + /// If no instance options are passed, then no options are set until the context is bound using , /// or until is called, where a new options instance is created and bound. /// - protected JsonSerializerContext(JsonSerializerOptions? options) + protected JsonSerializerContext(JsonSerializerOptions? instanceOptions, JsonSerializerOptions? defaultOptions) { - if (options != null) + DefaultOptions = defaultOptions; + + if (instanceOptions != null) { - if (options._context != null) + if (instanceOptions._context != null) { ThrowHelper.ThrowInvalidOperationException_JsonSerializerOptionsAlreadyBoundToContext(); } - _options = options; - options._context = this; + _options = instanceOptions; + instanceOptions._context = this; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs index ac6fe18b9dbda9..83c2819450c1c9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs @@ -9,7 +9,7 @@ namespace System.Text.Json.Serialization /// Converter to convert enums to and from strings. /// /// - /// Reading is case insensitive, writing can be customized via a . + /// Reading is case insensitive, writing can be customized via a . /// public sealed class JsonStringEnumConverter : JsonConverterFactory { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs index e4782e95f1c89c..12f3717ddfbdee 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs @@ -15,17 +15,21 @@ public static partial class JsonMetadataServices /// The to use. /// A instance representing the element type. /// The option to apply to number collection elements. + /// An optimized serialization implementation assuming pre-determined defaults. /// public static JsonTypeInfo CreateArrayInfo( JsonSerializerOptions options, JsonTypeInfo elementInfo, - JsonNumberHandling numberHandling) + JsonNumberHandling numberHandling, + Action? serializeFunc) => new JsonTypeInfoInternal( options, createObjectFunc: null, - new ArrayConverter(), + () => new ArrayConverter(), elementInfo, - numberHandling); + numberHandling, + serializeFunc, + typeof(TElement)); /// /// Creates metadata for types assignable to . @@ -36,19 +40,23 @@ public static JsonTypeInfo CreateArrayInfo( /// A to create an instance of the list when deserializing. /// A instance representing the element type. /// The option to apply to number collection elements. + /// An optimized serialization implementation assuming pre-determined defaults. /// public static JsonTypeInfo CreateListInfo( JsonSerializerOptions options, Func? createObjectFunc, JsonTypeInfo elementInfo, - JsonNumberHandling numberHandling) + JsonNumberHandling numberHandling, + Action? serializeFunc) where TCollection : List => new JsonTypeInfoInternal( options, createObjectFunc, - new ListOfTConverter(), + () => new ListOfTConverter(), elementInfo, - numberHandling); + numberHandling, + serializeFunc, + typeof(TElement)); /// /// Creates metadata for types assignable to . @@ -61,21 +69,26 @@ public static JsonTypeInfo CreateListInfo( /// A instance representing the key type. /// A instance representing the value type. /// The option to apply to number collection elements. + /// An optimized serialization implementation assuming pre-determined defaults. /// public static JsonTypeInfo CreateDictionaryInfo( JsonSerializerOptions options, Func createObjectFunc, JsonTypeInfo keyInfo, JsonTypeInfo valueInfo, - JsonNumberHandling numberHandling) + JsonNumberHandling numberHandling, + Action? serializeFunc) where TCollection : Dictionary where TKey : notnull => new JsonTypeInfoInternal( options, createObjectFunc, - new DictionaryOfTKeyTValueConverter(), + () => new DictionaryOfTKeyTValueConverter(), keyInfo, valueInfo, - numberHandling); + numberHandling, + serializeFunc, + typeof(TKey), + typeof(TValue)); } } 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 27a2fe109cd3f7..0688211059db59 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 @@ -91,6 +91,7 @@ public static JsonPropertyInfo CreatePropertyInfo( /// /// /// + /// /// /// Thrown when , , or is null. /// Thrown when , does not represent a complex class or struct type. @@ -98,8 +99,9 @@ public static void InitializeObjectInfo( JsonTypeInfo info, JsonSerializerOptions options, Func? createObjectFunc, - Func propInitFunc, - JsonNumberHandling numberHandling) + Func? propInitFunc, + JsonNumberHandling numberHandling, + Action? serializeFunc) where T : notnull { if (info == null) @@ -118,12 +120,7 @@ public static void InitializeObjectInfo( throw new ArgumentNullException(nameof(options)); } - if (propInitFunc == null) - { - throw new ArgumentNullException(nameof(propInitFunc)); - } - - ((JsonTypeInfoInternal)info).InitializeAsObject(options, createObjectFunc, propInitFunc, numberHandling); + ((JsonTypeInfoInternal)info).InitializeAsObject(options, createObjectFunc, propInitFunc, numberHandling, serializeFunc); Debug.Assert(info.PropertyInfoForTypeInfo!.ConverterStrategy == ConverterStrategy.Object); } 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 39dffda9170557..08c7fa53e6613f 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 @@ -568,14 +568,21 @@ internal void UpdateSortedParameterCache(ref ReadStackFrame frame) internal void InitializeSerializePropCache() { - Debug.Assert(PropInitFunc != null); Debug.Assert(Options._context != null); + Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object); + + if (PropInitFunc == null) + { + throw new NotSupportedException($"Property metadata was not provided for type {Type}."); + } PropertyCacheArray = PropInitFunc(Options._context); } internal void InitializeDeserializePropCache() { + Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object); + if (PropertyCacheArray == null) { InitializeSerializePropCache(); 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 38fe706f81d32a..a32d92132fd6d3 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.Diagnostics; using System.Text.Json.Serialization.Converters; namespace System.Text.Json.Serialization.Metadata @@ -32,10 +33,20 @@ public JsonTypeInfoInternal(JsonSerializerOptions options) public JsonTypeInfoInternal( JsonSerializerOptions options, Func? createObjectFunc, - JsonConverter converter, - JsonTypeInfo elementInfo, - JsonNumberHandling numberHandling) : base(typeof(T), options, ConverterStrategy.Enumerable) + Func> converterCreator, + JsonTypeInfo? elementInfo, + JsonNumberHandling numberHandling, + Action? serializeFunc, + Type elementType) : base(typeof(T), options, ConverterStrategy.Enumerable) { + if (serializeFunc != null) + { + Serialize = serializeFunc; + DetermineIfCanUseSerializeFastPath(); + } + + JsonConverter converter = new SourceGenConverter(converterCreator, ConverterStrategy.Enumerable, keyType: null, elementType); + ElementType = converter.ElementType; ElementTypeInfo = elementInfo ?? throw new ArgumentNullException(nameof(elementInfo)); NumberHandling = numberHandling; @@ -49,15 +60,27 @@ public JsonTypeInfoInternal( public JsonTypeInfoInternal( JsonSerializerOptions options, Func? createObjectFunc, - JsonConverter converter, - JsonTypeInfo keyInfo, - JsonTypeInfo valueInfo, - JsonNumberHandling numberHandling) : base(typeof(T), options, ConverterStrategy.Dictionary) + Func> converterCreator, + JsonTypeInfo? keyInfo, + JsonTypeInfo? valueInfo, + JsonNumberHandling numberHandling, + Action? serializeFunc, + Type keyType, + Type elementType) : base(typeof(T), options, ConverterStrategy.Dictionary) { + if (serializeFunc != null) + { + Serialize = serializeFunc; + DetermineIfCanUseSerializeFastPath(); + } + + JsonConverter converter = new SourceGenConverter(converterCreator, ConverterStrategy.Dictionary, keyType, elementType); + KeyType = converter.KeyType; + ElementType = converter.ElementType; KeyTypeInfo = keyInfo ?? throw new ArgumentNullException(nameof(keyInfo)); ; ElementType = converter.ElementType; - ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo)); + ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo)); ; NumberHandling = numberHandling; PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); SetCreateObjectFunc(createObjectFunc); @@ -69,20 +92,28 @@ public JsonTypeInfoInternal( public void InitializeAsObject( JsonSerializerOptions options, Func? createObjectFunc, - Func propInitFunc, - JsonNumberHandling numberHandling) + Func? propInitFunc, + JsonNumberHandling numberHandling, + Action? serializeFunc) { Options = options; + if (serializeFunc != null) + { + Serialize = serializeFunc; + DetermineIfCanUseSerializeFastPath(); + } + #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 ObjectSourceGenConverter(); + JsonConverter converter = new SourceGenConverter(() => new ObjectDefaultConverter(), ConverterStrategy.Object, keyType: null, elementType: null); #pragma warning restore CS8714 PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); NumberHandling = numberHandling; PropInitFunc = propInitFunc; + SetCreateObjectFunc(createObjectFunc); } @@ -93,5 +124,28 @@ private void SetCreateObjectFunc(Func? createObjectFunc) CreateObject = () => createObjectFunc(); } } + + private void DetermineIfCanUseSerializeFastPath() + { + Debug.Assert(Options == Options._context!.Options); + JsonSerializerOptions? contextDefaultOptions = Options._context!.DefaultOptions; + if (contextDefaultOptions == null) + { + throw new InvalidOperationException($"To specify a fast-path implementation for type {typeof(T)}, context {Options._context.GetType()} must specify default options."); + } + + UseFastPathOnWrite = + // Guard against unsupported features + Options.Encoder == null && + Options.NumberHandling == JsonNumberHandling.Strict && + Options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.None && + // Ensure options values are consistent with expected defaults. + Options.DefaultIgnoreCondition == contextDefaultOptions.DefaultIgnoreCondition && + Options.IgnoreReadOnlyFields == contextDefaultOptions.IgnoreReadOnlyFields && + Options.IgnoreReadOnlyProperties == contextDefaultOptions.IgnoreReadOnlyProperties && + Options.IncludeFields == contextDefaultOptions.IncludeFields && + Options.PropertyNamingPolicy == contextDefaultOptions.PropertyNamingPolicy && + Options.WriteIndented == contextDefaultOptions.WriteIndented; + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 6d3b7f0091cfea..272a59436ae282 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -21,5 +21,18 @@ internal JsonTypeInfo() { Debug.Assert(false, "This constructor should not be called."); } + + /// + /// A method that serializes an instance of using + /// values specified at design time. + /// + public Action? Serialize { get; internal set; } + + /// + /// When serializing objects with source-gen converters, indicates whether the + /// configured is compatible with the generated + /// func. + /// + internal bool UseFastPathOnWrite { get; set; } } } diff --git a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs new file mode 100644 index 00000000000000..8d53fc3c563181 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs @@ -0,0 +1,71 @@ +// 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 Xunit; + +namespace System.Text.Json +{ + internal static partial class JsonTestHelper + { + public static void AssertJsonEqual(string expected, string actual) + { + using JsonDocument expectedDom = JsonDocument.Parse(expected); + using JsonDocument actualDom = JsonDocument.Parse(actual); + AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement); + } + + private static void AssertJsonEqual(JsonElement expected, JsonElement actual) + { + JsonValueKind valueKind = expected.ValueKind; + Assert.Equal(valueKind, actual.ValueKind); + + switch (valueKind) + { + case JsonValueKind.Object: + var propertyNames = new HashSet(); + + foreach (JsonProperty property in expected.EnumerateObject()) + { + propertyNames.Add(property.Name); + } + + foreach (JsonProperty property in actual.EnumerateObject()) + { + propertyNames.Add(property.Name); + } + + foreach (string name in propertyNames) + { + AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name)); + } + break; + case JsonValueKind.Array: + JsonElement.ArrayEnumerator expectedEnumerator = actual.EnumerateArray(); + JsonElement.ArrayEnumerator actualEnumerator = expected.EnumerateArray(); + + while (expectedEnumerator.MoveNext()) + { + Assert.True(actualEnumerator.MoveNext()); + AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current); + } + + Assert.False(actualEnumerator.MoveNext()); + break; + case JsonValueKind.String: + Assert.Equal(expected.GetString(), actual.GetString()); + break; + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + Assert.Equal(expected.GetRawText(), actual.GetRawText()); + break; + default: + Debug.Fail($"Unexpected JsonValueKind: JsonValueKind.{valueKind}."); + break; + } + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs similarity index 70% rename from src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs rename to src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs index cc14bdcde626cc..e7859c499dc1d2 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs @@ -2,101 +2,126 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -using System.Text.Json.SourceGeneration.Tests; -using System.Text.Json.SourceGeneration.Tests.JsonSourceGeneration; using Xunit; -[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.MyNestedClass))] -[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.MyNestedClass.MyNestedNestedClass))] -[assembly: JsonSerializable(typeof(object[]))] -[assembly: JsonSerializable(typeof(string))] -[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.ClassWithEnumAndNullable))] - namespace System.Text.Json.SourceGeneration.Tests { - public static class JsonSerializerSourceGeneratorTests + public interface ITestContext + { + public JsonTypeInfo Location { get; } + public JsonTypeInfo RepeatedLocation { get; } + public JsonTypeInfo ActiveOrUpcomingEvent { get; } + public JsonTypeInfo CampaignSummaryViewModel { get; } + public JsonTypeInfo IndexViewModel { get; } + public JsonTypeInfo WeatherForecastWithPOCOs { get; } + public JsonTypeInfo EmptyPoco { get; } + public JsonTypeInfo HighLowTemps { get; } + public JsonTypeInfo MyType { get; } + public JsonTypeInfo MyType2 { get; } + public JsonTypeInfo MyIntermediateType { get; } + public JsonTypeInfo HighLowTempsImmutable { get; } + public JsonTypeInfo MyNestedClass { get; } + public JsonTypeInfo MyNestedNestedClass { get; } + public JsonTypeInfo ObjectArray { get; } + public JsonTypeInfo String { get; } + public JsonTypeInfo ClassWithEnumAndNullable { get; } + } + + public abstract class ContextTests { + protected ITestContext DefaultContext { get; } + private Func _contextCreator; + + public ContextTests(ITestContext defaultContext, Func contextCreator) + { + DefaultContext = defaultContext; + _contextCreator = contextCreator; + } + + public abstract void EnsureFastPathGeneratedAsExpected(); + [Fact] - public static void RoundTripLocation() + public virtual void RoundTripLocation() { Location expected = CreateLocation(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.Location); - Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.Location); + string json = JsonSerializer.Serialize(expected, DefaultContext.Location); + Location obj = JsonSerializer.Deserialize(json, DefaultContext.Location); VerifyLocation(expected, obj); } [Fact] - public static void RoundTripIndexViewModel() + public virtual void RoundTripIndexViewModel() { IndexViewModel expected = CreateIndexViewModel(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.IndexViewModel); - IndexViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Default.IndexViewModel); + string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); + IndexViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel); VerifyIndexViewModel(expected, obj); } [Fact] - public static void RoundTripCampaignSummaryViewModel() + public virtual void RoundTripCampaignSummaryViewModel() { CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.CampaignSummaryViewModel); - CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Default.CampaignSummaryViewModel); + string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel); VerifyCampaignSummaryViewModel(expected, obj); } [Fact] - public static void RoundTripActiveOrUpcomingEvent() + public virtual void RoundTripActiveOrUpcomingEvent() { ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.ActiveOrUpcomingEvent); - ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, JsonContext.Default.ActiveOrUpcomingEvent); + string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); + ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent); VerifyActiveOrUpcomingEvent(expected, obj); } [Fact] - public static void RoundTripCollectionsDictionary() + public virtual void RoundTripCollectionsDictionary() { WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.WeatherForecastWithPOCOs); - WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, JsonContext.Default.WeatherForecastWithPOCOs); + string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs); VerifyWeatherForecastWithPOCOs(expected, obj); } [Fact] - public static void RoundTripEmptyPoco() + public virtual void RoundTripEmptyPoco() { EmptyPoco expected = CreateEmptyPoco(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.EmptyPoco); - EmptyPoco obj = JsonSerializer.Deserialize(json, JsonContext.Default.EmptyPoco); + string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); + EmptyPoco obj = JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco); VerifyEmptyPoco(expected, obj); } [Fact] - public static void RoundTripTypeNameClash() + public virtual void RoundTripTypeNameClash() { RepeatedTypes.Location expected = CreateRepeatedLocation(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.RepeatedLocation); - RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.RepeatedLocation); + string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation); VerifyRepeatedLocation(expected, obj); } - private static Location CreateLocation() + protected static Location CreateLocation() { return new Location { @@ -112,7 +137,7 @@ private static Location CreateLocation() }; } - private static void VerifyLocation(Location expected, Location obj) + protected static void VerifyLocation(Location expected, Location obj) { Assert.Equal(expected.Address1, obj.Address1); Assert.Equal(expected.Address2, obj.Address2); @@ -124,7 +149,7 @@ private static void VerifyLocation(Location expected, Location obj) Assert.Equal(expected.Country, obj.Country); } - private static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() + protected static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() { return new ActiveOrUpcomingEvent { @@ -139,7 +164,7 @@ private static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() }; } - private static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj) + protected static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj) { Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName); Assert.Equal(expected.CampaignName, obj.CampaignName); @@ -151,7 +176,7 @@ private static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, Assert.Equal(expected.StartDate, obj.StartDate); } - private static CampaignSummaryViewModel CreateCampaignSummaryViewModel() + protected static CampaignSummaryViewModel CreateCampaignSummaryViewModel() { return new CampaignSummaryViewModel { @@ -164,7 +189,7 @@ private static CampaignSummaryViewModel CreateCampaignSummaryViewModel() }; } - private static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj) + protected static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj) { Assert.Equal(expected.Description, obj.Description); Assert.Equal(expected.Headline, obj.Headline); @@ -174,7 +199,7 @@ private static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expe Assert.Equal(expected.Title, obj.Title); } - private static IndexViewModel CreateIndexViewModel() + protected static IndexViewModel CreateIndexViewModel() { return new IndexViewModel { @@ -204,7 +229,7 @@ private static IndexViewModel CreateIndexViewModel() }; } - private static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj) + protected static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj) { Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count); for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++) @@ -217,7 +242,7 @@ private static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel Assert.Equal(expected.IsNewAccount, obj.IsNewAccount); } - private static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() + protected static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() { return new WeatherForecastWithPOCOs { @@ -251,7 +276,7 @@ private static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() }; } - private static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj) + protected static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj) { Assert.Equal(expected.Date, obj.Date); Assert.Equal(expected.TemperatureCelsius, obj.TemperatureCelsius); @@ -277,7 +302,7 @@ private static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expe } } - private static RepeatedTypes.Location CreateRepeatedLocation() + protected static RepeatedTypes.Location CreateRepeatedLocation() { return new RepeatedTypes.Location { @@ -293,7 +318,7 @@ private static RepeatedTypes.Location CreateRepeatedLocation() }; } - private static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) + protected static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) { Assert.Equal(expected.FakeAddress1, obj.FakeAddress1); Assert.Equal(expected.FakeAddress2, obj.FakeAddress2); @@ -305,51 +330,51 @@ private static void VerifyRepeatedLocation(RepeatedTypes.Location expected, Repe Assert.Equal(expected.FakeCountry, obj.FakeCountry); } - private static EmptyPoco CreateEmptyPoco() => new EmptyPoco(); + protected static EmptyPoco CreateEmptyPoco() => new EmptyPoco(); - private static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj) + protected static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj) { Assert.NotNull(expected); Assert.NotNull(obj); } [Fact] - public static void NestedSameTypeWorks() + public virtual void NestedSameTypeWorks() { MyType myType = new() { Type = new() }; - string json = JsonSerializer.Serialize(myType, JsonContext.Default.MyType); - myType = JsonSerializer.Deserialize(json, JsonContext.Default.MyType); - Assert.Equal(json, JsonSerializer.Serialize(myType, JsonContext.Default.MyType)); + string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); + myType = JsonSerializer.Deserialize(json, DefaultContext.MyType); + Assert.Equal(json, JsonSerializer.Serialize(myType, DefaultContext.MyType)); MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; - json = JsonSerializer.Serialize(myType2, JsonContext.Default.MyType2); - myType2 = JsonSerializer.Deserialize(json, JsonContext.Default.MyType2); - Assert.Equal(json, JsonSerializer.Serialize(myType2, JsonContext.Default.MyType2)); + json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); + myType2 = JsonSerializer.Deserialize(json, DefaultContext.MyType2); + Assert.Equal(json, JsonSerializer.Serialize(myType2, DefaultContext.MyType2)); } [Fact] - public static void SerializeObjectArray() + public virtual void SerializeObjectArray() { IndexViewModel index = CreateIndexViewModel(); CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, JsonContext.Default.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, JsonContext.Default.ObjectArray); + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, DefaultContext.ObjectArray); JsonElement indexAsJsonElement = (JsonElement)arr[0]; JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), JsonContext.Default.IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), JsonContext.Default.CampaignSummaryViewModel)); + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), DefaultContext.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), DefaultContext.CampaignSummaryViewModel)); } [Fact] - public static void SerializeObjectArray_WithCustomOptions() + public virtual void SerializeObjectArray_WithCustomOptions() { IndexViewModel index = CreateIndexViewModel(); CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - JsonContext context = new(options); + ITestContext context = _contextCreator(options); string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); object[] arr = JsonSerializer.Deserialize(json, context.ObjectArray); @@ -361,13 +386,13 @@ public static void SerializeObjectArray_WithCustomOptions() } [Fact] - public static void SerializeObjectArray_SimpleTypes_WithCustomOptions() + public virtual void SerializeObjectArray_SimpleTypes_WithCustomOptions() { JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - JsonContext context = new JsonContext(options); + ITestContext context = _contextCreator(options); - string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), context); - object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), context); + string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); + object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)context); JsonElement hello = (JsonElement)arr[0]; JsonElement world = (JsonElement)arr[1]; @@ -376,16 +401,16 @@ public static void SerializeObjectArray_SimpleTypes_WithCustomOptions() } [Fact] - public static void HandlesNestedTypes() + public virtual void HandlesNestedTypes() { string json = @"{""MyInt"":5}"; - MyNestedClass obj = JsonSerializer.Deserialize(json, JsonContext.Default.MyNestedClass); + MyNestedClass obj = JsonSerializer.Deserialize(json, DefaultContext.MyNestedClass); Assert.Equal(5, obj.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj, JsonContext.Default.MyNestedClass)); + Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, JsonContext.Default.MyNestedNestedClass); + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, DefaultContext.MyNestedNestedClass); Assert.Equal(5, obj2.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj2, JsonContext.Default.MyNestedNestedClass)); + Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); } public class MyNestedClass @@ -399,7 +424,7 @@ public class MyNestedNestedClass } [Fact] - public static void ConstructingFromOptionsKeepsReference() + public void ConstructingFromOptionsKeepsReference() { JsonStringEnumConverter converter = new(); JsonSerializerOptions options = new() @@ -408,29 +433,29 @@ public static void ConstructingFromOptionsKeepsReference() Converters = { converter } }; - JsonContext context = new(options); + JsonSerializerContext context = (JsonSerializerContext)_contextCreator(options); Assert.Same(options, context.Options); Assert.Equal(options.PropertyNameCaseInsensitive, context.Options.PropertyNameCaseInsensitive); Assert.Same(converter, context.Options.Converters[0]); } [Fact] - public static void JsonContextDefaultClonesDefaultOptions() + public void JsonContextDefaultClonesDefaultOptions() { - JsonContext context = JsonContext.Default; + JsonSerializerContext context = (JsonSerializerContext)DefaultContext; Assert.Equal(0, context.Options.Converters.Count); } [Fact] - public static void JsonContextOptionsNotMutableAfterConstruction() + public void JsonContextOptionsNotMutableAfterConstruction() { - JsonContext context = JsonContext.Default; + JsonSerializerContext context = (JsonSerializerContext)DefaultContext; InvalidOperationException ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); string exAsStr = ex.ToString(); Assert.Contains("JsonSerializerOptions", exAsStr); Assert.Contains("JsonSerializerContext", exAsStr); - context = new JsonContext(new JsonSerializerOptions()); + context = (JsonSerializerContext)_contextCreator(new JsonSerializerOptions()); ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); exAsStr = ex.ToString(); Assert.Contains("JsonSerializerOptions", exAsStr); @@ -438,26 +463,26 @@ public static void JsonContextOptionsNotMutableAfterConstruction() } [Fact] - public static void ParameterizedConstructor() + public void ParameterizedConstructor() { - string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), JsonContext.Default.HighLowTempsImmutable); + string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); Assert.Contains(@"""High"":1", json); Assert.Contains(@"""Low"":2", json); // Deserialization not supported for now. - Assert.Throws(() => JsonSerializer.Deserialize(json, JsonContext.Default.HighLowTempsImmutable)); + Assert.Throws(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable)); } [Fact] - public static void EnumAndNullable() + public virtual void EnumAndNullable() { RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); RunTest(new ClassWithEnumAndNullable()); - static void RunTest(ClassWithEnumAndNullable expected) + void RunTest(ClassWithEnumAndNullable expected) { - string json = JsonSerializer.Serialize(expected, JsonContext.Default.ClassWithEnumAndNullable); - ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, JsonContext.Default.ClassWithEnumAndNullable); + string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); + ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, DefaultContext.ClassWithEnumAndNullable); Assert.Equal(expected.Day, actual.Day); Assert.Equal(expected.NullableDay, actual.NullableDay); } @@ -470,13 +495,13 @@ public class ClassWithEnumAndNullable } [Fact] - public static void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent() + public void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent() { object[] objArr = new object[] { new MyStruct() }; // Metadata not generated for MyStruct without JsonSerializableAttribute. NotSupportedException ex = Assert.Throws( - () => JsonSerializer.Serialize(objArr, JsonContext.Default.ObjectArray)); + () => JsonSerializer.Serialize(objArr, DefaultContext.ObjectArray)); string exAsStr = ex.ToString(); Assert.Contains(typeof(MyStruct).ToString(), exAsStr); Assert.Contains("JsonSerializerOptions", exAsStr); @@ -493,7 +518,7 @@ public static void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresen AssertFieldNull("s_defaultFactoryConverters", optionsInstance: null); // Confirm type info dynamic creator not set. - AssertFieldNull("_typeInfoCreationFunc", JsonContext.Default.Options); + AssertFieldNull("_typeInfoCreationFunc", ((JsonSerializerContext)DefaultContext).Options); static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInstance) { @@ -507,7 +532,7 @@ static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInst private const string ExceptionMessageFromCustomContext = "Exception thrown from custom context."; [Fact] - public static void GetTypeInfoCalledDuringPolymorphicSerialization() + public void GetTypeInfoCalledDuringPolymorphicSerialization() { CustomContext context = new(new JsonSerializerOptions()); @@ -529,13 +554,13 @@ internal struct MyStruct { } internal class CustomContext : JsonSerializerContext { - public CustomContext(JsonSerializerOptions options) : base(options) { } + public CustomContext(JsonSerializerOptions options) : base(options, null) { } private JsonTypeInfo _object; public JsonTypeInfo Object => _object ??= JsonMetadataServices.CreateValueInfo(Options, JsonMetadataServices.ObjectConverter); private JsonTypeInfo _objectArray; - public JsonTypeInfo ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo(Options, Object, default); + public JsonTypeInfo ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo(Options, Object, default, serializeFunc: null); public override JsonTypeInfo GetTypeInfo(Type type) { @@ -547,5 +572,22 @@ public override JsonTypeInfo GetTypeInfo(Type type) throw new InvalidOperationException(ExceptionMessageFromCustomContext); } } + + protected void AssertThrowsNSEPropMetadataInit(Action action, Type type) + { + var ex = Assert.Throws(action); + string exAsStr = ex.ToString(); + Assert.Contains(type.ToString(), exAsStr); + } + + protected void AssertFastPathLogicCorrect(string expectedJson, T value, JsonTypeInfo typeInfo) + { + using MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + typeInfo.Serialize!(writer, value); + writer.Flush(); + string json = Encoding.UTF8.GetString(ms.ToArray()); + JsonTestHelper.AssertJsonEqual(expectedJson, json); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs new file mode 100644 index 00000000000000..7a9086ebc6b581 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location))] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent))] + [JsonSerializable(typeof(CampaignSummaryViewModel))] + [JsonSerializable(typeof(IndexViewModel))] + [JsonSerializable(typeof(WeatherForecastWithPOCOs))] + [JsonSerializable(typeof(EmptyPoco))] + // Ensure no errors when type of member in previously specified object graph is passed as input type to generator. + [JsonSerializable(typeof(HighLowTemps))] + [JsonSerializable(typeof(MyType))] + [JsonSerializable(typeof(MyType2))] + [JsonSerializable(typeof(MyIntermediateType))] + [JsonSerializable(typeof(HighLowTempsImmutable))] + [JsonSerializable(typeof(ContextTests.MyNestedClass))] + [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass))] + [JsonSerializable(typeof(object[]))] + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable))] + internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext + { + } + + public sealed class MetadataAndSerializationContextTests : ContextTests + { + public MetadataAndSerializationContextTests() : base(MetadataAndSerializationContext.Default, (options) => new MetadataAndSerializationContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.NotNull(MetadataAndSerializationContext.Default.Location.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.RepeatedLocation.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.CampaignSummaryViewModel.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.IndexViewModel.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.EmptyPoco.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTemps.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataAndSerializationContext.Default.String.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs new file mode 100644 index 00000000000000..e67da90ddae3ad --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] + internal partial class MetadataContext : JsonSerializerContext, ITestContext + { + } + + public sealed class MetadataContextTests : ContextTests + { + public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.Null(MetadataContext.Default.Location.Serialize); + Assert.Null(MetadataContext.Default.RepeatedLocation.Serialize); + Assert.Null(MetadataContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.Null(MetadataContext.Default.CampaignSummaryViewModel.Serialize); + Assert.Null(MetadataContext.Default.IndexViewModel.Serialize); + Assert.Null(MetadataContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.Null(MetadataContext.Default.EmptyPoco.Serialize); + Assert.Null(MetadataContext.Default.HighLowTemps.Serialize); + Assert.Null(MetadataContext.Default.MyType.Serialize); + Assert.Null(MetadataContext.Default.MyType2.Serialize); + Assert.Null(MetadataContext.Default.MyIntermediateType.Serialize); + Assert.Null(MetadataContext.Default.HighLowTempsImmutable.Serialize); + Assert.Null(MetadataContext.Default.MyNestedClass.Serialize); + Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(MetadataContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataContext.Default.String.Serialize); + Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs new file mode 100644 index 00000000000000..3f3f8072b7ec23 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + internal partial class MixedModeContext : JsonSerializerContext, ITestContext + { + } + + public sealed class MixedModeContextTests : ContextTests + { + public MixedModeContextTests() : base(MixedModeContext.Default, (options) => new MixedModeContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.Null(MixedModeContext.Default.Location.Serialize); + Assert.NotNull(MixedModeContext.Default.RepeatedLocation.Serialize); + Assert.NotNull(MixedModeContext.Default.CampaignSummaryViewModel.Serialize); + Assert.Null(MixedModeContext.Default.IndexViewModel.Serialize); + Assert.Null(MixedModeContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(MixedModeContext.Default.EmptyPoco.Serialize); + Assert.NotNull(MixedModeContext.Default.HighLowTemps.Serialize); + Assert.NotNull(MixedModeContext.Default.MyType.Serialize); + Assert.NotNull(MixedModeContext.Default.MyType2.Serialize); + Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize); + Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize); + Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize); + Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(MixedModeContext.Default.ObjectArray.Serialize); + Assert.Null(MixedModeContext.Default.String.Serialize); + Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize); + } + + [Fact] + public override void RoundTripIndexViewModel() + { + IndexViewModel expected = CreateIndexViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); + + IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); + VerifyIndexViewModel(expected, obj); + } + + [Fact] + public override void RoundTripCampaignSummaryViewModel() + { + CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); + VerifyCampaignSummaryViewModel(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); + } + + [Fact] + public override void RoundTripCollectionsDictionary() + { + WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); + + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); + VerifyWeatherForecastWithPOCOs(expected, obj); + } + + [Fact] + public override void RoundTripEmptyPoco() + { + EmptyPoco expected = CreateEmptyPoco(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + + EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); + VerifyEmptyPoco(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); + } + + [Fact] + public override void RoundTripTypeNameClash() + { + RepeatedTypes.Location expected = CreateRepeatedLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); + VerifyRepeatedLocation(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); + } + + [Fact] + public override void HandlesNestedTypes() + { + string json = @"{""MyInt"":5}"; + MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); + Assert.Equal(5, obj.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); + Assert.Equal(5, obj2.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); + } + + [Fact] + public override void SerializeObjectArray() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); + } + + [Fact] + public override void SerializeObjectArray_WithCustomOptions() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + ITestContext context = SerializationContextWithCamelCase.Default; + Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + + ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs new file mode 100644 index 00000000000000..3e137584e9ca89 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs @@ -0,0 +1,268 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] + internal partial class SerializationContext : JsonSerializerContext, ITestContext + { + } + + [JsonSerializerOptions(NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)] + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] + internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext + { + } + + public sealed class SerializationContextTests : ContextTests + { + public SerializationContextTests() : base(SerializationContext.Default, (options) => new SerializationContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.NotNull(SerializationContext.Default.Location.Serialize); + Assert.NotNull(SerializationContext.Default.RepeatedLocation.Serialize); + Assert.NotNull(SerializationContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.NotNull(SerializationContext.Default.CampaignSummaryViewModel.Serialize); + Assert.NotNull(SerializationContext.Default.IndexViewModel.Serialize); + Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(SerializationContext.Default.HighLowTemps.Serialize); + Assert.NotNull(SerializationContext.Default.MyType.Serialize); + Assert.NotNull(SerializationContext.Default.MyType2.Serialize); + Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize); + Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize); + Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize); + Assert.NotNull(SerializationContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(SerializationContext.Default.ObjectArray.Serialize); + Assert.Null(SerializationContext.Default.String.Serialize); + Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize); + } + + [Fact] + public override void RoundTripLocation() + { + Location expected = CreateLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.Location); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); + + Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location); + VerifyLocation(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.Location); + } + + [Fact] + public override void RoundTripIndexViewModel() + { + IndexViewModel expected = CreateIndexViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); + + IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); + VerifyIndexViewModel(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.IndexViewModel); + } + + [Fact] + public override void RoundTripCampaignSummaryViewModel() + { + CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); + VerifyCampaignSummaryViewModel(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); + } + + [Fact] + public override void RoundTripActiveOrUpcomingEvent() + { + ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); + + ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent); + VerifyActiveOrUpcomingEvent(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.ActiveOrUpcomingEvent); + } + + [Fact] + public override void RoundTripCollectionsDictionary() + { + WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); + + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); + VerifyWeatherForecastWithPOCOs(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.WeatherForecastWithPOCOs); + } + + [Fact] + public override void RoundTripEmptyPoco() + { + EmptyPoco expected = CreateEmptyPoco(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + + EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); + VerifyEmptyPoco(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); + } + + [Fact] + public override void RoundTripTypeNameClash() + { + RepeatedTypes.Location expected = CreateRepeatedLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); + AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); + VerifyRepeatedLocation(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); + } + + [Fact] + public override void NestedSameTypeWorks() + { + MyType myType = new() { Type = new() }; + string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); + myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType); + AssertFastPathLogicCorrect(json, myType, DefaultContext.MyType); + + MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; + json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); + myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType2); + AssertFastPathLogicCorrect(json, myType2, DefaultContext.MyType2); + } + + [Fact] + public override void SerializeObjectArray() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); + } + + [Fact] + public override void SerializeObjectArray_WithCustomOptions() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + ITestContext context = SerializationContextWithCamelCase.Default; + Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + + ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); + } + + [Fact] + public override void SerializeObjectArray_SimpleTypes_WithCustomOptions() + { + JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + ITestContext context = new SerializationContext(options); + + string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); + object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataContext.Default)); + + JsonElement hello = (JsonElement)arr[0]; + JsonElement world = (JsonElement)arr[1]; + Assert.Equal("\"Hello\"", hello.GetRawText()); + Assert.Equal("\"World\"", world.GetRawText()); + } + + [Fact] + public override void HandlesNestedTypes() + { + string json = @"{""MyInt"":5}"; + MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); + Assert.Equal(5, obj.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); + Assert.Equal(5, obj2.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); + } + + [Fact] + public override void EnumAndNullable() + { + RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); + RunTest(new ClassWithEnumAndNullable()); + + void RunTest(ClassWithEnumAndNullable expected) + { + string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); + ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ClassWithEnumAndNullable); + Assert.Equal(expected.Day, actual.Day); + Assert.Equal(expected.NullableDay, actual.NullableDay); + } + } + } +} 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 09f26986c040d4..424051a7bd0cb4 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 @@ -9,8 +9,13 @@ + + + + + - + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs index d5ebf88645f2c6..fdc8d9d2988e4e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs @@ -2,22 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Text.Json.Serialization; -using System.Text.Json.SourceGeneration.Tests; - -[assembly: JsonSerializable(typeof(Location))] -[assembly: JsonSerializable(typeof(System.Text.Json.SourceGeneration.Tests.RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] -[assembly: JsonSerializable(typeof(ActiveOrUpcomingEvent))] -[assembly: JsonSerializable(typeof(CampaignSummaryViewModel))] -[assembly: JsonSerializable(typeof(IndexViewModel))] -[assembly: JsonSerializable(typeof(WeatherForecastWithPOCOs))] -[assembly: JsonSerializable(typeof(EmptyPoco))] -// Ensure no errors when type of member in previously specified object graph is passed as input type to generator. -[assembly: JsonSerializable(typeof(HighLowTemps))] -[assembly: JsonSerializable(typeof(MyType))] -[assembly: JsonSerializable(typeof(MyType2))] -[assembly: JsonSerializable(typeof(MyIntermediateType))] -[assembly: JsonSerializable(typeof(HighLowTempsImmutable))] namespace System.Text.Json.SourceGeneration.Tests.RepeatedTypes { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs index da4370619fcd4e..86a261f284e67a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Text.Encodings.Web; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Xunit; @@ -33,6 +34,7 @@ public static Compilation CreateCompilation( MetadataReference.CreateFromFile(typeof(Type).Assembly.Location), MetadataReference.CreateFromFile(typeof(KeyValuePair).Assembly.Location), MetadataReference.CreateFromFile(typeof(ContractNamespaceAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(JavaScriptEncoder).Assembly.Location), MetadataReference.CreateFromFile(systemRuntimeAssemblyPath), MetadataReference.CreateFromFile(systemCollectionsAssemblyPath), }; @@ -167,8 +169,14 @@ public static Compilation CreateRepeatedLocationsCompilation() using System.Collections.Generic; using System.Text.Json.Serialization; - [assembly: JsonSerializable(typeof(Fake.Location))] - [assembly: JsonSerializable(typeof(HelloWorld.Location))] + namespace JsonSourceGeneration + { + [JsonSerializable(typeof(Fake.Location))] + [JsonSerializable(typeof(HelloWorld.Location))] + internal partial class JsonContext : JsonSerializerContext + { + } + } namespace Fake { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs index 046c40127fe7ec..caa283f25f91a3 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs @@ -1,8 +1,6 @@ // 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.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Xunit; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs index f462ca0dd14efa..654b4131020236 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs @@ -17,10 +17,13 @@ public void TypeDiscoveryPrimitivePOCO() string source = @" using System.Text.Json.Serialization; - [assembly: JsonSerializable(typeof(HelloWorld.MyType))] - namespace HelloWorld { + [JsonSerializable(typeof(HelloWorld.MyType))] + internal partial class JsonContext : JsonSerializerContext + { + } + public class MyType { public int PublicPropertyInt { get; set; } @@ -83,11 +86,14 @@ public void TypeDiscoveryPrimitiveExternalPOCO() using System.Text.Json.Serialization; using ReferencedAssembly; - [assembly: JsonSerializable(typeof(HelloWorld.MyType))] - [assembly: JsonSerializable(typeof(ReferencedAssembly.Location))] - namespace HelloWorld { + [JsonSerializable(typeof(HelloWorld.MyType))] + [JsonSerializable(typeof(ReferencedAssembly.Location))] + internal partial class JsonContext : JsonSerializerContext + { + } + public class MyType { public int PublicPropertyInt { get; set; } @@ -165,15 +171,19 @@ public void TypeDiscoveryWithRenamedAttribute() using System.Text.Json.Serialization; using ReferencedAssembly; - using @JsonSerializable = System.Runtime.Serialization.ContractNamespaceAttribute; + using @JsonSerializable = System.Runtime.Serialization.CollectionDataContractAttribute ; using AliasedAttribute = System.Text.Json.Serialization.JsonSerializableAttribute; - [assembly: AliasedAttribute(typeof(HelloWorld.MyType))] - [assembly: AliasedAttribute(typeof(ReferencedAssembly.Location))] - [module: @JsonSerializable(""my namespace"")] - namespace HelloWorld { + + [AliasedAttribute(typeof(HelloWorld.MyType))] + [AliasedAttribute(typeof(ReferencedAssembly.Location))] + [@JsonSerializable] + internal partial class JsonContext : JsonSerializerContext + { + } + public class MyType { public int PublicPropertyInt { get; set; } @@ -245,12 +255,15 @@ public static void LocalJsonSerializableAttributeExpectedShape(string assemblyNa string source = @"using System; using System.Text.Json.Serialization; -[assembly: JsonSerializable(typeof(int))] -[assembly: JsonSerializable(typeof(string), TypeInfoPropertyName = ""Str"")] - namespace System.Text.Json.Serialization { - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(string), TypeInfoPropertyName = ""Str"")] + internal partial class JsonContext : JsonSerializerContext + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class JsonSerializableAttribute : JsonAttribute { public string TypeInfoPropertyName { get; set; } @@ -341,11 +354,14 @@ public void CollectionDictionarySourceGeneration() using System.Collections.Generic; using System.Text.Json.Serialization; using ReferencedAssembly; - - [assembly: JsonSerializable(typeof(HelloWorld.WeatherForecastWithPOCOs))] namespace HelloWorld { + [JsonSerializable(typeof(HelloWorld.WeatherForecastWithPOCOs))] + internal partial class JsonContext : JsonSerializerContext + { + } + public class WeatherForecastWithPOCOs { public DateTimeOffset Date { get; set; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs index e08ee5ecaa2e05..afcd88a5867e8d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs @@ -46,11 +46,14 @@ public class ReferencedType using System.Text.Json.Serialization; using ReferencedAssembly; - [assembly: JsonSerializable(typeof(HelloWorld.MyType))] - [assembly: JsonSerializable(typeof(ReferencedAssembly.ReferencedType))] - namespace HelloWorld { + [JsonSerializable(typeof(HelloWorld.MyType))] + [JsonSerializable(typeof(ReferencedAssembly.ReferencedType))] + internal partial class JsonContext : JsonSerializerContext + { + } + public class MyType { public void MyMethod() { } @@ -82,10 +85,13 @@ public void CanGetAttributes() using System; using System.Text.Json.Serialization; - [assembly: JsonSerializable(typeof(HelloWorld.MyType))] - namespace HelloWorld { + [JsonSerializable(typeof(HelloWorld.MyType))] + internal partial class JsonContext : JsonSerializerContext + { + } + public class MyType { [JsonInclude] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonTestHelper.cs index f7f2aa2917841a..338445a9f51669 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonTestHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonTestHelper.cs @@ -15,7 +15,7 @@ namespace System.Text.Json { - internal static class JsonTestHelper + internal static partial class JsonTestHelper { #if BUILDING_INBOX_LIBRARY public const string DoubleFormatString = null; @@ -818,64 +818,5 @@ public static string NormalizeLineEndings(this string value) => s_replaceNewlines ? value.Replace(CompiledNewline, Environment.NewLine) : value; - - public static void AssertJsonEqual(string expected, string actual) - { - using JsonDocument expectedDom = JsonDocument.Parse(expected); - using JsonDocument actualDom = JsonDocument.Parse(actual); - AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement); - } - - private static void AssertJsonEqual(JsonElement expected, JsonElement actual) - { - JsonValueKind valueKind = expected.ValueKind; - Assert.Equal(valueKind, actual.ValueKind); - - switch (valueKind) - { - case JsonValueKind.Object: - var propertyNames = new HashSet(); - - foreach (JsonProperty property in expected.EnumerateObject()) - { - propertyNames.Add(property.Name); - } - - foreach (JsonProperty property in actual.EnumerateObject()) - { - propertyNames.Add(property.Name); - } - - foreach (string name in propertyNames) - { - AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name)); - } - break; - case JsonValueKind.Array: - JsonElement.ArrayEnumerator expectedEnumerator = actual.EnumerateArray(); - JsonElement.ArrayEnumerator actualEnumerator = expected.EnumerateArray(); - - while (expectedEnumerator.MoveNext()) - { - Assert.True(actualEnumerator.MoveNext()); - AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current); - } - - Assert.False(actualEnumerator.MoveNext()); - break; - case JsonValueKind.String: - Assert.Equal(expected.GetString(), actual.GetString()); - break; - case JsonValueKind.Number: - case JsonValueKind.True: - case JsonValueKind.False: - case JsonValueKind.Null: - Assert.Equal(expected.GetRawText(), actual.GetRawText()); - break; - default: - Debug.Fail($"Unexpected JsonValueKind: JsonValueKind.{valueKind}."); - break; - } - } } } 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 62488ff19037ea..b4773ffc3f88dc 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 @@ -15,6 +15,7 @@ + From ccb37060263ba7d0c2dff22e9ae5e2f0d564f9c2 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 25 May 2021 12:11:35 -0700 Subject: [PATCH 2/8] Fix System.Net.Http.Json test issues --- .../tests/FunctionalTests/JsonContext/JsonContext.cs | 4 ++-- .../tests/FunctionalTests/JsonContext/Person.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/JsonContext.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/JsonContext.cs index 452644e2aa5722..56a4ff57c308b1 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/JsonContext.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/JsonContext.cs @@ -13,11 +13,11 @@ internal partial class JsonContext : JsonSerializerContext private static JsonContext s_default; public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions()); - public JsonContext() : base(null) + public JsonContext() : base(null, null) { } - public JsonContext(JsonSerializerOptions options) : base(options) + public JsonContext(JsonSerializerOptions options) : base(options, null) { } diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs index f383d17a7f04dc..193930c7a04d0d 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs @@ -31,7 +31,8 @@ public JsonTypeInfo Person Options, createObjectFunc: static () => new Person(), PersonPropInitFunc, - default); + default, + serializeFunc: null); } } From 430ac7c110733ff7dd566bcde45b6bfb21e7ff1a Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 25 May 2021 16:01:21 -0700 Subject: [PATCH 3/8] Fix System.Text.Json test issues --- .../FunctionalTests/JsonContext/Person.cs | 3 +- .../gen/JsonSourceGenerator.Emitter.cs | 3 +- .../System.Text.Json/ref/System.Text.Json.cs | 4 +- .../src/Resources/Strings.resx | 3 + .../Metadata/JsonMetadataServices.cs | 23 ++---- .../Serialization/Metadata/JsonTypeInfo.cs | 9 +-- .../Metadata/JsonTypeInfoInternalOfT.cs | 33 ++++----- .../Text/Json/ThrowHelper.Serialization.cs | 7 ++ .../MetadataTests/JsonContext/Dictionary.cs | 2 +- .../MetadataTests/JsonContext/HighLowTemps.cs | 6 +- .../MetadataTests/JsonContext/JsonContext.cs | 4 +- .../MetadataTests/JsonContext/List.cs | 2 +- .../MetadataTests/JsonContext/StringArray.cs | 2 +- .../JsonContext/WeatherForecastWithPOCOs.cs | 6 +- .../MetadataTests.JsonMetadataServices.cs | 72 +++++++++++-------- .../MetadataTests/MetadataTests.Options.cs | 6 +- 16 files changed, 93 insertions(+), 92 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs index 193930c7a04d0d..4f7f2dfb7d99ae 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs @@ -23,12 +23,11 @@ public JsonTypeInfo Person } else { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(); + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); _Person = objectInfo; JsonMetadataServices.InitializeObjectInfo( objectInfo, - Options, createObjectFunc: static () => new Person(), PersonPropInitFunc, default, diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 69ba1ddd3c4efc..1aae5180248262 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -453,7 +453,7 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) StringBuilder sb = new(); - sb.Append($@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>(); + sb.Append($@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>({OptionsInstanceVariableName}); _{typeFriendlyName} = objectInfo; "); @@ -488,7 +488,6 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) sb.Append($@" {JsonMetadataServicesTypeRef}.InitializeObjectInfo( objectInfo, - {OptionsInstanceVariableName}, {createObjectFuncTypeArg}, {propMetadataInitFuncNamedArg}, {GetNumberHandlingAsStr(typeMetadata.NumberHandling)}, 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 30c58ce17638d5..624fceab68fbc7 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -898,12 +898,12 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateArrayInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { 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() where T : notnull { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo(System.Text.Json.JsonSerializerOptions options) where T : notnull { throw null; } public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, 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, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } 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 static void InitializeObjectInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo info, System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Func? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where T : notnull { } + public static void InitializeObjectInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo info, System.Func? createObjectFunc, System.Func? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where T : notnull { } } public abstract partial class JsonPropertyInfo { diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index c68c367bd3af84..defbefd744123b 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -596,4 +596,7 @@ A custom converter for JsonObject is not allowed on an extension property. + + 'propInitFunc' and 'serializeFunc' cannot both be 'null'. + \ No newline at end of file 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 0688211059db59..b766db1676d270 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 @@ -80,24 +80,24 @@ public static JsonPropertyInfo CreatePropertyInfo( /// Creates metadata for a complex class or struct. /// /// The type of the class or struct. + /// Thrown when, is null. /// A instance representing the class or struct. - public static JsonTypeInfo CreateObjectInfo() where T : notnull => new JsonTypeInfoInternal(); + public static JsonTypeInfo CreateObjectInfo(JsonSerializerOptions options) where T : notnull + => new JsonTypeInfoInternal(options, ConverterStrategy.Object); /// /// Initializes metadata for a class or struct. /// /// The type of the class or struct /// - /// /// /// /// /// - /// Thrown when , , or is null. + /// Thrown when, , or is null. /// Thrown when , does not represent a complex class or struct type. public static void InitializeObjectInfo( JsonTypeInfo info, - JsonSerializerOptions options, Func? createObjectFunc, Func? propInitFunc, JsonNumberHandling numberHandling, @@ -109,18 +109,7 @@ public static void InitializeObjectInfo( throw new ArgumentNullException(nameof(info)); } - if (info.PropertyInfoForTypeInfo != null) - { - // ConverterStrategy.Object is the only info type we won't have set PropertyInfoForTypeInfo for at this point. - throw new ArgumentException(SR.InitializeTypeInfoAsObjectInvalid, nameof(info)); - } - - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - ((JsonTypeInfoInternal)info).InitializeAsObject(options, createObjectFunc, propInitFunc, numberHandling, serializeFunc); + ((JsonTypeInfoInternal)info).InitializeAsObject(createObjectFunc, propInitFunc, numberHandling, serializeFunc); Debug.Assert(info.PropertyInfoForTypeInfo!.ConverterStrategy == ConverterStrategy.Object); } @@ -131,7 +120,7 @@ public static void InitializeObjectInfo( /// A instance representing the type. public static JsonTypeInfo CreateValueInfo(JsonSerializerOptions options, JsonConverter converter) { - JsonTypeInfo info = new JsonTypeInfoInternal(options); + JsonTypeInfo info = new JsonTypeInfoInternal(options, ConverterStrategy.Value); info.PropertyInfoForTypeInfo = CreateJsonPropertyInfoForClassInfo(typeof(T), info, converter, options); return info; } 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 e61da1786a70fa..2de65bf152c7cd 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 @@ -149,15 +149,8 @@ internal JsonTypeInfo() internal JsonTypeInfo(Type type, JsonSerializerOptions options, ConverterStrategy converterStrategy) { - // Options setting for object class types is deferred till initialization. - if (converterStrategy != ConverterStrategy.Object && options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - Options = options!; Type = type; - + Options = options ?? throw new ArgumentNullException(nameof(options)); // Setting this option is deferred to the initialization methods of the various metadada info types. PropertyInfoForTypeInfo = null!; } 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 a32d92132fd6d3..be4d08762456a8 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 @@ -13,17 +13,10 @@ namespace System.Text.Json.Serialization.Metadata internal sealed class JsonTypeInfoInternal : JsonTypeInfo { /// - /// Creates serialization metadata for a . + /// Creates serialization metadata given JsonSerializerOptions and a ConverterStrategy. /// - public JsonTypeInfoInternal() : base(typeof(T), null!, ConverterStrategy.Object) - { - } - - /// - /// Creates serialization metadata for a . - /// - public JsonTypeInfoInternal(JsonSerializerOptions options) - : base (typeof(T), options, ConverterStrategy.Value) + public JsonTypeInfoInternal(JsonSerializerOptions options, ConverterStrategy converterStrategy) + : base(typeof(T), options, converterStrategy) { } @@ -90,13 +83,15 @@ public JsonTypeInfoInternal( /// Initializes serialization metadata for a . /// public void InitializeAsObject( - JsonSerializerOptions options, Func? createObjectFunc, Func? propInitFunc, JsonNumberHandling numberHandling, Action? serializeFunc) { - Options = options; + if (propInitFunc == null && serializeFunc == null) + { + ThrowHelper.ThrowInvalidOperationException_PropInitAndSerializeFuncsNull(); + } if (serializeFunc != null) { @@ -110,7 +105,7 @@ public void InitializeAsObject( JsonConverter converter = new SourceGenConverter(() => new ObjectDefaultConverter(), ConverterStrategy.Object, keyType: null, elementType: null); #pragma warning restore CS8714 - PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); + PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options); NumberHandling = numberHandling; PropInitFunc = propInitFunc; @@ -127,11 +122,17 @@ private void SetCreateObjectFunc(Func? createObjectFunc) private void DetermineIfCanUseSerializeFastPath() { - Debug.Assert(Options == Options._context!.Options); - JsonSerializerOptions? contextDefaultOptions = Options._context!.DefaultOptions; + JsonSerializerContext? context = Options._context; + if (context == null) + { + return; + } + + Debug.Assert(Options == context.Options); + JsonSerializerOptions? contextDefaultOptions = context.DefaultOptions; if (contextDefaultOptions == null) { - throw new InvalidOperationException($"To specify a fast-path implementation for type {typeof(T)}, context {Options._context.GetType()} must specify default options."); + throw new InvalidOperationException($"To specify a fast-path implementation for type {typeof(T)}, context {context.GetType()} must specify default options."); } UseFastPathOnWrite = 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 ab7cf89e8afb51..7274b94d23868f 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 @@ -699,5 +699,12 @@ public static void ThrowInvalidOperationException_NoMetadataForType(Type type) { throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type)); } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidOperationException_PropInitAndSerializeFuncsNull() + { + throw new InvalidOperationException(SR.Format(SR.PropInitAndSerializeFuncsNull)); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/Dictionary.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/Dictionary.cs index 781e0fa409d6be..b3e3bb3e863159 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/Dictionary.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/Dictionary.cs @@ -23,7 +23,7 @@ public JsonTypeInfo> Dictionary } else { - _Dictionary = JsonMetadataServices.CreateDictionaryInfo, string, HighLowTemps>(Options, () => new Dictionary(), this.String, this.HighLowTemps, default); + _Dictionary = JsonMetadataServices.CreateDictionaryInfo, string, HighLowTemps>(Options, () => new Dictionary(), this.String, this.HighLowTemps, default, serializeFunc: null); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs index c477b08bb2c380..c89c682d541090 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs @@ -22,15 +22,15 @@ public JsonTypeInfo HighLowTemps } else { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(); + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); _HighLowTemps = objectInfo; JsonMetadataServices.InitializeObjectInfo( objectInfo, - Options, createObjectFunc: static () => new HighLowTemps(), HighLowTempsPropInitFunc, - default); + default, + serializeFunc: null); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/JsonContext.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/JsonContext.cs index f3172684bb6fa0..c16add28cc24d9 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/JsonContext.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/JsonContext.cs @@ -11,11 +11,11 @@ internal partial class JsonContext : JsonSerializerContext private static JsonContext s_default; public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions()); - public JsonContext() : base(null) + public JsonContext() : base(null, null) { } - public JsonContext(JsonSerializerOptions options) : base(options) + public JsonContext(JsonSerializerOptions options) : base(options, null) { } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/List.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/List.cs index 24a2e95c3105a4..3a092554913613 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/List.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/List.cs @@ -23,7 +23,7 @@ public JsonTypeInfo> ListSystemDateTimeOffset } else { - _ListSystemDateTimeOffset = JsonMetadataServices.CreateListInfo, DateTimeOffset>(Options, () => new List(), this.DateTimeOffset, default); + _ListSystemDateTimeOffset = JsonMetadataServices.CreateListInfo, DateTimeOffset>(Options, () => new List(), this.DateTimeOffset, default, serializeFunc: null); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/StringArray.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/StringArray.cs index 21445e3dc50d47..6fab57d06309c8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/StringArray.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/StringArray.cs @@ -22,7 +22,7 @@ public JsonTypeInfo StringArray } else { - _StringArray = JsonMetadataServices.CreateArrayInfo(Options, this.String, default); + _StringArray = JsonMetadataServices.CreateArrayInfo(Options, this.String, default, serializeFunc: null); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs index 62d85930d74fb6..601b99aeb3683e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs @@ -22,15 +22,15 @@ public JsonTypeInfo WeatherForecastWithPOCOs } else { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(); + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); _WeatherForecastWithPOCOs = objectInfo; JsonMetadataServices.InitializeObjectInfo( objectInfo, - Options, createObjectFunc: static () => new WeatherForecastWithPOCOs(), WeatherForecastWithPOCOsPropInitFunc, - default); + default, + serializeFunc: null); } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs index 40feb1b20824e1..28269894c1d2e3 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs @@ -105,44 +105,47 @@ private class MyDerivedClass : MyClass { } public void CreateObjectInfo() { JsonSerializerOptions options = new(); + JsonTypeInfo info = JsonMetadataServices.CreateObjectInfo(options); - JsonTypeInfo info = JsonMetadataServices.CreateObjectInfo(); + // Null options + ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreateObjectInfo(null!)); + Assert.Contains("options", ane.ToString()); // Null info - ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( + ane = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( info: null, - options: options, createObjectFunc: null, propInitFunc: (context) => Array.Empty(), - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("info", ane.ToString()); - // Info is not for object converter strategy - ArgumentException ae = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( - info: JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter()), - options: options, + // Null prop init func is fine if serialize func is provided. + JsonMetadataServices.InitializeObjectInfo( + info, createObjectFunc: null, - propInitFunc: (context) => Array.Empty(), - numberHandling: default)); - Assert.Contains("info", ae.ToString()); + propInitFunc: null, + numberHandling: default, + serializeFunc: (writer, obj) => { }); - // Null options - ane = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( - info: info, - options: null, + // Null serialize func is fine if prop init func is provided. + JsonMetadataServices.InitializeObjectInfo( + info, createObjectFunc: null, propInitFunc: (context) => Array.Empty(), - numberHandling: default)); - Assert.Contains("options", ane.ToString()); + numberHandling: default, + serializeFunc: null); - // Null prop init func. - ane = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( - info: info, - options: options, + // Null prop init func and serialize func + InvalidOperationException ioe = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( + info, createObjectFunc: null, propInitFunc: null, - numberHandling: default)); - Assert.Contains("propInitFunc", ane.ToString()); + numberHandling: default, + serializeFunc: null)); + string ioeAsStr = ioe.ToString(); + Assert.Contains("propInitFunc", ioeAsStr); + Assert.Contains("serializeFunc", ioeAsStr); } [Fact] @@ -170,14 +173,16 @@ public void CreateArrayInfo() ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreateArrayInfo( options: null, elementInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("options", ane.ToString()); // Null element info ane = Assert.Throws(() => JsonMetadataServices.CreateArrayInfo( - options: options, + options, elementInfo: null, - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("elementInfo", ane.ToString()); } @@ -191,7 +196,8 @@ public void CreateListInfo() options: null, createObjectFunc: null, elementInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("options", ane.ToString()); // Null element info @@ -199,7 +205,8 @@ public void CreateListInfo() options: options, createObjectFunc: null, elementInfo: null, - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("elementInfo", ane.ToString()); } @@ -214,7 +221,8 @@ public void CreateDictionaryInfo() createObjectFunc: null, keyInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.StringConverter), valueInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("options", ane.ToString()); // Null key info @@ -223,7 +231,8 @@ public void CreateDictionaryInfo() createObjectFunc: null, keyInfo: null, valueInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("keyInfo", ane.ToString()); // Null value info @@ -232,7 +241,8 @@ public void CreateDictionaryInfo() createObjectFunc: null, keyInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.StringConverter), valueInfo: null, - numberHandling: default)); + numberHandling: default, + serializeFunc: null)); Assert.Contains("valueInfo", ane.ToString()); } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.Options.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.Options.cs index 285eb397fa3d9b..7ebb217f628eea 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.Options.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.Options.cs @@ -89,16 +89,16 @@ public void OptionsImmutableAfterBinding() private class MyJsonContext : JsonSerializerContext { - public MyJsonContext() : base(null) { } + public MyJsonContext() : base(null, null) { } - public MyJsonContext(JsonSerializerOptions options) : base(options) { } + public MyJsonContext(JsonSerializerOptions options) : base(options, null) { } public override JsonTypeInfo? GetTypeInfo(Type type) => throw new NotImplementedException(); } private class MyJsonContextThatSetsOptionsInParameterlessCtor : JsonSerializerContext { - public MyJsonContextThatSetsOptionsInParameterlessCtor() : base(new JsonSerializerOptions()) { } + public MyJsonContextThatSetsOptionsInParameterlessCtor() : base(new JsonSerializerOptions(), null) { } public override JsonTypeInfo? GetTypeInfo(Type type) => throw new NotImplementedException(); } } From 1dd5d734dd66d163b48ed406bc71fc308c63e2eb Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 25 May 2021 17:16:11 -0700 Subject: [PATCH 4/8] Make check to determine if fast-path can be used more efficient --- .../Converters/Value/SourceGenConverter.cs | 5 +- .../JsonSerializer.Write.Helpers.cs | 6 ++- .../Serialization/JsonSerializerContext.cs | 47 ++++++++++++++--- .../Metadata/JsonTypeInfoInternalOfT.cs | 52 ++----------------- .../Serialization/Metadata/JsonTypeInfoOfT.cs | 7 --- 5 files changed, 51 insertions(+), 66 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs index 35e8f6283f3af9..6b2d288f87c0b2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs @@ -68,7 +68,10 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializer { JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; - if (!state.SupportContinuation && jsonTypeInfo is JsonTypeInfo info && info.UseFastPathOnWrite) + if (!state.SupportContinuation && + jsonTypeInfo is JsonTypeInfo info && + info.Serialize != null && + info.Options._context?.CanUseSerializationLogic == true) { Debug.Assert(info.Serialize != null); info.Serialize(writer, value); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs index 16d00359e7ab4a..50c5eac6d63f87 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs @@ -41,9 +41,11 @@ private static bool WriteCore( private static void WriteUsingMetadata(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo) { - if (jsonTypeInfo is JsonTypeInfo typedInfo && typedInfo.UseFastPathOnWrite) + if (jsonTypeInfo is JsonTypeInfo typedInfo && + typedInfo.Serialize != null && + typedInfo.Options._context?.CanUseSerializationLogic == true) { - typedInfo.Serialize!(writer, value); + typedInfo.Serialize(writer, value); } else { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs index 009f38d108778a..c9ae786aadcb86 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs @@ -12,8 +12,46 @@ namespace System.Text.Json.Serialization [EditorBrowsable(EditorBrowsableState.Never)] public abstract partial class JsonSerializerContext { + private bool? _canUseSerializationLogic; + private JsonSerializerOptions? _defaultOptions; + internal JsonSerializerOptions? _options; + /// + /// Indicates whether pre-generated serialization logic for types in the context + /// is compatible with the run-time specified . + /// + internal bool CanUseSerializationLogic + { + get + { + if (!_canUseSerializationLogic.HasValue) + { + if (_defaultOptions == null) + { + _canUseSerializationLogic = false; + } + else + { + _canUseSerializationLogic = + // Guard against unsupported features + Options.Encoder == null && + Options.NumberHandling == JsonNumberHandling.Strict && + Options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.None && + // Ensure options values are consistent with expected defaults. + Options.DefaultIgnoreCondition == _defaultOptions.DefaultIgnoreCondition && + Options.IgnoreReadOnlyFields == _defaultOptions.IgnoreReadOnlyFields && + Options.IgnoreReadOnlyProperties == _defaultOptions.IgnoreReadOnlyProperties && + Options.IncludeFields == _defaultOptions.IncludeFields && + Options.PropertyNamingPolicy == _defaultOptions.PropertyNamingPolicy && + Options.WriteIndented == _defaultOptions.WriteIndented; + } + } + + return _canUseSerializationLogic.Value; + } + } + /// /// Gets the run-time specified options of the context. If no options were passed /// when instanciating the context, then a new instance is bound and returned. @@ -35,11 +73,6 @@ public JsonSerializerOptions Options } } - /// - /// The default run-time options for the context. It's values are defined at design-time via . - /// - internal JsonSerializerOptions? DefaultOptions { get; } - /// /// Creates an instance of and binds it with the indicated . /// @@ -51,8 +84,6 @@ public JsonSerializerOptions Options /// protected JsonSerializerContext(JsonSerializerOptions? instanceOptions, JsonSerializerOptions? defaultOptions) { - DefaultOptions = defaultOptions; - if (instanceOptions != null) { if (instanceOptions._context != null) @@ -63,6 +94,8 @@ protected JsonSerializerContext(JsonSerializerOptions? instanceOptions, JsonSeri _options = instanceOptions; instanceOptions._context = this; } + + _defaultOptions = defaultOptions; } /// 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 be4d08762456a8..a083a36b48e407 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,7 +1,6 @@ // 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.Text.Json.Serialization.Converters; namespace System.Text.Json.Serialization.Metadata @@ -32,18 +31,13 @@ public JsonTypeInfoInternal( Action? serializeFunc, Type elementType) : base(typeof(T), options, ConverterStrategy.Enumerable) { - if (serializeFunc != null) - { - Serialize = serializeFunc; - DetermineIfCanUseSerializeFastPath(); - } - JsonConverter converter = new SourceGenConverter(converterCreator, ConverterStrategy.Enumerable, keyType: null, elementType); ElementType = converter.ElementType; ElementTypeInfo = elementInfo ?? throw new ArgumentNullException(nameof(elementInfo)); NumberHandling = numberHandling; PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); + Serialize = serializeFunc; SetCreateObjectFunc(createObjectFunc); } @@ -61,12 +55,6 @@ public JsonTypeInfoInternal( Type keyType, Type elementType) : base(typeof(T), options, ConverterStrategy.Dictionary) { - if (serializeFunc != null) - { - Serialize = serializeFunc; - DetermineIfCanUseSerializeFastPath(); - } - JsonConverter converter = new SourceGenConverter(converterCreator, ConverterStrategy.Dictionary, keyType, elementType); KeyType = converter.KeyType; @@ -76,6 +64,7 @@ public JsonTypeInfoInternal( ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo)); ; NumberHandling = numberHandling; PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); + Serialize = serializeFunc; SetCreateObjectFunc(createObjectFunc); } @@ -93,12 +82,6 @@ public void InitializeAsObject( ThrowHelper.ThrowInvalidOperationException_PropInitAndSerializeFuncsNull(); } - if (serializeFunc != null) - { - Serialize = serializeFunc; - DetermineIfCanUseSerializeFastPath(); - } - #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. @@ -108,7 +91,7 @@ public void InitializeAsObject( PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options); NumberHandling = numberHandling; PropInitFunc = propInitFunc; - + Serialize = serializeFunc; SetCreateObjectFunc(createObjectFunc); } @@ -119,34 +102,5 @@ private void SetCreateObjectFunc(Func? createObjectFunc) CreateObject = () => createObjectFunc(); } } - - private void DetermineIfCanUseSerializeFastPath() - { - JsonSerializerContext? context = Options._context; - if (context == null) - { - return; - } - - Debug.Assert(Options == context.Options); - JsonSerializerOptions? contextDefaultOptions = context.DefaultOptions; - if (contextDefaultOptions == null) - { - throw new InvalidOperationException($"To specify a fast-path implementation for type {typeof(T)}, context {context.GetType()} must specify default options."); - } - - UseFastPathOnWrite = - // Guard against unsupported features - Options.Encoder == null && - Options.NumberHandling == JsonNumberHandling.Strict && - Options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.None && - // Ensure options values are consistent with expected defaults. - Options.DefaultIgnoreCondition == contextDefaultOptions.DefaultIgnoreCondition && - Options.IgnoreReadOnlyFields == contextDefaultOptions.IgnoreReadOnlyFields && - Options.IgnoreReadOnlyProperties == contextDefaultOptions.IgnoreReadOnlyProperties && - Options.IncludeFields == contextDefaultOptions.IncludeFields && - Options.PropertyNamingPolicy == contextDefaultOptions.PropertyNamingPolicy && - Options.WriteIndented == contextDefaultOptions.WriteIndented; - } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 272a59436ae282..762555889e2800 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -27,12 +27,5 @@ internal JsonTypeInfo() /// values specified at design time. /// public Action? Serialize { get; internal set; } - - /// - /// When serializing objects with source-gen converters, indicates whether the - /// configured is compatible with the generated - /// func. - /// - internal bool UseFastPathOnWrite { get; set; } } } From 07d2439dd251006d2a3acb0e940b46076ba895cc Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 25 May 2021 16:33:30 -0700 Subject: [PATCH 5/8] Address review feedback --- .../Common/JsonKnownNamingPolicy.cs | 2 +- .../gen/JsonSourceGenerator.Emitter.cs | 66 +++++++------- .../gen/JsonSourceGenerator.Parser.cs | 9 +- .../gen/PropertyGenerationSpec.cs | 2 +- .../gen/Reflection/TypeExtensions.cs | 26 ++++-- .../gen/SourceGenerationSpec.cs | 12 ++- .../gen/TypeGenerationSpec.cs | 24 +++-- .../src/Resources/Strings.resx | 6 ++ .../src/System.Text.Json.csproj | 2 +- .../Attributes/JsonSerializableAttribute.cs | 12 +-- .../Serialization/JsonSerializerContext.cs | 1 + .../Serialization/JsonStringEnumConverter.cs | 2 +- .../JsonMetadataServices.Converters.cs | 6 +- .../Metadata/JsonMetadataServices.cs | 24 +++-- .../JsonMetadataServicesConverter.cs} | 5 +- .../Metadata/JsonTypeInfo.Cache.cs | 9 +- .../Metadata/JsonTypeInfoInternalOfT.cs | 10 +-- .../Text/Json/ThrowHelper.Serialization.cs | 10 +++ .../ContextClasses.cs | 89 +++++++++++++++++++ .../ContextTests.cs | 36 +------- .../JsonTestHelper.cs | 17 ++++ .../MixedModeContextTests.cs | 10 +-- .../SerializationContextTests.cs | 30 +++++-- .../SerializationLogicTests.cs | 61 +++++++++++++ ...em.Text.Json.SourceGeneration.Tests.csproj | 3 + .../TestClasses.cs | 6 ++ 26 files changed, 347 insertions(+), 133 deletions(-) rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/{Converters/Value/SourceGenConverter.cs => Metadata/JsonMetadataServicesConverter.cs} (93%) create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonTestHelper.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs index ec4325968247be..99290715f31363 100644 --- a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs @@ -19,7 +19,7 @@ enum JsonKnownNamingPolicy Unspecified = 0, /// - /// Speciies that the built-in be used to convert JSON property names. + /// Specifies that the built-in be used to convert JSON property names. /// BuiltInCamelCase = 1 } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 1aae5180248262..61e483f26f0f6c 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -30,7 +30,6 @@ private sealed partial class Emitter [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{_assemblyName.Name}"", ""{_assemblyName.Version}"")]"; // global::fully.qualified.name for referenced types - private const string ActionTypeRef = "global::System.Action"; private const string ArrayTypeRef = "global::System.Array"; private const string InvalidOperationExceptionTypeRef = "global::System.InvalidOperationException"; private const string TypeTypeRef = "global::System.Type"; @@ -75,7 +74,7 @@ private sealed partial class Emitter private readonly GeneratorExecutionContext _executionContext; - private ContextGenerationSpec _currentContext = new(); + private ContextGenerationSpec _currentContext = null!; private readonly SourceGenerationSpec _generationSpec = null!; @@ -235,7 +234,7 @@ private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetada {TypeTypeRef} typeToConvert = typeof({typeCompilableName}); if (!converter.CanConvert(typeToConvert)) {{ - Type underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert); + {TypeTypeRef} underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert); if (underlyingType != null && converter.CanConvert(underlyingType)) {{ {JsonConverterTypeRef} actualConverter = converter; @@ -379,30 +378,30 @@ private string GenerateForCollection(TypeGenerationSpec typeGenerationSpec) private string GenerateFastPathFuncForEnumerable(string typeInfoRef, string serializeMethodName, bool canBeNull, bool isArray, TypeGenerationSpec valueTypeGenerationSpec) { - string? writerMethodToCall = GetWriterSerializationMethod(valueTypeGenerationSpec.Type); + string? writerMethodToCall = GetWriterMethod(valueTypeGenerationSpec.Type); string valueToWrite = $"{ValueVarName}[i]"; string lengthPropName = isArray ? "Length" : "Count"; - string serializationLogic; + string elementSerializationLogic; if (writerMethodToCall != null) { - serializationLogic = $"{writerMethodToCall}Value({valueToWrite});"; + elementSerializationLogic = $"{writerMethodToCall}Value({valueToWrite});"; } else { - serializationLogic = GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic); + elementSerializationLogic = GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic); } - string fastPathLogic = $@"{WriterVarName}.WriteStartArray(); + string serializationLogic = $@"{WriterVarName}.WriteStartArray(); for (int i = 0; i < {ValueVarName}.{lengthPropName}; i++) {{ - {serializationLogic} + {elementSerializationLogic} }} {WriterVarName}.WriteEndArray();"; - return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, fastPathLogic, canBeNull); + return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, canBeNull); } private string GenerateFastPathFuncForDictionary( @@ -412,32 +411,33 @@ private string GenerateFastPathFuncForDictionary( TypeGenerationSpec keyTypeGenerationSpec, TypeGenerationSpec valueTypeGenerationSpec) { - string? writerMethodToCall = GetWriterSerializationMethod(valueTypeGenerationSpec.Type); const string pairVarName = "pair"; string keyToWrite = $"{pairVarName}.Key"; string valueToWrite = $"{pairVarName}.Value"; - string serializationLogic; + string? writerMethodToCall = GetWriterMethod(valueTypeGenerationSpec.Type); + string elementSerializationLogic; + if (writerMethodToCall != null) { - serializationLogic = $"{writerMethodToCall}({keyToWrite}, {valueToWrite})"; + elementSerializationLogic = $"{writerMethodToCall}({keyToWrite}, {valueToWrite});"; } else { - serializationLogic = $@"{WriterVarName}.WritePropertyName({keyToWrite}); + elementSerializationLogic = $@"{WriterVarName}.WritePropertyName({keyToWrite}); {GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic)}"; } - string fastPathLogic = $@"{WriterVarName}.WriteStartObject(); + string serializationLogic = $@"{WriterVarName}.WriteStartObject(); - foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName}) - {{ - {serializationLogic} - }} + foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName}) + {{ + {elementSerializationLogic} + }} - {WriterVarName}.WriteEndObject();"; + {WriterVarName}.WriteEndObject();"; - return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, fastPathLogic, canBeNull); + return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, canBeNull); } private string GenerateForObject(TypeGenerationSpec typeMetadata) @@ -615,8 +615,6 @@ private string GenerateFastPathFuncForObject( bool canBeNull, List? properties) { - string methodSignature = $"private static void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {typeInfoTypeRef} {ValueVarName})"; - JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions; // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation. @@ -666,7 +664,7 @@ private string GenerateFastPathFuncForObject( string propValue = $"{ValueVarName}.{propertySpec.ClrName}"; string methodArgs = $"{propName}, {propValue}"; - string? methodToCall = GetWriterSerializationMethod(propertyType); + string? methodToCall = GetWriterMethod(propertyType); if (propertyType == _generationSpec.CharType) { @@ -716,35 +714,35 @@ private string GenerateFastPathFuncForObject( return GenerateFastPathFuncForType(serializeMethodName, typeInfoTypeRef, sb.ToString(), canBeNull); } - private string? GetWriterSerializationMethod(Type type) + private string? GetWriterMethod(Type type) { - string? methodToCall; + string? method; if (_generationSpec.IsStringBasedType(type)) { - methodToCall = $"{WriterVarName}.WriteString"; + method = $"{WriterVarName}.WriteString"; } else if (type == _generationSpec.BooleanType) { - methodToCall = $"{WriterVarName}.WriteBoolean"; + method = $"{WriterVarName}.WriteBoolean"; } else if (type == _generationSpec.ByteArrayType) { - methodToCall = $"{WriterVarName}.WriteBase64String"; + method = $"{WriterVarName}.WriteBase64String"; } else if (type == _generationSpec.CharType) { - methodToCall = $"{WriterVarName}.WriteString"; + method = $"{WriterVarName}.WriteString"; } else if (_generationSpec.IsNumberType(type)) { - methodToCall = $"{WriterVarName}.WriteNumber"; + method = $"{WriterVarName}.WriteNumber"; } else { - methodToCall = null; + method = null; } - return methodToCall; + return method; } private string GenerateFastPathFuncForType(string serializeMethodName, string typeInfoTypeRef, string serializationLogic, bool canBeNull) @@ -1037,7 +1035,7 @@ private static string IndentSource(string source, int numIndentations) private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) => numberHandling.HasValue - ? $"(JsonNumberHandling){(int)numberHandling.Value}" + ? $"({JsonNumberHandlingTypeRef}){(int)numberHandling.Value}" : "default"; private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>"; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 356ee9b0213baf..5ff89bd4badb06 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -46,6 +46,7 @@ private sealed class Parser private readonly Type _dateTimeType; private readonly Type _dateTimeOffsetType; private readonly Type _guidType; + private readonly Type _nullableOfTType; private readonly Type _stringType; private readonly Type _uriType; private readonly Type _versionType; @@ -64,9 +65,6 @@ public Parser(Compilation compilation) _compilation = compilation; _metadataLoadContext = new MetadataLoadContextInternal(compilation); - TypeExtensions.NullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>)); - TypeExtensions.ObjectArrayType = _metadataLoadContext.Resolve(typeof(object[])); - _ienumerableType = _metadataLoadContext.Resolve(typeof(IEnumerable)); _listOfTType = _metadataLoadContext.Resolve(typeof(List<>)); _dictionaryType = _metadataLoadContext.Resolve(typeof(Dictionary<,>)); @@ -77,6 +75,7 @@ public Parser(Compilation compilation) _dateTimeType = _metadataLoadContext.Resolve(typeof(DateTime)); _dateTimeOffsetType = _metadataLoadContext.Resolve(typeof(DateTimeOffset)); _guidType = _metadataLoadContext.Resolve(typeof(Guid)); + _nullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>)); _stringType = _metadataLoadContext.Resolve(typeof(string)); _uriType = _metadataLoadContext.Resolve(typeof(Uri)); _versionType = _metadataLoadContext.Resolve(typeof(Version)); @@ -395,7 +394,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type) { classType = ClassType.KnownType; } - else if (type.IsNullableValueType(out nullableUnderlyingType)) + else if (type.IsNullableValueType(_nullableOfTType, out nullableUnderlyingType)) { Debug.Assert(nullableUnderlyingType != null); classType = ClassType.Nullable; @@ -469,8 +468,6 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type) continue; } - string key = metadata.JsonPropertyName ?? metadata.ClrName; - if (metadata.CanUseGetter || metadata.CanUseSetter) { (propertiesMetadata ??= new()).Add(metadata); diff --git a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs index 5a9db6b60318fa..25ae73692ecb07 100644 --- a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.SourceGeneration { [DebuggerDisplay("Name={Name}, Type={TypeMetadata}")] - internal class PropertyGenerationSpec + internal sealed class PropertyGenerationSpec { /// /// The CLR name of the property. diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs index 365b48e2fdada1..8ff487ab0c6ee8 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs @@ -38,18 +38,26 @@ private static string GetFriendlyTypeName(string compilableName) return compilableName.Replace(".", "").Replace("<", "").Replace(">", "").Replace(",", "").Replace("[]", "Array"); } - public static Type NullableOfTType { get; set; } - - public static Type ObjectArrayType { get; set; } - - public static bool IsNullableValueType(this Type type, out Type? underlyingType) + public static bool IsNullableValueType(this Type type, Type nullableOfTType, out Type? underlyingType) { - Debug.Assert(NullableOfTType != null); + Debug.Assert(nullableOfTType != null); // TODO: log bug because Nullable.GetUnderlyingType doesn't work due to // https://github.com/dotnet/runtimelab/blob/7472c863db6ec5ddab7f411ddb134a6e9f3c105f/src/libraries/System.Private.CoreLib/src/System/Nullable.cs#L124 // i.e. type.GetGenericTypeDefinition() will never equal typeof(Nullable<>), as expected in that code segment. - if (type.IsGenericType && type.GetGenericTypeDefinition() == NullableOfTType) + if (type.IsGenericType && type.GetGenericTypeDefinition() == nullableOfTType) + { + underlyingType = type.GetGenericArguments()[0]; + return true; + } + + underlyingType = null; + return false; + } + + public static bool IsNullableValueType(this Type type, out Type? underlyingType) + { + if (type.IsGenericType && type.Name.StartsWith("Nullable`1")) { underlyingType = type.GetGenericArguments()[0]; return true; @@ -59,6 +67,10 @@ public static bool IsNullableValueType(this Type type, out Type? underlyingType) return false; } + public static bool IsObjectType(this Type type) => type.FullName == "System.Object"; + + public static bool IsStringType(this Type type) => type.FullName == "System.String"; + public static Type? GetCompatibleBaseClass(this Type type, string baseTypeFullName) { Type? baseTypeToCheck = type; diff --git a/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs b/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs index f8bf0b19ca57ad..0019dc524955aa 100644 --- a/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs @@ -7,18 +7,17 @@ namespace System.Text.Json.SourceGeneration { - internal class SourceGenerationSpec + internal sealed class SourceGenerationSpec { public List ContextGenerationSpecList { get; init; } - #region Known types public Type BooleanType { get; init; } public Type ByteArrayType { get; init; } public Type CharType { get; init; } - public Type DateTimeType { get; init; } - public Type DateTimeOffsetType { get; init; } - public Type GuidType { get; init; } - public Type StringType { get; init; } + public Type DateTimeType { private get; init; } + public Type DateTimeOffsetType { private get; init; } + public Type GuidType { private get; init; } + public Type StringType { private get; init; } public HashSet NumberTypes { private get; init; } @@ -26,6 +25,5 @@ public bool IsStringBasedType(Type type) => type == StringType || type == DateTimeType || type == DateTimeOffsetType || type == GuidType; public bool IsNumberType(Type type) => NumberTypes.Contains(type); - #endregion } } diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index 6133e595cbb762..b957bb25bbc8d3 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -14,7 +14,7 @@ internal class TypeGenerationSpec private bool _hasBeenInitialized; /// - /// Fully qualified assembly name, prefaced with "global::", e.g. global::System.Numerics.BigInteger. + /// Fully qualified assembly name, prefixed with "global::", e.g. global::System.Numerics.BigInteger. /// public string TypeRef { get; private set; } @@ -97,9 +97,23 @@ public void Initialize( } public bool FastPathIsSupported() - => Type != TypeExtensions.ObjectArrayType && (ClassType == ClassType.Object || - CollectionType == CollectionType.Dictionary || - CollectionType == CollectionType.Array || - CollectionType == CollectionType.List); + { + if (ClassType == ClassType.Object) + { + return true; + } + + if (CollectionType == CollectionType.Array || CollectionType == CollectionType.List) + { + return !CollectionValueTypeMetadata!.Type.IsObjectType(); + } + + if (CollectionType == CollectionType.Dictionary) + { + return CollectionKeyTypeMetadata!.Type.IsStringType() && !CollectionValueTypeMetadata!.Type.IsObjectType(); + } + + return false; + } } } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index defbefd744123b..9f73cb0ec2956f 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -599,4 +599,10 @@ 'propInitFunc' and 'serializeFunc' cannot both be 'null'. + + 'JsonSerializerContext' '{0}' did not provide property metadata for type '{1}'. + + + To specify a serialization implementation for type '{0}'', context '{0}' must specify default options. + \ 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 d9e132bd02a92b..2b3bdafcf171a5 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -92,7 +92,7 @@ - + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs index d11ce9e8dba700..b223fada10cb9d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs @@ -12,6 +12,12 @@ namespace System.Text.Json.Serialization [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class JsonSerializableAttribute : JsonAttribute { + /// + /// Initializes a new instance of with the specified type. + /// + /// The type to generate source code for. + public JsonSerializableAttribute(Type type) { } + /// /// The name of the property for the generated for /// the type on the generated, derived type. @@ -25,11 +31,5 @@ public sealed class JsonSerializableAttribute : JsonAttribute /// Determines what the source generator should generate for the type. /// public JsonSourceGenerationMode GenerationMode { get; set; } - - /// - /// Initializes a new instance of with the specified type. - /// - /// The type to generate source code for. - public JsonSerializableAttribute(Type type) { } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs index c9ae786aadcb86..78843cfcda7823 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs @@ -35,6 +35,7 @@ internal bool CanUseSerializationLogic { _canUseSerializationLogic = // Guard against unsupported features + Options.Converters.Count == 0 && Options.Encoder == null && Options.NumberHandling == JsonNumberHandling.Strict && Options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.None && diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs index 83c2819450c1c9..ac6fe18b9dbda9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs @@ -9,7 +9,7 @@ namespace System.Text.Json.Serialization /// Converter to convert enums to and from strings. /// /// - /// Reading is case insensitive, writing can be customized via a . + /// Reading is case insensitive, writing can be customized via a . /// public sealed class JsonStringEnumConverter : JsonConverterFactory { 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 681a201e1e3f5c..0be5273425cdb6 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 @@ -147,7 +147,7 @@ public static partial class JsonMetadataServices /// Creates a instance that converts values. /// /// The generic definition for the enum type. - /// + /// The to use for serialization and deserialization. /// public static JsonConverter GetEnumConverter(JsonSerializerOptions options) where T : struct, Enum => new EnumConverter(EnumConverterOptions.AllowNumbers, options ?? throw new ArgumentNullException(nameof(options))); @@ -155,8 +155,8 @@ public static JsonConverter GetEnumConverter(JsonSerializerOptions options /// /// Creates a instance that converts values. /// - /// - /// + /// The generic definition for the underlying nullable type. + /// Serialization metadata for the underlying nullable type. /// public static JsonConverter GetNullableConverter(JsonTypeInfo underlyingTypeInfo) where T : struct { 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 b766db1676d270..779915ec18877b 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 @@ -16,6 +16,17 @@ public static partial class JsonMetadataServices /// Creates metadata for a property or field. /// /// The type that the converter for the property returns or accepts when converting JSON data. + /// The to initialize the metadata with. + /// Whether the CLR member is a property or field. + /// The declaring type of the property or field. + /// The info for the property or field's type. + /// A for the property or field, specified by . + /// Provides a mechanism to get the property or field's value. + /// Provides a mechanism to set the property or field's value. + /// Specifies a condition for the property to be ignored. + /// If the property or field is a number, specifies how it should processed when serializing and deserializing. + /// The CLR name of the property or field. + /// The name to be used when processing the property or field, specified by . /// A instance intialized with the provided metadata. public static JsonPropertyInfo CreatePropertyInfo( JsonSerializerOptions options, @@ -79,6 +90,7 @@ public static JsonPropertyInfo CreatePropertyInfo( /// /// Creates metadata for a complex class or struct. /// + /// The to initialize the metadata with. /// The type of the class or struct. /// Thrown when, is null. /// A instance representing the class or struct. @@ -86,14 +98,14 @@ public static JsonTypeInfo CreateObjectInfo(JsonSerializerOptions options) => new JsonTypeInfoInternal(options, ConverterStrategy.Object); /// - /// Initializes metadata for a class or struct. + /// Initializes metadata for a complex class or struct. /// /// The type of the class or struct - /// - /// - /// - /// - /// + /// The metadata instance to augment with serialization related information. + /// 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. /// Thrown when, , or is null. /// Thrown when , does not represent a complex class or struct type. public static void InitializeObjectInfo( diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs similarity index 93% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs index 6b2d288f87c0b2..8fca70ffd985a2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SourceGenConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs @@ -13,7 +13,7 @@ namespace System.Text.Json.Serialization.Converters /// , to provide a fallback when the fast path cannot be used. /// /// The type to converter - internal class SourceGenConverter : JsonResumableConverter + internal sealed class JsonMetadataServicesConverter : JsonResumableConverter { private readonly Func> _converterCreator; @@ -44,7 +44,7 @@ private JsonConverter Converter internal override Type? ElementType => _elementType; - public SourceGenConverter(Func> converterCreator, ConverterStrategy converterStrategy, Type? keyType, Type? elementType) + public JsonMetadataServicesConverter(Func> converterCreator, ConverterStrategy converterStrategy, Type? keyType, Type? elementType) { _converterCreator = converterCreator ?? throw new ArgumentNullException(nameof(converterCreator)); _converterStrategy = converterStrategy; @@ -85,6 +85,5 @@ jsonTypeInfo is JsonTypeInfo info && return Converter.OnTryWrite(writer, value, options, ref state); } - } } 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 08c7fa53e6613f..f6cbe62a84fbb4 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 @@ -568,15 +568,18 @@ internal void UpdateSortedParameterCache(ref ReadStackFrame frame) internal void InitializeSerializePropCache() { - Debug.Assert(Options._context != null); + JsonSerializerContext? context = Options._context; + + Debug.Assert(context != null); Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object); if (PropInitFunc == null) { - throw new NotSupportedException($"Property metadata was not provided for type {Type}."); + ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(context, Type); + return; } - PropertyCacheArray = PropInitFunc(Options._context); + PropertyCacheArray = PropInitFunc(context); } internal void InitializeDeserializePropCache() 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 a083a36b48e407..b08371976801e9 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 @@ -31,7 +31,7 @@ public JsonTypeInfoInternal( Action? serializeFunc, Type elementType) : base(typeof(T), options, ConverterStrategy.Enumerable) { - JsonConverter converter = new SourceGenConverter(converterCreator, ConverterStrategy.Enumerable, keyType: null, elementType); + JsonConverter converter = new JsonMetadataServicesConverter(converterCreator, ConverterStrategy.Enumerable, keyType: null, elementType); ElementType = converter.ElementType; ElementTypeInfo = elementInfo ?? throw new ArgumentNullException(nameof(elementInfo)); @@ -55,13 +55,13 @@ public JsonTypeInfoInternal( Type keyType, Type elementType) : base(typeof(T), options, ConverterStrategy.Dictionary) { - JsonConverter converter = new SourceGenConverter(converterCreator, ConverterStrategy.Dictionary, keyType, elementType); + JsonConverter converter = new JsonMetadataServicesConverter(converterCreator, ConverterStrategy.Dictionary, keyType, elementType); KeyType = converter.KeyType; ElementType = converter.ElementType; - KeyTypeInfo = keyInfo ?? throw new ArgumentNullException(nameof(keyInfo)); ; + KeyTypeInfo = keyInfo ?? throw new ArgumentNullException(nameof(keyInfo)); ElementType = converter.ElementType; - ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo)); ; + ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo)); NumberHandling = numberHandling; PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); Serialize = serializeFunc; @@ -85,7 +85,7 @@ public void InitializeAsObject( #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 SourceGenConverter(() => new ObjectDefaultConverter(), ConverterStrategy.Object, keyType: null, elementType: null); + JsonConverter converter = new JsonMetadataServicesConverter(() => new ObjectDefaultConverter(), ConverterStrategy.Object, keyType: null, elementType: null); #pragma warning restore CS8714 PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options); 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 7274b94d23868f..7beb0612a4e18e 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 @@ -706,5 +706,15 @@ public static void ThrowInvalidOperationException_PropInitAndSerializeFuncsNull( { throw new InvalidOperationException(SR.Format(SR.PropInitAndSerializeFuncsNull)); } + + public static void ThrowInvalidOperationException_NoMetadataForTypeProperties(JsonSerializerContext context, Type type) + { + throw new InvalidOperationException(SR.Format(SR.NoMetadataForTypeProperties, 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/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs new file mode 100644 index 00000000000000..d3d27853d9a840 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs @@ -0,0 +1,89 @@ +// 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; +using System.Text.Json.Serialization.Metadata; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public interface ITestContext + { + public JsonTypeInfo Location { get; } + public JsonTypeInfo RepeatedLocation { get; } + public JsonTypeInfo ActiveOrUpcomingEvent { get; } + public JsonTypeInfo CampaignSummaryViewModel { get; } + public JsonTypeInfo IndexViewModel { get; } + public JsonTypeInfo WeatherForecastWithPOCOs { get; } + public JsonTypeInfo EmptyPoco { get; } + public JsonTypeInfo HighLowTemps { get; } + public JsonTypeInfo MyType { get; } + public JsonTypeInfo MyType2 { get; } + public JsonTypeInfo MyIntermediateType { get; } + public JsonTypeInfo HighLowTempsImmutable { get; } + public JsonTypeInfo MyNestedClass { get; } + public JsonTypeInfo MyNestedNestedClass { get; } + public JsonTypeInfo ObjectArray { get; } + public JsonTypeInfo String { get; } + public JsonTypeInfo ClassWithEnumAndNullable { get; } + } + + internal partial class JsonContext : JsonSerializerContext + { + private static JsonSerializerOptions s_defaultOptions { get; } = new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + private static JsonContext s_defaultContext; + public static JsonContext Default => s_defaultContext ??= new JsonContext(new JsonSerializerOptions(s_defaultOptions)); + + public JsonContext() : base(null, s_defaultOptions) + { + } + + public JsonContext(JsonSerializerOptions options) : base(options, s_defaultOptions) + { + } + + public override JsonTypeInfo GetTypeInfo(global::System.Type type) + { + if (type == typeof(JsonMessage)) + { + return JsonMessage; + } + + return null!; + } + + private JsonTypeInfo _JsonMessage; + public JsonTypeInfo JsonMessage + { + get + { + if (_JsonMessage == null) + { + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); + _JsonMessage = objectInfo; + + JsonMetadataServices.InitializeObjectInfo( + objectInfo, + createObjectFunc: static () => new JsonMessage(), + propInitFunc: null, + default, + serializeFunc: JsonMessageSerialize); + } + + return _JsonMessage; + } + } + + private static void JsonMessageSerialize(Utf8JsonWriter writer, JsonMessage value) => throw new NotImplementedException(); + } + + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + internal partial class DictionaryTypeContext : JsonSerializerContext { } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs index e7859c499dc1d2..25fbf75fc52414 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs @@ -11,27 +11,6 @@ namespace System.Text.Json.SourceGeneration.Tests { - public interface ITestContext - { - public JsonTypeInfo Location { get; } - public JsonTypeInfo RepeatedLocation { get; } - public JsonTypeInfo ActiveOrUpcomingEvent { get; } - public JsonTypeInfo CampaignSummaryViewModel { get; } - public JsonTypeInfo IndexViewModel { get; } - public JsonTypeInfo WeatherForecastWithPOCOs { get; } - public JsonTypeInfo EmptyPoco { get; } - public JsonTypeInfo HighLowTemps { get; } - public JsonTypeInfo MyType { get; } - public JsonTypeInfo MyType2 { get; } - public JsonTypeInfo MyIntermediateType { get; } - public JsonTypeInfo HighLowTempsImmutable { get; } - public JsonTypeInfo MyNestedClass { get; } - public JsonTypeInfo MyNestedNestedClass { get; } - public JsonTypeInfo ObjectArray { get; } - public JsonTypeInfo String { get; } - public JsonTypeInfo ClassWithEnumAndNullable { get; } - } - public abstract class ContextTests { protected ITestContext DefaultContext { get; } @@ -463,7 +442,7 @@ public void JsonContextOptionsNotMutableAfterConstruction() } [Fact] - public void ParameterizedConstructor() + public virtual void ParameterizedConstructor() { string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); Assert.Contains(@"""High"":1", json); @@ -573,21 +552,14 @@ public override JsonTypeInfo GetTypeInfo(Type type) } } - protected void AssertThrowsNSEPropMetadataInit(Action action, Type type) - { - var ex = Assert.Throws(action); - string exAsStr = ex.ToString(); - Assert.Contains(type.ToString(), exAsStr); - } - - protected void AssertFastPathLogicCorrect(string expectedJson, T value, JsonTypeInfo typeInfo) + protected static void AssertFastPathLogicCorrect(string expectedJson, T value, JsonTypeInfo typeInfo) { using MemoryStream ms = new(); using Utf8JsonWriter writer = new(ms); typeInfo.Serialize!(writer, value); writer.Flush(); - string json = Encoding.UTF8.GetString(ms.ToArray()); - JsonTestHelper.AssertJsonEqual(expectedJson, json); + + JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray())); } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonTestHelper.cs new file mode 100644 index 00000000000000..66b2020b4b808c --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonTestHelper.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 Xunit; + +namespace System.Text.Json +{ + internal static partial class JsonTestHelper + { + internal static void AssertThrows_PropMetadataInit(Action action, Type type) + { + var ex = Assert.Throws(action); + string exAsStr = ex.ToString(); + Assert.Contains(type.ToString(), exAsStr); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs index 3f3f8072b7ec23..71fdba596ad2f0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs @@ -58,7 +58,7 @@ public override void RoundTripIndexViewModel() IndexViewModel expected = CreateIndexViewModel(); string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); VerifyIndexViewModel(expected, obj); @@ -70,7 +70,7 @@ public override void RoundTripCampaignSummaryViewModel() CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); VerifyCampaignSummaryViewModel(expected, obj); @@ -84,7 +84,7 @@ public override void RoundTripCollectionsDictionary() WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); VerifyWeatherForecastWithPOCOs(expected, obj); @@ -96,7 +96,7 @@ public override void RoundTripEmptyPoco() EmptyPoco expected = CreateEmptyPoco(); string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); VerifyEmptyPoco(expected, obj); @@ -110,7 +110,7 @@ public override void RoundTripTypeNameClash() RepeatedTypes.Location expected = CreateRepeatedLocation(); string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); VerifyRepeatedLocation(expected, obj); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs index 3e137584e9ca89..df3d315b93509e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs @@ -81,7 +81,7 @@ public override void RoundTripLocation() Location expected = CreateLocation(); string json = JsonSerializer.Serialize(expected, DefaultContext.Location); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location); VerifyLocation(expected, obj); @@ -95,7 +95,7 @@ public override void RoundTripIndexViewModel() IndexViewModel expected = CreateIndexViewModel(); string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); VerifyIndexViewModel(expected, obj); @@ -109,7 +109,7 @@ public override void RoundTripCampaignSummaryViewModel() CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); VerifyCampaignSummaryViewModel(expected, obj); @@ -123,7 +123,7 @@ public override void RoundTripActiveOrUpcomingEvent() ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent); VerifyActiveOrUpcomingEvent(expected, obj); @@ -137,7 +137,7 @@ public override void RoundTripCollectionsDictionary() WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); VerifyWeatherForecastWithPOCOs(expected, obj); @@ -151,7 +151,7 @@ public override void RoundTripEmptyPoco() EmptyPoco expected = CreateEmptyPoco(); string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); VerifyEmptyPoco(expected, obj); @@ -165,7 +165,7 @@ public override void RoundTripTypeNameClash() RepeatedTypes.Location expected = CreateRepeatedLocation(); string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); - AssertThrowsNSEPropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); VerifyRepeatedLocation(expected, obj); @@ -212,6 +212,12 @@ public override void SerializeObjectArray_WithCustomOptions() Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); + // Verify JSON was written with camel casing. + Assert.Contains("activeOrUpcomingEvents", json); + Assert.Contains("featuredCampaign", json); + Assert.Contains("description", json); + Assert.Contains("organizationName", json); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); JsonElement indexAsJsonElement = (JsonElement)arr[0]; @@ -264,5 +270,15 @@ void RunTest(ClassWithEnumAndNullable expected) Assert.Equal(expected.NullableDay, actual.NullableDay); } } + + [Fact] + public override void ParameterizedConstructor() + { + string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); + Assert.Contains(@"""High"":1", json); + Assert.Contains(@"""Low"":2", json); + + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable)); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs new file mode 100644 index 00000000000000..87d8822c818ff9 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs @@ -0,0 +1,61 @@ +// 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.Encodings.Web; +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public static class SerializationLogicTests + { + [Theory] + [MemberData(nameof(GetOptionsUsingUnsupportedFeatures))] + [MemberData(nameof(GetIncompatibleOptions))] + public static void SerializationFuncNotInvokedWhenNotSupported(JsonSerializerOptions options) + { + JsonMessage message = new(); + + // Per context implementation, NotImplementedException thrown because the options are compatible, hence the serialization func is invoked. + Assert.Throws(() => JsonSerializer.Serialize(message, JsonContext.Default.JsonMessage)); + Assert.Throws(() => JsonSerializer.Serialize(message, typeof(JsonMessage), JsonContext.Default)); + + // NotSupportedException thrown because + // - the options are not compatible, hence the serialization func is not invoked. + // - the serializer correctly tries to serialize based on property metadata, but we have not provided it in our implementation. + JsonContext context = new(options); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Serialize(message, context.JsonMessage), typeof(JsonMessage)); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Serialize(message, typeof(JsonMessage), context), typeof(JsonMessage)); + } + + [Fact] + public static void DictionaryFastPathPrimitiveValueSupported() + { + Assert.NotNull(DictionaryTypeContext.Default.DictionarySystemStringSystemString.Serialize); + Assert.NotNull(DictionaryTypeContext.Default.DictionarySystemStringSystemTextJsonSourceGenerationTestsJsonMessage.Serialize); + Assert.NotNull(DictionaryTypeContext.Default.JsonMessage.Serialize); + Assert.Null(DictionaryTypeContext.Default.String.Serialize); + Assert.Null(DictionaryTypeContext.Default.Int32.Serialize); + } + + // Options with features that aren't supported in generated serialization funcs. + public static IEnumerable GetOptionsUsingUnsupportedFeatures() + { + yield return new object[] { new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } }; + yield return new object[] { new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping } }; + yield return new object[] { new JsonSerializerOptions { NumberHandling = JsonNumberHandling.WriteAsString } }; + yield return new object[] { new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve } }; + yield return new object[] { new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles } }; + yield return new object[] { new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles } }; + } + + // Options incompatible with JsonContext.s_defaultOptions below. + public static IEnumerable GetIncompatibleOptions() + { + yield return new object[] { new JsonSerializerOptions() }; + yield return new object[] { new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.Never } }; + yield return new object[] { new JsonSerializerOptions { IgnoreReadOnlyFields = true } }; + } + } +} 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 424051a7bd0cb4..09470b46800bd3 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 @@ -10,10 +10,13 @@ + + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs index fdc8d9d2988e4e..0f1f6c68dca884 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs @@ -107,4 +107,10 @@ public class MyIntermediateType { public MyType Type = new(); } + + public class JsonMessage + { + public string Message { get; set; } + public int Length => Message?.Length ?? 0; // Read-only property + } } From 2dbebc9df14dbc7c966be22f6a75db5384af8829 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Wed, 26 May 2021 00:28:47 -0700 Subject: [PATCH 6/8] Improve derived-JsonSerializerContext detection and support --- .../gen/ContextGenerationSpec.cs | 5 +- .../gen/JsonSourceGenerator.Emitter.cs | 190 +++--- .../gen/JsonSourceGenerator.Parser.cs | 100 ++- .../gen/JsonSourceGenerator.cs | 17 +- .../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 + .../ContextClasses.cs | 43 +- .../ContextTests.cs | 565 ----------------- .../JsonSerializerContextTests.cs | 30 + .../MetadataAndSerializationContextTests.cs | 106 ++-- .../MetadataContextTests.cs | 104 ++-- .../MixedModeContextTests.cs | 338 +++++------ .../RealWorldContextTests.cs | 565 +++++++++++++++++ .../SerializationContextTests.cs | 568 +++++++++--------- ...em.Text.Json.SourceGeneration.Tests.csproj | 3 +- 27 files changed, 1504 insertions(+), 1266 deletions(-) delete mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs index 6d690af0e4c698..7ba239fba491d9 100644 --- a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using System.Text.Json.SourceGeneration.Reflection; namespace System.Text.Json.SourceGeneration { @@ -18,6 +19,8 @@ internal sealed class ContextGenerationSpec public List? RootSerializableTypes { get; init; } + public List ContextClassDeclarationList { get; init; } + /// /// Types that we have initiated serialization metadata generation for. A type may be discoverable in the object graph, /// but not reachable for serialization (e.g. it is [JsonIgnore]'d); thus we maintain a separate cache. @@ -29,6 +32,6 @@ internal sealed class ContextGenerationSpec /// public HashSet RuntimePropertyNames { get; } = new(); - public string ContextTypeRef => $"global::{ContextType.FullName}"; + public string ContextTypeRef => $"global::{ContextType.GetUniqueCompilableTypeName()}"; } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 61e483f26f0f6c..176673823ad876 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -24,10 +24,12 @@ private sealed partial class Emitter private const string DefaultContextBackingStaticVarName = "s_defaultContext"; private const string WriterVarName = "writer"; private const string ValueVarName = "value"; + private const string JsonSerializerContextName = "JsonSerializerContext"; private static AssemblyName _assemblyName = typeof(Emitter).Assembly.GetName(); private static readonly string s_generatedCodeAttributeSource = $@" - [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{_assemblyName.Name}"", ""{_assemblyName.Version}"")]"; +[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{_assemblyName.Name}"", ""{_assemblyName.Version}"")] +"; // global::fully.qualified.name for referenced types private const string ArrayTypeRef = "global::System.Array"; @@ -53,9 +55,6 @@ private sealed partial class Emitter private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo"; private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo"; - // Diagnostic descriptors - private const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration"; - private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor( id: "SYSLIB1030", title: new LocalizableResourceString(nameof(SR.TypeNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), @@ -112,15 +111,41 @@ private void AddSource(string fileName, string source, bool isRootContextDef = f { string? generatedCodeAttributeSource = isRootContextDef ? s_generatedCodeAttributeSource : null; - string wrappedSource = $@"// + List declarationList = _currentContext.ContextClassDeclarationList; + int declarationCount = declarationList.Count; + Debug.Assert(declarationCount >= 1); + + StringBuilder sb = new(); + + sb.Append($@"// namespace {_currentContext.ContextType.Namespace} -{{{generatedCodeAttributeSource} - {IndentSource(source, numIndentations: 1)} -}} -"; +{{"); + + for (int i = 0; i < declarationCount - 1; i++) + { + string declarationSource = $@" +{declarationList[declarationCount - 1 - i]} +{{"; + sb.Append(IndentSource(declarationSource, numIndentations: i + 1)); + } - _executionContext.AddSource(fileName, SourceText.From(wrappedSource, Encoding.UTF8)); + // Add the core implementation for the derived context class. + string partialContextImplementation = $@"{generatedCodeAttributeSource}{declarationList[0]} +{{{IndentSource(source, Math.Max(1, declarationCount - 1))} +}}"; + sb.AppendLine(IndentSource(partialContextImplementation, numIndentations: declarationCount)); + + // Match curly brace for each containing type. + for (int i = 0; i < declarationCount - 1; i++) + { + sb.AppendLine(IndentSource("}", numIndentations: declarationCount + i + 1)); + } + + // Match curly brace for namespace. + sb.AppendLine("}"); + + _executionContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8)); } private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) @@ -851,22 +876,19 @@ private string GenerateForType(TypeGenerationSpec typeMetadata, string metadataI string typeFriendlyName = typeMetadata.TypeInfoPropertyName; string typeInfoPropertyTypeRef = $"{JsonTypeInfoTypeRef}<{typeCompilableName}>"; - return @$"{GetJsonContextDeclarationSource()} + return @$"private {typeInfoPropertyTypeRef} _{typeFriendlyName}; +public {typeInfoPropertyTypeRef} {typeFriendlyName} {{ - private {typeInfoPropertyTypeRef} _{typeFriendlyName}; - public {typeInfoPropertyTypeRef} {typeFriendlyName} + get {{ - get + if (_{typeFriendlyName} == null) {{ - if (_{typeFriendlyName} == null) - {{ - {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))} - }} - - return _{typeFriendlyName}; + {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))} }} - }}{additionalSource} -}}"; + + return _{typeFriendlyName}; + }} +}}{additionalSource}"; } private string WrapWithCheckForCustomConverterIfRequired(string source, string typeCompilableName, string typeFriendlyName, string numberHandlingNamedArg) @@ -894,27 +916,44 @@ private string GetRootJsonContextImplementation() StringBuilder sb = new(); - sb.Append(@$"{GetJsonContextDeclarationSource()} -{{ - {GetLogicForDefaultSerializerOptionsInit()} + sb.Append(@$"{GetLogicForDefaultSerializerOptionsInit()} - private static {contextTypeRef} {DefaultContextBackingStaticVarName}; - public static {contextTypeRef} Default => {DefaultContextBackingStaticVarName} ??= new {contextTypeRef}(new {JsonSerializerOptionsTypeRef}({DefaultOptionsStaticVarName})); +private static {contextTypeRef} {DefaultContextBackingStaticVarName}; +public static {contextTypeRef} Default => {DefaultContextBackingStaticVarName} ??= new {contextTypeRef}(new {JsonSerializerOptionsTypeRef}({DefaultOptionsStaticVarName})); - public {contextTypeName}() : base(null, {DefaultOptionsStaticVarName}) - {{ - }} +public {contextTypeName}() : base(null, {DefaultOptionsStaticVarName}) +{{ +}} - public {contextTypeName}({JsonSerializerOptionsTypeRef} options) : base(options, {DefaultOptionsStaticVarName}) - {{ - }} +public {contextTypeName}({JsonSerializerOptionsTypeRef} options) : base(options, {DefaultOptionsStaticVarName}) +{{ +}} - {GetFetchLogicForRuntimeSpecifiedCustomConverter()} -}}"); +{GetFetchLogicForRuntimeSpecifiedCustomConverter()}"); return sb.ToString(); } + private string GetLogicForDefaultSerializerOptionsInit() + { + JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions; + + string? namingPolicyInit = options.NamingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase + ? $@" + PropertyNamingPolicy = {JsonNamingPolicyTypeRef}.CamelCase" + : null; + + return $@" +private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ get; }} = new {JsonSerializerOptionsTypeRef}() +{{ + DefaultIgnoreCondition = {JsonIgnoreConditionTypeRef}.{options.DefaultIgnoreCondition}, + IgnoreReadOnlyFields = {options.IgnoreReadOnlyFields.ToString().ToLowerInvariant()}, + IgnoreReadOnlyProperties = {options.IgnoreReadOnlyProperties.ToString().ToLowerInvariant()}, + IncludeFields = {options.IncludeFields.ToString().ToLowerInvariant()}, + WriteIndented = {options.WriteIndented.ToString().ToLowerInvariant()},{namingPolicyInit} +}};"; + } + private string GetFetchLogicForRuntimeSpecifiedCustomConverter() { if (_currentContext.SerializerOptions.IgnoreRuntimeCustomConverters) @@ -924,59 +963,38 @@ private string GetFetchLogicForRuntimeSpecifiedCustomConverter() // TODO (https://github.com/dotnet/runtime/issues/52218): use a dictionary if count > ~15. return @$"private {JsonConverterTypeRef} {RuntimeCustomConverterFetchingMethodName}({TypeTypeRef} type) +{{ + {IListTypeRef}<{JsonConverterTypeRef}> converters = {OptionsInstanceVariableName}.Converters; + + for (int i = 0; i < converters.Count; i++) {{ - {IListTypeRef}<{JsonConverterTypeRef}> converters = {OptionsInstanceVariableName}.Converters; + {JsonConverterTypeRef} converter = converters[i]; - for (int i = 0; i < converters.Count; i++) + if (converter.CanConvert(type)) {{ - {JsonConverterTypeRef} converter = converters[i]; - - if (converter.CanConvert(type)) + if (converter is {JsonConverterFactoryTypeRef} factory) {{ - if (converter is {JsonConverterFactoryTypeRef} factory) + converter = factory.CreateConverter(type, {OptionsInstanceVariableName}); + if (converter == null || converter is {JsonConverterFactoryTypeRef}) {{ - converter = factory.CreateConverter(type, {OptionsInstanceVariableName}); - if (converter == null || converter is {JsonConverterFactoryTypeRef}) - {{ - throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance.""); - }} + throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance.""); }} - - return converter; }} - }} - return null; - }}"; - } - - private string GetLogicForDefaultSerializerOptionsInit() - { - JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions; - - string? namingPolicyInit = options.NamingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase - ? $@" - PropertyNamingPolicy = {JsonNamingPolicyTypeRef}.CamelCase" - : null; + return converter; + }} + }} - return $@"private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ get; }} = new {JsonSerializerOptionsTypeRef}() - {{ - DefaultIgnoreCondition = {JsonIgnoreConditionTypeRef}.{options.DefaultIgnoreCondition}, - IgnoreReadOnlyFields = {options.IgnoreReadOnlyFields.ToString().ToLowerInvariant()}, - IgnoreReadOnlyProperties = {options.IgnoreReadOnlyProperties.ToString().ToLowerInvariant()}, - IncludeFields = {options.IncludeFields.ToString().ToLowerInvariant()}, - WriteIndented = {options.WriteIndented.ToString().ToLowerInvariant()},{namingPolicyInit} - }};"; + return null; +}}"; } private string GetGetTypeInfoImplementation() { StringBuilder sb = new(); - sb.Append(@$"{GetJsonContextDeclarationSource()} -{{ - public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type) - {{"); + sb.Append(@$"public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type) +{{"); // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64. foreach (TypeGenerationSpec metadata in _currentContext.RootSerializableTypes) @@ -984,19 +1002,17 @@ private string GetGetTypeInfoImplementation() if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) { sb.Append($@" - if (type == typeof({metadata.TypeRef})) - {{ - return this.{metadata.TypeInfoPropertyName}; - }} + if (type == typeof({metadata.TypeRef})) + {{ + return this.{metadata.TypeInfoPropertyName}; + }} "); } } sb.Append(@" - return null!; - } -} -"); + return null!; +}"); return sb.ToString(); } @@ -1010,26 +1026,18 @@ private string GetPropertyNameInitialization() StringBuilder sb = new(); - sb.Append(@$"{GetJsonContextDeclarationSource()} -{{"); - foreach (string propName in _currentContext.RuntimePropertyNames) { sb.Append($@" - private static {JsonEncodedTextTypeRef} {propName}PropName = {JsonEncodedTextTypeRef}.Encode(""{propName}"");"); +private static {JsonEncodedTextTypeRef} {propName}PropName = {JsonEncodedTextTypeRef}.Encode(""{propName}"");"); } - sb.Append(@" -}"); - return sb.ToString(); } - private string GetJsonContextDeclarationSource() => $"internal partial class {_currentContext.ContextType.Name} : {JsonSerializerContextTypeRef}"; - private static string IndentSource(string source, int numIndentations) { - Debug.Assert(numIndentations >= 0); + Debug.Assert(numIndentations >= 1); return source.Replace(Environment.NewLine, $"{Environment.NewLine}{new string(' ', 4 * numIndentations)}"); // 4 spaces per indentation. } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 5ff89bd4badb06..b909e1e42e0097 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -11,6 +11,8 @@ using System.Text.Json.SourceGeneration.Reflection; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace System.Text.Json.SourceGeneration { @@ -32,7 +34,7 @@ private sealed class Parser private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute"; - private readonly Compilation _compilation; + private readonly GeneratorExecutionContext _executionContext; private readonly MetadataLoadContextInternal _metadataLoadContext; @@ -60,10 +62,18 @@ private sealed class Parser /// private readonly Dictionary _typeGenerationSpecCache = new(); - public Parser(Compilation compilation) + private static DiagnosticDescriptor ContextClassesMustBePartial { get; } = new DiagnosticDescriptor( + id: "SYSLIB1032", + title: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public Parser(in GeneratorExecutionContext executionContext) { - _compilation = compilation; - _metadataLoadContext = new MetadataLoadContextInternal(compilation); + _executionContext = executionContext; + _metadataLoadContext = new MetadataLoadContextInternal(executionContext.Compilation); _ienumerableType = _metadataLoadContext.Resolve(typeof(IEnumerable)); _listOfTType = _metadataLoadContext.Resolve(typeof(List<>)); @@ -85,9 +95,10 @@ public Parser(Compilation compilation) public SourceGenerationSpec? GetGenerationSpec(List classDeclarationSyntaxList) { - INamedTypeSymbol jsonSerializerContextSymbol = _compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext"); - INamedTypeSymbol jsonSerializableAttributeSymbol = _compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); - INamedTypeSymbol jsonSerializerOptionsAttributeSymbol = _compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerOptionsAttribute"); + Compilation compilation = _executionContext.Compilation; + INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext"); + INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); + INamedTypeSymbol jsonSerializerOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerOptionsAttribute"); if (jsonSerializerContextSymbol == null || jsonSerializableAttributeSymbol == null || jsonSerializerOptionsAttributeSymbol == null) { @@ -99,9 +110,9 @@ public Parser(Compilation compilation) foreach (ClassDeclarationSyntax classDeclarationSyntax in classDeclarationSyntaxList) { CompilationUnitSyntax compilationUnitSyntax = classDeclarationSyntax.FirstAncestorOrSelf(); - SemanticModel compilationSemanticModel = _compilation.GetSemanticModel(compilationUnitSyntax.SyntaxTree); + SemanticModel compilationSemanticModel = compilation.GetSemanticModel(compilationUnitSyntax.SyntaxTree); - if (!IsConfigurableJsonSerializerContext(classDeclarationSyntax, jsonSerializerContextSymbol, compilationSemanticModel)) + if (!DerivesFromJsonSerializerContext(classDeclarationSyntax, jsonSerializerContextSymbol, compilationSemanticModel)) { continue; } @@ -140,15 +151,23 @@ public Parser(Compilation compilation) continue; } - ITypeSymbol contextTypeSymbol = (ITypeSymbol)compilationSemanticModel.GetDeclaredSymbol(classDeclarationSyntax); + INamedTypeSymbol contextTypeSymbol = (INamedTypeSymbol)compilationSemanticModel.GetDeclaredSymbol(classDeclarationSyntax); Debug.Assert(contextTypeSymbol != null); + if (!TryGetClassDeclarationList(contextTypeSymbol, out List classDeclarationList)) + { + // Class or one of its containing types is not partial so we can't add to it. + _executionContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, Location.None, new string[] { contextTypeSymbol.Name })); + continue; + } + contextGenSpecList ??= new List(); contextGenSpecList.Add(new ContextGenerationSpec { SerializerOptions = options ?? new JsonSerializerOptionsAttribute(), ContextType = contextTypeSymbol.AsType(_metadataLoadContext), RootSerializableTypes = rootTypes, + ContextClassDeclarationList = classDeclarationList }); // Clear the cache of generated metadata between the processing of context classes. @@ -174,11 +193,8 @@ public Parser(Compilation compilation) }; } - // Returns true if a given type - // - derives from JsonSerializerContext - // - is partial - // - has no members - private bool IsConfigurableJsonSerializerContext( + // Returns true if a given type derives directly from JsonSerializerContext. + private bool DerivesFromJsonSerializerContext( ClassDeclarationSyntax classDeclarationSyntax, INamedTypeSymbol jsonSerializerContextSymbol, SemanticModel compilationSemanticModel) @@ -201,18 +217,56 @@ private bool IsConfigurableJsonSerializerContext( } } - if (match == null) - { - return false; - } + return match != null; + } + + private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhenAttribute(true)] out List classDeclarationList) + { + classDeclarationList = new(); + + INamedTypeSymbol currentSymbol = typeSymbol; - bool isPartial = classDeclarationSyntax.Modifiers.Where(token => token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword)).FirstOrDefault() != default; - if (!isPartial) + while (currentSymbol != null) { - return false; + ClassDeclarationSyntax? classDeclarationSyntax = currentSymbol.DeclaringSyntaxReferences.First().GetSyntax() as ClassDeclarationSyntax; + + if (classDeclarationSyntax != null) + { + SyntaxTokenList tokenList = classDeclarationSyntax.Modifiers; + int tokenCount = tokenList.Count; + + bool isPartial = false; + + string[] declarationElements = new string[tokenCount + 2]; + + for (int i = 0; i < tokenCount; i++) + { + SyntaxToken token = tokenList[i]; + declarationElements[i] = token.Text; + + if (token.IsKind(SyntaxKind.PartialKeyword)) + { + isPartial = true; + } + } + + if (!isPartial) + { + classDeclarationList = null; + return false; + } + + declarationElements[tokenCount] = "class"; + declarationElements[tokenCount + 1] = currentSymbol.Name; + + classDeclarationList.Add(string.Join(" ", declarationElements)); + } + + currentSymbol = currentSymbol.ContainingType; } - return classDeclarationSyntax.Members.Count() == 0; + Debug.Assert(classDeclarationList.Count > 0); + return true; } private TypeGenerationSpec? GetRootSerializableType(SemanticModel compilationSemanticModel, AttributeSyntax attributeSyntax) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs index d12d5ee5edb8f7..d49dfd0e75988d 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs @@ -18,12 +18,6 @@ namespace System.Text.Json.SourceGeneration [Generator] public sealed partial class JsonSourceGenerator : ISourceGenerator { - /// - /// Helper for unit tests. - /// - public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type); - private List? _rootTypes; - /// /// Registers a syntax resolver to receive compilation units. /// @@ -39,6 +33,7 @@ public void Initialize(GeneratorInitializationContext context) /// public void Execute(GeneratorExecutionContext executionContext) { + //if (!Diagnostics.Debugger.IsAttached) { Diagnostics.Debugger.Launch(); }; SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver; List? contextClasses = receiver.ClassDeclarationSyntaxList; if (contextClasses == null) @@ -46,7 +41,7 @@ public void Execute(GeneratorExecutionContext executionContext) return; } - Parser parser = new(executionContext.Compilation); + Parser parser = new(executionContext); SourceGenerationSpec? spec = parser.GetGenerationSpec(receiver.ClassDeclarationSyntaxList); if (spec != null) { @@ -57,6 +52,14 @@ public void Execute(GeneratorExecutionContext executionContext) } } + private const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration"; + + /// + /// Helper for unit tests. + /// + public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type); + private List? _rootTypes; + private sealed class SyntaxReceiver : ISyntaxReceiver { public List? ClassDeclarationSyntaxList { get; private set; } diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index 45603559480556..d6d4a153ed2e3a 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -129,4 +129,10 @@ Did not generate serialization metadata for type. + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + \ 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 85182d5d5047a0..d27ae15d636488 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 1b94670b3e5432..d2e73c0b473b34 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 55915ce81424e3..176e2138360748 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 cb63078ef065ef..288e44e6088ebb 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 2ac4e6fc16613d..434a86ffe60ac2 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 4661920d08a5ca..1ed485527a403d 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 322cc79f11a3be..e503e6b3c1574b 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 408e27c2935753..728d28ae5d3d5d 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 e131d2555c8884..b02aae15c2088a 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 f02ca1c7c22a72..ebfd3c9e24e01a 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 2a00a92caf8e67..0e590487886682 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 2822193d1924a1..27a56b787c18d2 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 c5c1cc188a7f89..9ba73dc3464081 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 @@ -2,6 +2,16 @@ + + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation. + + + + Derived 'JsonSerializerContext' types and all containing types must be partial. + Derived 'JsonSerializerContext' types and all containing types must be partial. + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. 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 d3d27853d9a840..1f310d2500bdc5 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 @@ -7,26 +7,26 @@ namespace System.Text.Json.SourceGeneration.Tests { - public interface ITestContext - { - public JsonTypeInfo Location { get; } - public JsonTypeInfo RepeatedLocation { get; } - public JsonTypeInfo ActiveOrUpcomingEvent { get; } - public JsonTypeInfo CampaignSummaryViewModel { get; } - public JsonTypeInfo IndexViewModel { get; } - public JsonTypeInfo WeatherForecastWithPOCOs { get; } - public JsonTypeInfo EmptyPoco { get; } - public JsonTypeInfo HighLowTemps { get; } - public JsonTypeInfo MyType { get; } - public JsonTypeInfo MyType2 { get; } - public JsonTypeInfo MyIntermediateType { get; } - public JsonTypeInfo HighLowTempsImmutable { get; } - public JsonTypeInfo MyNestedClass { get; } - public JsonTypeInfo MyNestedNestedClass { get; } - public JsonTypeInfo ObjectArray { get; } - public JsonTypeInfo String { get; } - public JsonTypeInfo ClassWithEnumAndNullable { get; } - } + //public interface ITestContext + //{ + // public JsonTypeInfo Location { get; } + // public JsonTypeInfo RepeatedLocation { get; } + // public JsonTypeInfo ActiveOrUpcomingEvent { get; } + // public JsonTypeInfo CampaignSummaryViewModel { get; } + // public JsonTypeInfo IndexViewModel { get; } + // public JsonTypeInfo WeatherForecastWithPOCOs { get; } + // public JsonTypeInfo EmptyPoco { get; } + // public JsonTypeInfo HighLowTemps { get; } + // public JsonTypeInfo MyType { get; } + // public JsonTypeInfo MyType2 { get; } + // public JsonTypeInfo MyIntermediateType { get; } + // public JsonTypeInfo HighLowTempsImmutable { get; } + // public JsonTypeInfo MyNestedClass { get; } + // public JsonTypeInfo MyNestedNestedClass { get; } + // public JsonTypeInfo ObjectArray { get; } + // public JsonTypeInfo String { get; } + // public JsonTypeInfo ClassWithEnumAndNullable { get; } + //} internal partial class JsonContext : JsonSerializerContext { @@ -86,4 +86,7 @@ public JsonTypeInfo JsonMessage [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(Dictionary))] internal partial class DictionaryTypeContext : JsonSerializerContext { } + + [JsonSerializable(typeof(JsonMessage))] + public partial class PublicContext : JsonSerializerContext { } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs deleted file mode 100644 index 25fbf75fc52414..00000000000000 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs +++ /dev/null @@ -1,565 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using Xunit; - -namespace System.Text.Json.SourceGeneration.Tests -{ - public abstract class ContextTests - { - protected ITestContext DefaultContext { get; } - private Func _contextCreator; - - public ContextTests(ITestContext defaultContext, Func contextCreator) - { - DefaultContext = defaultContext; - _contextCreator = contextCreator; - } - - public abstract void EnsureFastPathGeneratedAsExpected(); - - [Fact] - public virtual void RoundTripLocation() - { - Location expected = CreateLocation(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.Location); - Location obj = JsonSerializer.Deserialize(json, DefaultContext.Location); - VerifyLocation(expected, obj); - } - - [Fact] - public virtual void RoundTripIndexViewModel() - { - IndexViewModel expected = CreateIndexViewModel(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); - IndexViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel); - - VerifyIndexViewModel(expected, obj); - } - - [Fact] - public virtual void RoundTripCampaignSummaryViewModel() - { - CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); - CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel); - - VerifyCampaignSummaryViewModel(expected, obj); - } - - [Fact] - public virtual void RoundTripActiveOrUpcomingEvent() - { - ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); - ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent); - - VerifyActiveOrUpcomingEvent(expected, obj); - } - - [Fact] - public virtual void RoundTripCollectionsDictionary() - { - WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); - WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs); - - VerifyWeatherForecastWithPOCOs(expected, obj); - } - - [Fact] - public virtual void RoundTripEmptyPoco() - { - EmptyPoco expected = CreateEmptyPoco(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); - EmptyPoco obj = JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco); - - VerifyEmptyPoco(expected, obj); - } - - [Fact] - public virtual void RoundTripTypeNameClash() - { - RepeatedTypes.Location expected = CreateRepeatedLocation(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); - RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation); - - VerifyRepeatedLocation(expected, obj); - } - - protected static Location CreateLocation() - { - return new Location - { - Id = 1234, - Address1 = "The Street Name", - Address2 = "20/11", - City = "The City", - State = "The State", - PostalCode = "abc-12", - Name = "Nonexisting", - PhoneNumber = "+0 11 222 333 44", - Country = "The Greatest" - }; - } - - protected static void VerifyLocation(Location expected, Location obj) - { - Assert.Equal(expected.Address1, obj.Address1); - Assert.Equal(expected.Address2, obj.Address2); - Assert.Equal(expected.City, obj.City); - Assert.Equal(expected.State, obj.State); - Assert.Equal(expected.PostalCode, obj.PostalCode); - Assert.Equal(expected.Name, obj.Name); - Assert.Equal(expected.PhoneNumber, obj.PhoneNumber); - Assert.Equal(expected.Country, obj.Country); - } - - protected static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() - { - return new ActiveOrUpcomingEvent - { - Id = 10, - CampaignManagedOrganizerName = "Name FamilyName", - CampaignName = "The very new campaign", - Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", - EndDate = DateTime.UtcNow.AddYears(1), - Name = "Just a name", - ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", - StartDate = DateTime.UtcNow - }; - } - - protected static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj) - { - Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName); - Assert.Equal(expected.CampaignName, obj.CampaignName); - Assert.Equal(expected.Description, obj.Description); - Assert.Equal(expected.EndDate, obj.EndDate); - Assert.Equal(expected.Id, obj.Id); - Assert.Equal(expected.ImageUrl, obj.ImageUrl); - Assert.Equal(expected.Name, obj.Name); - Assert.Equal(expected.StartDate, obj.StartDate); - } - - protected static CampaignSummaryViewModel CreateCampaignSummaryViewModel() - { - return new CampaignSummaryViewModel - { - Description = "Very nice campaign", - Headline = "The Headline", - Id = 234235, - OrganizationName = "The Company XYZ", - ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", - Title = "Promoting Open Source" - }; - } - - protected static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj) - { - Assert.Equal(expected.Description, obj.Description); - Assert.Equal(expected.Headline, obj.Headline); - Assert.Equal(expected.Id, obj.Id); - Assert.Equal(expected.ImageUrl, obj.ImageUrl); - Assert.Equal(expected.OrganizationName, obj.OrganizationName); - Assert.Equal(expected.Title, obj.Title); - } - - protected static IndexViewModel CreateIndexViewModel() - { - return new IndexViewModel - { - IsNewAccount = false, - FeaturedCampaign = new CampaignSummaryViewModel - { - Description = "Very nice campaign", - Headline = "The Headline", - Id = 234235, - OrganizationName = "The Company XYZ", - ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", - Title = "Promoting Open Source" - }, - ActiveOrUpcomingEvents = Enumerable.Repeat( - new ActiveOrUpcomingEvent - { - Id = 10, - CampaignManagedOrganizerName = "Name FamilyName", - CampaignName = "The very new campaign", - Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", - EndDate = DateTime.UtcNow.AddYears(1), - Name = "Just a name", - ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", - StartDate = DateTime.UtcNow - }, - count: 20).ToList() - }; - } - - protected static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj) - { - Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count); - for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++) - { - VerifyActiveOrUpcomingEvent(expected.ActiveOrUpcomingEvents[i], obj.ActiveOrUpcomingEvents[i]); - } - - VerifyCampaignSummaryViewModel(expected.FeaturedCampaign, obj.FeaturedCampaign); - Assert.Equal(expected.HasFeaturedCampaign, obj.HasFeaturedCampaign); - Assert.Equal(expected.IsNewAccount, obj.IsNewAccount); - } - - protected static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() - { - return new WeatherForecastWithPOCOs - { - Date = DateTime.Parse("2019-08-01T00:00:00-07:00"), - TemperatureCelsius = 25, - Summary = "Hot", - DatesAvailable = new List - { - DateTimeOffset.Parse("2019-08-01T00:00:00-07:00"), - DateTimeOffset.Parse("2019-08-02T00:00:00-07:00"), - }, - TemperatureRanges = new Dictionary { - { - "Cold", - new HighLowTemps - { - High = 20, - Low = -10, - } - }, - { - "Hot", - new HighLowTemps - { - High = 60, - Low = 20, - } - }, - }, - SummaryWords = new string[] { "Cool", "Windy", "Humid" }, - }; - } - - protected static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj) - { - Assert.Equal(expected.Date, obj.Date); - Assert.Equal(expected.TemperatureCelsius, obj.TemperatureCelsius); - Assert.Equal(expected.Summary, obj.Summary); - Assert.Equal(expected.DatesAvailable.Count, obj.DatesAvailable.Count); - for (int i = 0; i < expected.DatesAvailable.Count; i++) - { - Assert.Equal(expected.DatesAvailable[i], obj.DatesAvailable[i]); - } - List> expectedTemperatureRanges = expected.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); - List> objTemperatureRanges = obj.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); - Assert.Equal(expectedTemperatureRanges.Count, objTemperatureRanges.Count); - for (int i = 0; i < expectedTemperatureRanges.Count; i++) - { - Assert.Equal(expectedTemperatureRanges[i].Key, objTemperatureRanges[i].Key); - Assert.Equal(expectedTemperatureRanges[i].Value.Low, objTemperatureRanges[i].Value.Low); - Assert.Equal(expectedTemperatureRanges[i].Value.High, objTemperatureRanges[i].Value.High); - } - Assert.Equal(expected.SummaryWords.Length, obj.SummaryWords.Length); - for (int i = 0; i < expected.SummaryWords.Length; i++) - { - Assert.Equal(expected.SummaryWords[i], obj.SummaryWords[i]); - } - } - - protected static RepeatedTypes.Location CreateRepeatedLocation() - { - return new RepeatedTypes.Location - { - FakeId = 1234, - FakeAddress1 = "The Street Name", - FakeAddress2 = "20/11", - FakeCity = "The City", - FakeState = "The State", - FakePostalCode = "abc-12", - FakeName = "Nonexisting", - FakePhoneNumber = "+0 11 222 333 44", - FakeCountry = "The Greatest" - }; - } - - protected static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) - { - Assert.Equal(expected.FakeAddress1, obj.FakeAddress1); - Assert.Equal(expected.FakeAddress2, obj.FakeAddress2); - Assert.Equal(expected.FakeCity, obj.FakeCity); - Assert.Equal(expected.FakeState, obj.FakeState); - Assert.Equal(expected.FakePostalCode, obj.FakePostalCode); - Assert.Equal(expected.FakeName, obj.FakeName); - Assert.Equal(expected.FakePhoneNumber, obj.FakePhoneNumber); - Assert.Equal(expected.FakeCountry, obj.FakeCountry); - } - - protected static EmptyPoco CreateEmptyPoco() => new EmptyPoco(); - - protected static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj) - { - Assert.NotNull(expected); - Assert.NotNull(obj); - } - - [Fact] - public virtual void NestedSameTypeWorks() - { - MyType myType = new() { Type = new() }; - string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); - myType = JsonSerializer.Deserialize(json, DefaultContext.MyType); - Assert.Equal(json, JsonSerializer.Serialize(myType, DefaultContext.MyType)); - - MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; - json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); - myType2 = JsonSerializer.Deserialize(json, DefaultContext.MyType2); - Assert.Equal(json, JsonSerializer.Serialize(myType2, DefaultContext.MyType2)); - } - - [Fact] - public virtual void SerializeObjectArray() - { - IndexViewModel index = CreateIndexViewModel(); - CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - - string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, DefaultContext.ObjectArray); - - JsonElement indexAsJsonElement = (JsonElement)arr[0]; - JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), DefaultContext.IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), DefaultContext.CampaignSummaryViewModel)); - } - - [Fact] - public virtual void SerializeObjectArray_WithCustomOptions() - { - IndexViewModel index = CreateIndexViewModel(); - CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - - JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - ITestContext context = _contextCreator(options); - - string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, context.ObjectArray); - - JsonElement indexAsJsonElement = (JsonElement)arr[0]; - JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), context.IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), context.CampaignSummaryViewModel)); - } - - [Fact] - public virtual void SerializeObjectArray_SimpleTypes_WithCustomOptions() - { - JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - ITestContext context = _contextCreator(options); - - string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); - object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)context); - - JsonElement hello = (JsonElement)arr[0]; - JsonElement world = (JsonElement)arr[1]; - Assert.Equal("\"Hello\"", hello.GetRawText()); - Assert.Equal("\"World\"", world.GetRawText()); - } - - [Fact] - public virtual void HandlesNestedTypes() - { - string json = @"{""MyInt"":5}"; - MyNestedClass obj = JsonSerializer.Deserialize(json, DefaultContext.MyNestedClass); - Assert.Equal(5, obj.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - - MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, DefaultContext.MyNestedNestedClass); - Assert.Equal(5, obj2.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); - } - - public class MyNestedClass - { - public int MyInt { get; set; } - - public class MyNestedNestedClass - { - public int MyInt { get; set; } - } - } - - [Fact] - public void ConstructingFromOptionsKeepsReference() - { - JsonStringEnumConverter converter = new(); - JsonSerializerOptions options = new() - { - PropertyNameCaseInsensitive = true, - Converters = { converter } - }; - - JsonSerializerContext context = (JsonSerializerContext)_contextCreator(options); - Assert.Same(options, context.Options); - Assert.Equal(options.PropertyNameCaseInsensitive, context.Options.PropertyNameCaseInsensitive); - Assert.Same(converter, context.Options.Converters[0]); - } - - [Fact] - public void JsonContextDefaultClonesDefaultOptions() - { - JsonSerializerContext context = (JsonSerializerContext)DefaultContext; - Assert.Equal(0, context.Options.Converters.Count); - } - - [Fact] - public void JsonContextOptionsNotMutableAfterConstruction() - { - JsonSerializerContext context = (JsonSerializerContext)DefaultContext; - InvalidOperationException ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); - string exAsStr = ex.ToString(); - Assert.Contains("JsonSerializerOptions", exAsStr); - Assert.Contains("JsonSerializerContext", exAsStr); - - context = (JsonSerializerContext)_contextCreator(new JsonSerializerOptions()); - ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); - exAsStr = ex.ToString(); - Assert.Contains("JsonSerializerOptions", exAsStr); - Assert.Contains("JsonSerializerContext", exAsStr); - } - - [Fact] - public virtual void ParameterizedConstructor() - { - string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); - Assert.Contains(@"""High"":1", json); - Assert.Contains(@"""Low"":2", json); - - // Deserialization not supported for now. - Assert.Throws(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable)); - } - - [Fact] - public virtual void EnumAndNullable() - { - RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); - RunTest(new ClassWithEnumAndNullable()); - - void RunTest(ClassWithEnumAndNullable expected) - { - string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); - ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, DefaultContext.ClassWithEnumAndNullable); - Assert.Equal(expected.Day, actual.Day); - Assert.Equal(expected.NullableDay, actual.NullableDay); - } - } - - public class ClassWithEnumAndNullable - { - public DayOfWeek Day { get; set; } - public DayOfWeek? NullableDay { get; set; } - } - - [Fact] - public void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent() - { - object[] objArr = new object[] { new MyStruct() }; - - // Metadata not generated for MyStruct without JsonSerializableAttribute. - NotSupportedException ex = Assert.Throws( - () => JsonSerializer.Serialize(objArr, DefaultContext.ObjectArray)); - string exAsStr = ex.ToString(); - Assert.Contains(typeof(MyStruct).ToString(), exAsStr); - Assert.Contains("JsonSerializerOptions", exAsStr); - - // This test uses reflection to: - // - Access JsonSerializerOptions.s_defaultSimpleConverters - // - Access JsonSerializerOptions.s_defaultFactoryConverters - // - Access JsonSerializerOptions._typeInfoCreationFunc - // - // If any of them changes, this test will need to be kept in sync. - - // Confirm built-in converters not set. - AssertFieldNull("s_defaultSimpleConverters", optionsInstance: null); - AssertFieldNull("s_defaultFactoryConverters", optionsInstance: null); - - // Confirm type info dynamic creator not set. - AssertFieldNull("_typeInfoCreationFunc", ((JsonSerializerContext)DefaultContext).Options); - - static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInstance) - { - BindingFlags bindingFlags = BindingFlags.NonPublic | (optionsInstance == null ? BindingFlags.Static : BindingFlags.Instance); - FieldInfo fieldInfo = typeof(JsonSerializerOptions).GetField(fieldName, bindingFlags); - Assert.NotNull(fieldInfo); - Assert.Null(fieldInfo.GetValue(optionsInstance)); - } - } - - private const string ExceptionMessageFromCustomContext = "Exception thrown from custom context."; - - [Fact] - public void GetTypeInfoCalledDuringPolymorphicSerialization() - { - CustomContext context = new(new JsonSerializerOptions()); - - // Empty array is fine since we don't need metadata for children. - Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), context.ObjectArray)); - Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), typeof(object[]), context)); - - // GetTypeInfo method called to get metadata for element run-time type. - object[] objArr = new object[] { new MyStruct() }; - - InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, context.ObjectArray)); - Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); - - ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, typeof(object[]), context)); - Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); - } - - internal struct MyStruct { } - - internal class CustomContext : JsonSerializerContext - { - public CustomContext(JsonSerializerOptions options) : base(options, null) { } - - private JsonTypeInfo _object; - public JsonTypeInfo Object => _object ??= JsonMetadataServices.CreateValueInfo(Options, JsonMetadataServices.ObjectConverter); - - private JsonTypeInfo _objectArray; - public JsonTypeInfo ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo(Options, Object, default, serializeFunc: null); - - public override JsonTypeInfo GetTypeInfo(Type type) - { - if (type == typeof(object[])) - { - return ObjectArray; - } - - throw new InvalidOperationException(ExceptionMessageFromCustomContext); - } - } - - protected static void AssertFastPathLogicCorrect(string expectedJson, T value, JsonTypeInfo typeInfo) - { - using MemoryStream ms = new(); - using Utf8JsonWriter writer = new(ms); - typeInfo.Serialize!(writer, value); - writer.Flush(); - - JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray())); - } - } -} 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 new file mode 100644 index 00000000000000..77882f662d6114 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.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. + +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public static partial class JsonSerializerContextTests + { + [Fact] + public static void VariousNestingAndVisibilityLevelsAreSupported() + { + Assert.NotNull(PublicContext.Default); + Assert.NotNull(NestedContext.Default); + Assert.NotNull(NestedPublicContext.Default); + Assert.NotNull(NestedPublicContext.NestedProtectedInternalClass.Default); + } + + [JsonSerializable(typeof(JsonMessage))] + internal partial class NestedContext : JsonSerializerContext { } + + [JsonSerializable(typeof(JsonMessage))] + public partial class NestedPublicContext : JsonSerializerContext + { + [JsonSerializable(typeof(JsonMessage))] + protected internal partial class NestedProtectedInternalClass : JsonSerializerContext { } + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs index 7a9086ebc6b581..fc40dd30b56388 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs @@ -1,57 +1,57 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. -using System.Text.Json.Serialization; -using Xunit; +//using System.Text.Json.Serialization; +//using Xunit; -namespace System.Text.Json.SourceGeneration.Tests -{ - [JsonSerializable(typeof(Location))] - [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] - [JsonSerializable(typeof(ActiveOrUpcomingEvent))] - [JsonSerializable(typeof(CampaignSummaryViewModel))] - [JsonSerializable(typeof(IndexViewModel))] - [JsonSerializable(typeof(WeatherForecastWithPOCOs))] - [JsonSerializable(typeof(EmptyPoco))] - // Ensure no errors when type of member in previously specified object graph is passed as input type to generator. - [JsonSerializable(typeof(HighLowTemps))] - [JsonSerializable(typeof(MyType))] - [JsonSerializable(typeof(MyType2))] - [JsonSerializable(typeof(MyIntermediateType))] - [JsonSerializable(typeof(HighLowTempsImmutable))] - [JsonSerializable(typeof(ContextTests.MyNestedClass))] - [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass))] - [JsonSerializable(typeof(object[]))] - [JsonSerializable(typeof(string))] - [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable))] - internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext - { - } +//namespace System.Text.Json.SourceGeneration.Tests +//{ +// [JsonSerializable(typeof(Location))] +// [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] +// [JsonSerializable(typeof(ActiveOrUpcomingEvent))] +// [JsonSerializable(typeof(CampaignSummaryViewModel))] +// [JsonSerializable(typeof(IndexViewModel))] +// [JsonSerializable(typeof(WeatherForecastWithPOCOs))] +// [JsonSerializable(typeof(EmptyPoco))] +// // Ensure no errors when type of member in previously specified object graph is passed as input type to generator. +// [JsonSerializable(typeof(HighLowTemps))] +// [JsonSerializable(typeof(MyType))] +// [JsonSerializable(typeof(MyType2))] +// [JsonSerializable(typeof(MyIntermediateType))] +// [JsonSerializable(typeof(HighLowTempsImmutable))] +// [JsonSerializable(typeof(ContextTests.MyNestedClass))] +// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass))] +// [JsonSerializable(typeof(object[]))] +// [JsonSerializable(typeof(string))] +// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable))] +// internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext +// { +// } - public sealed class MetadataAndSerializationContextTests : ContextTests - { - public MetadataAndSerializationContextTests() : base(MetadataAndSerializationContext.Default, (options) => new MetadataAndSerializationContext(options)) { } +// public sealed class MetadataAndSerializationContextTests : ContextTests +// { +// public MetadataAndSerializationContextTests() : base(MetadataAndSerializationContext.Default, (options) => new MetadataAndSerializationContext(options)) { } - [Fact] - public override void EnsureFastPathGeneratedAsExpected() - { - Assert.NotNull(MetadataAndSerializationContext.Default.Location.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.RepeatedLocation.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.ActiveOrUpcomingEvent.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.CampaignSummaryViewModel.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.IndexViewModel.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.WeatherForecastWithPOCOs.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.EmptyPoco.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTemps.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize); - Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize); - Assert.Null(MetadataAndSerializationContext.Default.String.Serialize); - Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize); - } - } -} +// [Fact] +// public override void EnsureFastPathGeneratedAsExpected() +// { +// Assert.NotNull(MetadataAndSerializationContext.Default.Location.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.RepeatedLocation.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.ActiveOrUpcomingEvent.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.CampaignSummaryViewModel.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.IndexViewModel.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.WeatherForecastWithPOCOs.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.EmptyPoco.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTemps.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize); +// Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize); +// Assert.Null(MetadataAndSerializationContext.Default.String.Serialize); +// Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize); +// } +// } +//} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs index e67da90ddae3ad..26325229b1afbc 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs @@ -1,56 +1,56 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. -using System.Text.Json.Serialization; -using Xunit; +//using System.Text.Json.Serialization; +//using Xunit; -namespace System.Text.Json.SourceGeneration.Tests -{ - [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] - internal partial class MetadataContext : JsonSerializerContext, ITestContext - { - } +//namespace System.Text.Json.SourceGeneration.Tests +//{ +// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] +// internal partial class MetadataContext : JsonSerializerContext, ITestContext +// { +// } - public sealed class MetadataContextTests : ContextTests - { - public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { } +// public sealed class MetadataContextTests : ContextTests +// { +// public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { } - [Fact] - public override void EnsureFastPathGeneratedAsExpected() - { - Assert.Null(MetadataContext.Default.Location.Serialize); - Assert.Null(MetadataContext.Default.RepeatedLocation.Serialize); - Assert.Null(MetadataContext.Default.ActiveOrUpcomingEvent.Serialize); - Assert.Null(MetadataContext.Default.CampaignSummaryViewModel.Serialize); - Assert.Null(MetadataContext.Default.IndexViewModel.Serialize); - Assert.Null(MetadataContext.Default.WeatherForecastWithPOCOs.Serialize); - Assert.Null(MetadataContext.Default.EmptyPoco.Serialize); - Assert.Null(MetadataContext.Default.HighLowTemps.Serialize); - Assert.Null(MetadataContext.Default.MyType.Serialize); - Assert.Null(MetadataContext.Default.MyType2.Serialize); - Assert.Null(MetadataContext.Default.MyIntermediateType.Serialize); - Assert.Null(MetadataContext.Default.HighLowTempsImmutable.Serialize); - Assert.Null(MetadataContext.Default.MyNestedClass.Serialize); - Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize); - Assert.Null(MetadataContext.Default.ObjectArray.Serialize); - Assert.Null(MetadataContext.Default.String.Serialize); - Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize); - } - } -} +// [Fact] +// public override void EnsureFastPathGeneratedAsExpected() +// { +// Assert.Null(MetadataContext.Default.Location.Serialize); +// Assert.Null(MetadataContext.Default.RepeatedLocation.Serialize); +// Assert.Null(MetadataContext.Default.ActiveOrUpcomingEvent.Serialize); +// Assert.Null(MetadataContext.Default.CampaignSummaryViewModel.Serialize); +// Assert.Null(MetadataContext.Default.IndexViewModel.Serialize); +// Assert.Null(MetadataContext.Default.WeatherForecastWithPOCOs.Serialize); +// Assert.Null(MetadataContext.Default.EmptyPoco.Serialize); +// Assert.Null(MetadataContext.Default.HighLowTemps.Serialize); +// Assert.Null(MetadataContext.Default.MyType.Serialize); +// Assert.Null(MetadataContext.Default.MyType2.Serialize); +// Assert.Null(MetadataContext.Default.MyIntermediateType.Serialize); +// Assert.Null(MetadataContext.Default.HighLowTempsImmutable.Serialize); +// Assert.Null(MetadataContext.Default.MyNestedClass.Serialize); +// Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize); +// Assert.Null(MetadataContext.Default.ObjectArray.Serialize); +// Assert.Null(MetadataContext.Default.String.Serialize); +// Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize); +// } +// } +//} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs index 71fdba596ad2f0..56cf429c8c8706 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs @@ -1,169 +1,169 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json.Serialization; -using Xunit; - -namespace System.Text.Json.SourceGeneration.Tests -{ - [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - internal partial class MixedModeContext : JsonSerializerContext, ITestContext - { - } - - public sealed class MixedModeContextTests : ContextTests - { - public MixedModeContextTests() : base(MixedModeContext.Default, (options) => new MixedModeContext(options)) { } - - [Fact] - public override void EnsureFastPathGeneratedAsExpected() - { - Assert.Null(MixedModeContext.Default.Location.Serialize); - Assert.NotNull(MixedModeContext.Default.RepeatedLocation.Serialize); - Assert.NotNull(MixedModeContext.Default.CampaignSummaryViewModel.Serialize); - Assert.Null(MixedModeContext.Default.IndexViewModel.Serialize); - Assert.Null(MixedModeContext.Default.WeatherForecastWithPOCOs.Serialize); - Assert.NotNull(MixedModeContext.Default.EmptyPoco.Serialize); - Assert.NotNull(MixedModeContext.Default.HighLowTemps.Serialize); - Assert.NotNull(MixedModeContext.Default.MyType.Serialize); - Assert.NotNull(MixedModeContext.Default.MyType2.Serialize); - Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize); - Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize); - Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize); - Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize); - Assert.Null(MixedModeContext.Default.ObjectArray.Serialize); - Assert.Null(MixedModeContext.Default.String.Serialize); - Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize); - } - - [Fact] - public override void RoundTripIndexViewModel() - { - IndexViewModel expected = CreateIndexViewModel(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); - - IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); - VerifyIndexViewModel(expected, obj); - } - - [Fact] - public override void RoundTripCampaignSummaryViewModel() - { - CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); - - CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); - VerifyCampaignSummaryViewModel(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); - } - - [Fact] - public override void RoundTripCollectionsDictionary() - { - WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); - - WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); - VerifyWeatherForecastWithPOCOs(expected, obj); - } - - [Fact] - public override void RoundTripEmptyPoco() - { - EmptyPoco expected = CreateEmptyPoco(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); - - EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); - VerifyEmptyPoco(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); - } - - [Fact] - public override void RoundTripTypeNameClash() - { - RepeatedTypes.Location expected = CreateRepeatedLocation(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); - - RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); - VerifyRepeatedLocation(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); - } - - [Fact] - public override void HandlesNestedTypes() - { - string json = @"{""MyInt"":5}"; - MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); - Assert.Equal(5, obj.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - - MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); - Assert.Equal(5, obj2.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); - } - - [Fact] - public override void SerializeObjectArray() - { - IndexViewModel index = CreateIndexViewModel(); - CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - - string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - - JsonElement indexAsJsonElement = (JsonElement)arr[0]; - JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); - } - - [Fact] - public override void SerializeObjectArray_WithCustomOptions() - { - IndexViewModel index = CreateIndexViewModel(); - CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - - ITestContext context = SerializationContextWithCamelCase.Default; - Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); - - string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - - JsonElement indexAsJsonElement = (JsonElement)arr[0]; - JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - - ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); - } - } -} +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Text.Json.Serialization; +//using Xunit; + +//namespace System.Text.Json.SourceGeneration.Tests +//{ +// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] +// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] +// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] +// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] +// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] +// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] +// internal partial class MixedModeContext : JsonSerializerContext, ITestContext +// { +// } + +// public sealed class MixedModeContextTests : ContextTests +// { +// public MixedModeContextTests() : base(MixedModeContext.Default, (options) => new MixedModeContext(options)) { } + +// [Fact] +// public override void EnsureFastPathGeneratedAsExpected() +// { +// Assert.Null(MixedModeContext.Default.Location.Serialize); +// Assert.NotNull(MixedModeContext.Default.RepeatedLocation.Serialize); +// Assert.NotNull(MixedModeContext.Default.CampaignSummaryViewModel.Serialize); +// Assert.Null(MixedModeContext.Default.IndexViewModel.Serialize); +// Assert.Null(MixedModeContext.Default.WeatherForecastWithPOCOs.Serialize); +// Assert.NotNull(MixedModeContext.Default.EmptyPoco.Serialize); +// Assert.NotNull(MixedModeContext.Default.HighLowTemps.Serialize); +// Assert.NotNull(MixedModeContext.Default.MyType.Serialize); +// Assert.NotNull(MixedModeContext.Default.MyType2.Serialize); +// Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize); +// Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize); +// Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize); +// Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize); +// Assert.Null(MixedModeContext.Default.ObjectArray.Serialize); +// Assert.Null(MixedModeContext.Default.String.Serialize); +// Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize); +// } + +// [Fact] +// public override void RoundTripIndexViewModel() +// { +// IndexViewModel expected = CreateIndexViewModel(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); + +// IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); +// VerifyIndexViewModel(expected, obj); +// } + +// [Fact] +// public override void RoundTripCampaignSummaryViewModel() +// { +// CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + +// CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); +// VerifyCampaignSummaryViewModel(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); +// } + +// [Fact] +// public override void RoundTripCollectionsDictionary() +// { +// WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); + +// WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); +// VerifyWeatherForecastWithPOCOs(expected, obj); +// } + +// [Fact] +// public override void RoundTripEmptyPoco() +// { +// EmptyPoco expected = CreateEmptyPoco(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + +// EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); +// VerifyEmptyPoco(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); +// } + +// [Fact] +// public override void RoundTripTypeNameClash() +// { +// RepeatedTypes.Location expected = CreateRepeatedLocation(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + +// RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); +// VerifyRepeatedLocation(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); +// } + +// [Fact] +// public override void HandlesNestedTypes() +// { +// string json = @"{""MyInt"":5}"; +// MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); +// Assert.Equal(5, obj.MyInt); +// Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + +// MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); +// Assert.Equal(5, obj2.MyInt); +// Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); +// } + +// [Fact] +// public override void SerializeObjectArray() +// { +// IndexViewModel index = CreateIndexViewModel(); +// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + +// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); +// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + +// JsonElement indexAsJsonElement = (JsonElement)arr[0]; +// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; +// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); +// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); +// } + +// [Fact] +// public override void SerializeObjectArray_WithCustomOptions() +// { +// IndexViewModel index = CreateIndexViewModel(); +// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + +// ITestContext context = SerializationContextWithCamelCase.Default; +// Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); + +// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); +// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + +// JsonElement indexAsJsonElement = (JsonElement)arr[0]; +// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + +// ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); +// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); +// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); +// } +// } +//} 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 new file mode 100644 index 00000000000000..a64bf4220122f7 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs @@ -0,0 +1,565 @@ +//// 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.IO; +//using System.Linq; +//using System.Reflection; +//using System.Text.Json.Serialization; +//using System.Text.Json.Serialization.Metadata; +//using Xunit; + +//namespace System.Text.Json.SourceGeneration.Tests +//{ +// public abstract class ContextTests +// { +// protected ITestContext DefaultContext { get; } +// private Func _contextCreator; + +// public ContextTests(ITestContext defaultContext, Func contextCreator) +// { +// DefaultContext = defaultContext; +// _contextCreator = contextCreator; +// } + +// public abstract void EnsureFastPathGeneratedAsExpected(); + +// [Fact] +// public virtual void RoundTripLocation() +// { +// Location expected = CreateLocation(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.Location); +// Location obj = JsonSerializer.Deserialize(json, DefaultContext.Location); +// VerifyLocation(expected, obj); +// } + +// [Fact] +// public virtual void RoundTripIndexViewModel() +// { +// IndexViewModel expected = CreateIndexViewModel(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); +// IndexViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel); + +// VerifyIndexViewModel(expected, obj); +// } + +// [Fact] +// public virtual void RoundTripCampaignSummaryViewModel() +// { +// CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); +// CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel); + +// VerifyCampaignSummaryViewModel(expected, obj); +// } + +// [Fact] +// public virtual void RoundTripActiveOrUpcomingEvent() +// { +// ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); +// ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent); + +// VerifyActiveOrUpcomingEvent(expected, obj); +// } + +// [Fact] +// public virtual void RoundTripCollectionsDictionary() +// { +// WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); +// WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs); + +// VerifyWeatherForecastWithPOCOs(expected, obj); +// } + +// [Fact] +// public virtual void RoundTripEmptyPoco() +// { +// EmptyPoco expected = CreateEmptyPoco(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); +// EmptyPoco obj = JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco); + +// VerifyEmptyPoco(expected, obj); +// } + +// [Fact] +// public virtual void RoundTripTypeNameClash() +// { +// RepeatedTypes.Location expected = CreateRepeatedLocation(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); +// RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation); + +// VerifyRepeatedLocation(expected, obj); +// } + +// protected static Location CreateLocation() +// { +// return new Location +// { +// Id = 1234, +// Address1 = "The Street Name", +// Address2 = "20/11", +// City = "The City", +// State = "The State", +// PostalCode = "abc-12", +// Name = "Nonexisting", +// PhoneNumber = "+0 11 222 333 44", +// Country = "The Greatest" +// }; +// } + +// protected static void VerifyLocation(Location expected, Location obj) +// { +// Assert.Equal(expected.Address1, obj.Address1); +// Assert.Equal(expected.Address2, obj.Address2); +// Assert.Equal(expected.City, obj.City); +// Assert.Equal(expected.State, obj.State); +// Assert.Equal(expected.PostalCode, obj.PostalCode); +// Assert.Equal(expected.Name, obj.Name); +// Assert.Equal(expected.PhoneNumber, obj.PhoneNumber); +// Assert.Equal(expected.Country, obj.Country); +// } + +// protected static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() +// { +// return new ActiveOrUpcomingEvent +// { +// Id = 10, +// CampaignManagedOrganizerName = "Name FamilyName", +// CampaignName = "The very new campaign", +// Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", +// EndDate = DateTime.UtcNow.AddYears(1), +// Name = "Just a name", +// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", +// StartDate = DateTime.UtcNow +// }; +// } + +// protected static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj) +// { +// Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName); +// Assert.Equal(expected.CampaignName, obj.CampaignName); +// Assert.Equal(expected.Description, obj.Description); +// Assert.Equal(expected.EndDate, obj.EndDate); +// Assert.Equal(expected.Id, obj.Id); +// Assert.Equal(expected.ImageUrl, obj.ImageUrl); +// Assert.Equal(expected.Name, obj.Name); +// Assert.Equal(expected.StartDate, obj.StartDate); +// } + +// protected static CampaignSummaryViewModel CreateCampaignSummaryViewModel() +// { +// return new CampaignSummaryViewModel +// { +// Description = "Very nice campaign", +// Headline = "The Headline", +// Id = 234235, +// OrganizationName = "The Company XYZ", +// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", +// Title = "Promoting Open Source" +// }; +// } + +// protected static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj) +// { +// Assert.Equal(expected.Description, obj.Description); +// Assert.Equal(expected.Headline, obj.Headline); +// Assert.Equal(expected.Id, obj.Id); +// Assert.Equal(expected.ImageUrl, obj.ImageUrl); +// Assert.Equal(expected.OrganizationName, obj.OrganizationName); +// Assert.Equal(expected.Title, obj.Title); +// } + +// protected static IndexViewModel CreateIndexViewModel() +// { +// return new IndexViewModel +// { +// IsNewAccount = false, +// FeaturedCampaign = new CampaignSummaryViewModel +// { +// Description = "Very nice campaign", +// Headline = "The Headline", +// Id = 234235, +// OrganizationName = "The Company XYZ", +// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", +// Title = "Promoting Open Source" +// }, +// ActiveOrUpcomingEvents = Enumerable.Repeat( +// new ActiveOrUpcomingEvent +// { +// Id = 10, +// CampaignManagedOrganizerName = "Name FamilyName", +// CampaignName = "The very new campaign", +// Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", +// EndDate = DateTime.UtcNow.AddYears(1), +// Name = "Just a name", +// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", +// StartDate = DateTime.UtcNow +// }, +// count: 20).ToList() +// }; +// } + +// protected static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj) +// { +// Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count); +// for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++) +// { +// VerifyActiveOrUpcomingEvent(expected.ActiveOrUpcomingEvents[i], obj.ActiveOrUpcomingEvents[i]); +// } + +// VerifyCampaignSummaryViewModel(expected.FeaturedCampaign, obj.FeaturedCampaign); +// Assert.Equal(expected.HasFeaturedCampaign, obj.HasFeaturedCampaign); +// Assert.Equal(expected.IsNewAccount, obj.IsNewAccount); +// } + +// protected static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() +// { +// return new WeatherForecastWithPOCOs +// { +// Date = DateTime.Parse("2019-08-01T00:00:00-07:00"), +// TemperatureCelsius = 25, +// Summary = "Hot", +// DatesAvailable = new List +// { +// DateTimeOffset.Parse("2019-08-01T00:00:00-07:00"), +// DateTimeOffset.Parse("2019-08-02T00:00:00-07:00"), +// }, +// TemperatureRanges = new Dictionary { +// { +// "Cold", +// new HighLowTemps +// { +// High = 20, +// Low = -10, +// } +// }, +// { +// "Hot", +// new HighLowTemps +// { +// High = 60, +// Low = 20, +// } +// }, +// }, +// SummaryWords = new string[] { "Cool", "Windy", "Humid" }, +// }; +// } + +// protected static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj) +// { +// Assert.Equal(expected.Date, obj.Date); +// Assert.Equal(expected.TemperatureCelsius, obj.TemperatureCelsius); +// Assert.Equal(expected.Summary, obj.Summary); +// Assert.Equal(expected.DatesAvailable.Count, obj.DatesAvailable.Count); +// for (int i = 0; i < expected.DatesAvailable.Count; i++) +// { +// Assert.Equal(expected.DatesAvailable[i], obj.DatesAvailable[i]); +// } +// List> expectedTemperatureRanges = expected.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); +// List> objTemperatureRanges = obj.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); +// Assert.Equal(expectedTemperatureRanges.Count, objTemperatureRanges.Count); +// for (int i = 0; i < expectedTemperatureRanges.Count; i++) +// { +// Assert.Equal(expectedTemperatureRanges[i].Key, objTemperatureRanges[i].Key); +// Assert.Equal(expectedTemperatureRanges[i].Value.Low, objTemperatureRanges[i].Value.Low); +// Assert.Equal(expectedTemperatureRanges[i].Value.High, objTemperatureRanges[i].Value.High); +// } +// Assert.Equal(expected.SummaryWords.Length, obj.SummaryWords.Length); +// for (int i = 0; i < expected.SummaryWords.Length; i++) +// { +// Assert.Equal(expected.SummaryWords[i], obj.SummaryWords[i]); +// } +// } + +// protected static RepeatedTypes.Location CreateRepeatedLocation() +// { +// return new RepeatedTypes.Location +// { +// FakeId = 1234, +// FakeAddress1 = "The Street Name", +// FakeAddress2 = "20/11", +// FakeCity = "The City", +// FakeState = "The State", +// FakePostalCode = "abc-12", +// FakeName = "Nonexisting", +// FakePhoneNumber = "+0 11 222 333 44", +// FakeCountry = "The Greatest" +// }; +// } + +// protected static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) +// { +// Assert.Equal(expected.FakeAddress1, obj.FakeAddress1); +// Assert.Equal(expected.FakeAddress2, obj.FakeAddress2); +// Assert.Equal(expected.FakeCity, obj.FakeCity); +// Assert.Equal(expected.FakeState, obj.FakeState); +// Assert.Equal(expected.FakePostalCode, obj.FakePostalCode); +// Assert.Equal(expected.FakeName, obj.FakeName); +// Assert.Equal(expected.FakePhoneNumber, obj.FakePhoneNumber); +// Assert.Equal(expected.FakeCountry, obj.FakeCountry); +// } + +// protected static EmptyPoco CreateEmptyPoco() => new EmptyPoco(); + +// protected static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj) +// { +// Assert.NotNull(expected); +// Assert.NotNull(obj); +// } + +// [Fact] +// public virtual void NestedSameTypeWorks() +// { +// MyType myType = new() { Type = new() }; +// string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); +// myType = JsonSerializer.Deserialize(json, DefaultContext.MyType); +// Assert.Equal(json, JsonSerializer.Serialize(myType, DefaultContext.MyType)); + +// MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; +// json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); +// myType2 = JsonSerializer.Deserialize(json, DefaultContext.MyType2); +// Assert.Equal(json, JsonSerializer.Serialize(myType2, DefaultContext.MyType2)); +// } + +// [Fact] +// public virtual void SerializeObjectArray() +// { +// IndexViewModel index = CreateIndexViewModel(); +// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + +// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); +// object[] arr = JsonSerializer.Deserialize(json, DefaultContext.ObjectArray); + +// JsonElement indexAsJsonElement = (JsonElement)arr[0]; +// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; +// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), DefaultContext.IndexViewModel)); +// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), DefaultContext.CampaignSummaryViewModel)); +// } + +// [Fact] +// public virtual void SerializeObjectArray_WithCustomOptions() +// { +// IndexViewModel index = CreateIndexViewModel(); +// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + +// JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; +// ITestContext context = _contextCreator(options); + +// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); +// object[] arr = JsonSerializer.Deserialize(json, context.ObjectArray); + +// JsonElement indexAsJsonElement = (JsonElement)arr[0]; +// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; +// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), context.IndexViewModel)); +// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), context.CampaignSummaryViewModel)); +// } + +// [Fact] +// public virtual void SerializeObjectArray_SimpleTypes_WithCustomOptions() +// { +// JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; +// ITestContext context = _contextCreator(options); + +// string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); +// object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)context); + +// JsonElement hello = (JsonElement)arr[0]; +// JsonElement world = (JsonElement)arr[1]; +// Assert.Equal("\"Hello\"", hello.GetRawText()); +// Assert.Equal("\"World\"", world.GetRawText()); +// } + +// [Fact] +// public virtual void HandlesNestedTypes() +// { +// string json = @"{""MyInt"":5}"; +// MyNestedClass obj = JsonSerializer.Deserialize(json, DefaultContext.MyNestedClass); +// Assert.Equal(5, obj.MyInt); +// Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + +// MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, DefaultContext.MyNestedNestedClass); +// Assert.Equal(5, obj2.MyInt); +// Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); +// } + +// public class MyNestedClass +// { +// public int MyInt { get; set; } + +// public class MyNestedNestedClass +// { +// public int MyInt { get; set; } +// } +// } + +// [Fact] +// public void ConstructingFromOptionsKeepsReference() +// { +// JsonStringEnumConverter converter = new(); +// JsonSerializerOptions options = new() +// { +// PropertyNameCaseInsensitive = true, +// Converters = { converter } +// }; + +// JsonSerializerContext context = (JsonSerializerContext)_contextCreator(options); +// Assert.Same(options, context.Options); +// Assert.Equal(options.PropertyNameCaseInsensitive, context.Options.PropertyNameCaseInsensitive); +// Assert.Same(converter, context.Options.Converters[0]); +// } + +// [Fact] +// public void JsonContextDefaultClonesDefaultOptions() +// { +// JsonSerializerContext context = (JsonSerializerContext)DefaultContext; +// Assert.Equal(0, context.Options.Converters.Count); +// } + +// [Fact] +// public void JsonContextOptionsNotMutableAfterConstruction() +// { +// JsonSerializerContext context = (JsonSerializerContext)DefaultContext; +// InvalidOperationException ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); +// string exAsStr = ex.ToString(); +// Assert.Contains("JsonSerializerOptions", exAsStr); +// Assert.Contains("JsonSerializerContext", exAsStr); + +// context = (JsonSerializerContext)_contextCreator(new JsonSerializerOptions()); +// ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); +// exAsStr = ex.ToString(); +// Assert.Contains("JsonSerializerOptions", exAsStr); +// Assert.Contains("JsonSerializerContext", exAsStr); +// } + +// [Fact] +// public virtual void ParameterizedConstructor() +// { +// string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); +// Assert.Contains(@"""High"":1", json); +// Assert.Contains(@"""Low"":2", json); + +// // Deserialization not supported for now. +// Assert.Throws(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable)); +// } + +// [Fact] +// public virtual void EnumAndNullable() +// { +// RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); +// RunTest(new ClassWithEnumAndNullable()); + +// void RunTest(ClassWithEnumAndNullable expected) +// { +// string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); +// ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, DefaultContext.ClassWithEnumAndNullable); +// Assert.Equal(expected.Day, actual.Day); +// Assert.Equal(expected.NullableDay, actual.NullableDay); +// } +// } + +// public class ClassWithEnumAndNullable +// { +// public DayOfWeek Day { get; set; } +// public DayOfWeek? NullableDay { get; set; } +// } + +// [Fact] +// public void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent() +// { +// object[] objArr = new object[] { new MyStruct() }; + +// // Metadata not generated for MyStruct without JsonSerializableAttribute. +// NotSupportedException ex = Assert.Throws( +// () => JsonSerializer.Serialize(objArr, DefaultContext.ObjectArray)); +// string exAsStr = ex.ToString(); +// Assert.Contains(typeof(MyStruct).ToString(), exAsStr); +// Assert.Contains("JsonSerializerOptions", exAsStr); + +// // This test uses reflection to: +// // - Access JsonSerializerOptions.s_defaultSimpleConverters +// // - Access JsonSerializerOptions.s_defaultFactoryConverters +// // - Access JsonSerializerOptions._typeInfoCreationFunc +// // +// // If any of them changes, this test will need to be kept in sync. + +// // Confirm built-in converters not set. +// AssertFieldNull("s_defaultSimpleConverters", optionsInstance: null); +// AssertFieldNull("s_defaultFactoryConverters", optionsInstance: null); + +// // Confirm type info dynamic creator not set. +// AssertFieldNull("_typeInfoCreationFunc", ((JsonSerializerContext)DefaultContext).Options); + +// static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInstance) +// { +// BindingFlags bindingFlags = BindingFlags.NonPublic | (optionsInstance == null ? BindingFlags.Static : BindingFlags.Instance); +// FieldInfo fieldInfo = typeof(JsonSerializerOptions).GetField(fieldName, bindingFlags); +// Assert.NotNull(fieldInfo); +// Assert.Null(fieldInfo.GetValue(optionsInstance)); +// } +// } + +// private const string ExceptionMessageFromCustomContext = "Exception thrown from custom context."; + +// [Fact] +// public void GetTypeInfoCalledDuringPolymorphicSerialization() +// { +// CustomContext context = new(new JsonSerializerOptions()); + +// // Empty array is fine since we don't need metadata for children. +// Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), context.ObjectArray)); +// Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), typeof(object[]), context)); + +// // GetTypeInfo method called to get metadata for element run-time type. +// object[] objArr = new object[] { new MyStruct() }; + +// InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, context.ObjectArray)); +// Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); + +// ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, typeof(object[]), context)); +// Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); +// } + +// internal struct MyStruct { } + +// internal class CustomContext : JsonSerializerContext +// { +// public CustomContext(JsonSerializerOptions options) : base(options, null) { } + +// private JsonTypeInfo _object; +// public JsonTypeInfo Object => _object ??= JsonMetadataServices.CreateValueInfo(Options, JsonMetadataServices.ObjectConverter); + +// private JsonTypeInfo _objectArray; +// public JsonTypeInfo ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo(Options, Object, default, serializeFunc: null); + +// public override JsonTypeInfo GetTypeInfo(Type type) +// { +// if (type == typeof(object[])) +// { +// return ObjectArray; +// } + +// throw new InvalidOperationException(ExceptionMessageFromCustomContext); +// } +// } + +// protected static void AssertFastPathLogicCorrect(string expectedJson, T value, JsonTypeInfo typeInfo) +// { +// using MemoryStream ms = new(); +// using Utf8JsonWriter writer = new(ms); +// typeInfo.Serialize!(writer, value); +// writer.Flush(); + +// JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray())); +// } +// } +//} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs index df3d315b93509e..a03b103ceebd21 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs @@ -1,284 +1,284 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json.Serialization; -using Xunit; - -namespace System.Text.Json.SourceGeneration.Tests -{ - [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] - [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] - internal partial class SerializationContext : JsonSerializerContext, ITestContext - { - } - - [JsonSerializerOptions(NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)] - [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] - [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] - internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext - { - } - - public sealed class SerializationContextTests : ContextTests - { - public SerializationContextTests() : base(SerializationContext.Default, (options) => new SerializationContext(options)) { } - - [Fact] - public override void EnsureFastPathGeneratedAsExpected() - { - Assert.NotNull(SerializationContext.Default.Location.Serialize); - Assert.NotNull(SerializationContext.Default.RepeatedLocation.Serialize); - Assert.NotNull(SerializationContext.Default.ActiveOrUpcomingEvent.Serialize); - Assert.NotNull(SerializationContext.Default.CampaignSummaryViewModel.Serialize); - Assert.NotNull(SerializationContext.Default.IndexViewModel.Serialize); - Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); - Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); - Assert.NotNull(SerializationContext.Default.HighLowTemps.Serialize); - Assert.NotNull(SerializationContext.Default.MyType.Serialize); - Assert.NotNull(SerializationContext.Default.MyType2.Serialize); - Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize); - Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize); - Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize); - Assert.NotNull(SerializationContext.Default.MyNestedNestedClass.Serialize); - Assert.Null(SerializationContext.Default.ObjectArray.Serialize); - Assert.Null(SerializationContext.Default.String.Serialize); - Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize); - } - - [Fact] - public override void RoundTripLocation() - { - Location expected = CreateLocation(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.Location); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); - - Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location); - VerifyLocation(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.Location); - } - - [Fact] - public override void RoundTripIndexViewModel() - { - IndexViewModel expected = CreateIndexViewModel(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); - - IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); - VerifyIndexViewModel(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.IndexViewModel); - } - - [Fact] - public override void RoundTripCampaignSummaryViewModel() - { - CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); - - CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); - VerifyCampaignSummaryViewModel(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); - } - - [Fact] - public override void RoundTripActiveOrUpcomingEvent() - { - ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); - - ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent); - VerifyActiveOrUpcomingEvent(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.ActiveOrUpcomingEvent); - } - - [Fact] - public override void RoundTripCollectionsDictionary() - { - WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); - - WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); - VerifyWeatherForecastWithPOCOs(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.WeatherForecastWithPOCOs); - } - - [Fact] - public override void RoundTripEmptyPoco() - { - EmptyPoco expected = CreateEmptyPoco(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); - - EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); - VerifyEmptyPoco(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); - } - - [Fact] - public override void RoundTripTypeNameClash() - { - RepeatedTypes.Location expected = CreateRepeatedLocation(); - - string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); - - RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); - VerifyRepeatedLocation(expected, obj); - - AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); - } - - [Fact] - public override void NestedSameTypeWorks() - { - MyType myType = new() { Type = new() }; - string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); - myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType); - AssertFastPathLogicCorrect(json, myType, DefaultContext.MyType); - - MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; - json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); - myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType2); - AssertFastPathLogicCorrect(json, myType2, DefaultContext.MyType2); - } - - [Fact] - public override void SerializeObjectArray() - { - IndexViewModel index = CreateIndexViewModel(); - CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - - string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - - JsonElement indexAsJsonElement = (JsonElement)arr[0]; - JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); - } - - [Fact] - public override void SerializeObjectArray_WithCustomOptions() - { - IndexViewModel index = CreateIndexViewModel(); - CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - - ITestContext context = SerializationContextWithCamelCase.Default; - Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); - - string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); - // Verify JSON was written with camel casing. - Assert.Contains("activeOrUpcomingEvents", json); - Assert.Contains("featuredCampaign", json); - Assert.Contains("description", json); - Assert.Contains("organizationName", json); - - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - - JsonElement indexAsJsonElement = (JsonElement)arr[0]; - JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - - ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); - } - - [Fact] - public override void SerializeObjectArray_SimpleTypes_WithCustomOptions() - { - JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - ITestContext context = new SerializationContext(options); - - string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); - object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataContext.Default)); - - JsonElement hello = (JsonElement)arr[0]; - JsonElement world = (JsonElement)arr[1]; - Assert.Equal("\"Hello\"", hello.GetRawText()); - Assert.Equal("\"World\"", world.GetRawText()); - } - - [Fact] - public override void HandlesNestedTypes() - { - string json = @"{""MyInt"":5}"; - MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); - Assert.Equal(5, obj.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - - MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); - Assert.Equal(5, obj2.MyInt); - Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); - } - - [Fact] - public override void EnumAndNullable() - { - RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); - RunTest(new ClassWithEnumAndNullable()); - - void RunTest(ClassWithEnumAndNullable expected) - { - string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); - ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ClassWithEnumAndNullable); - Assert.Equal(expected.Day, actual.Day); - Assert.Equal(expected.NullableDay, actual.NullableDay); - } - } - - [Fact] - public override void ParameterizedConstructor() - { - string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); - Assert.Contains(@"""High"":1", json); - Assert.Contains(@"""Low"":2", json); - - JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable)); - } - } -} +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Text.Json.Serialization; +//using Xunit; + +//namespace System.Text.Json.SourceGeneration.Tests +//{ +// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] +// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] +// internal partial class SerializationContext : JsonSerializerContext, ITestContext +// { +// } + +// [JsonSerializerOptions(NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)] +// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] +// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] +// internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext +// { +// } + +// public sealed class SerializationContextTests : ContextTests +// { +// public SerializationContextTests() : base(SerializationContext.Default, (options) => new SerializationContext(options)) { } + +// [Fact] +// public override void EnsureFastPathGeneratedAsExpected() +// { +// Assert.NotNull(SerializationContext.Default.Location.Serialize); +// Assert.NotNull(SerializationContext.Default.RepeatedLocation.Serialize); +// Assert.NotNull(SerializationContext.Default.ActiveOrUpcomingEvent.Serialize); +// Assert.NotNull(SerializationContext.Default.CampaignSummaryViewModel.Serialize); +// Assert.NotNull(SerializationContext.Default.IndexViewModel.Serialize); +// Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); +// Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); +// Assert.NotNull(SerializationContext.Default.HighLowTemps.Serialize); +// Assert.NotNull(SerializationContext.Default.MyType.Serialize); +// Assert.NotNull(SerializationContext.Default.MyType2.Serialize); +// Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize); +// Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize); +// Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize); +// Assert.NotNull(SerializationContext.Default.MyNestedNestedClass.Serialize); +// Assert.Null(SerializationContext.Default.ObjectArray.Serialize); +// Assert.Null(SerializationContext.Default.String.Serialize); +// Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize); +// } + +// [Fact] +// public override void RoundTripLocation() +// { +// Location expected = CreateLocation(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.Location); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); + +// Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location); +// VerifyLocation(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.Location); +// } + +// [Fact] +// public override void RoundTripIndexViewModel() +// { +// IndexViewModel expected = CreateIndexViewModel(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); + +// IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); +// VerifyIndexViewModel(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.IndexViewModel); +// } + +// [Fact] +// public override void RoundTripCampaignSummaryViewModel() +// { +// CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + +// CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); +// VerifyCampaignSummaryViewModel(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); +// } + +// [Fact] +// public override void RoundTripActiveOrUpcomingEvent() +// { +// ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); + +// ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent); +// VerifyActiveOrUpcomingEvent(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.ActiveOrUpcomingEvent); +// } + +// [Fact] +// public override void RoundTripCollectionsDictionary() +// { +// WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); + +// WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); +// VerifyWeatherForecastWithPOCOs(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.WeatherForecastWithPOCOs); +// } + +// [Fact] +// public override void RoundTripEmptyPoco() +// { +// EmptyPoco expected = CreateEmptyPoco(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + +// EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); +// VerifyEmptyPoco(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); +// } + +// [Fact] +// public override void RoundTripTypeNameClash() +// { +// RepeatedTypes.Location expected = CreateRepeatedLocation(); + +// string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + +// RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); +// VerifyRepeatedLocation(expected, obj); + +// AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); +// } + +// [Fact] +// public override void NestedSameTypeWorks() +// { +// MyType myType = new() { Type = new() }; +// string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); +// myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType); +// AssertFastPathLogicCorrect(json, myType, DefaultContext.MyType); + +// MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; +// json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); +// myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType2); +// AssertFastPathLogicCorrect(json, myType2, DefaultContext.MyType2); +// } + +// [Fact] +// public override void SerializeObjectArray() +// { +// IndexViewModel index = CreateIndexViewModel(); +// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + +// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); +// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + +// JsonElement indexAsJsonElement = (JsonElement)arr[0]; +// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; +// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); +// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); +// } + +// [Fact] +// public override void SerializeObjectArray_WithCustomOptions() +// { +// IndexViewModel index = CreateIndexViewModel(); +// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + +// ITestContext context = SerializationContextWithCamelCase.Default; +// Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); + +// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); +// // Verify JSON was written with camel casing. +// Assert.Contains("activeOrUpcomingEvents", json); +// Assert.Contains("featuredCampaign", json); +// Assert.Contains("description", json); +// Assert.Contains("organizationName", json); + +// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + +// JsonElement indexAsJsonElement = (JsonElement)arr[0]; +// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + +// ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); +// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); +// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); +// } + +// [Fact] +// public override void SerializeObjectArray_SimpleTypes_WithCustomOptions() +// { +// JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; +// ITestContext context = new SerializationContext(options); + +// string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); +// object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataContext.Default)); + +// JsonElement hello = (JsonElement)arr[0]; +// JsonElement world = (JsonElement)arr[1]; +// Assert.Equal("\"Hello\"", hello.GetRawText()); +// Assert.Equal("\"World\"", world.GetRawText()); +// } + +// [Fact] +// public override void HandlesNestedTypes() +// { +// string json = @"{""MyInt"":5}"; +// MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); +// Assert.Equal(5, obj.MyInt); +// Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + +// MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); +// Assert.Equal(5, obj2.MyInt); +// Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); +// } + +// [Fact] +// public override void EnumAndNullable() +// { +// RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); +// RunTest(new ClassWithEnumAndNullable()); + +// void RunTest(ClassWithEnumAndNullable expected) +// { +// string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); +// ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ClassWithEnumAndNullable); +// Assert.Equal(expected.Day, actual.Day); +// Assert.Equal(expected.NullableDay, actual.NullableDay); +// } +// } + +// [Fact] +// public override void ParameterizedConstructor() +// { +// string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); +// Assert.Contains(@"""High"":1", json); +// Assert.Contains(@"""Low"":2", json); + +// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable)); +// } +// } +//} 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 09470b46800bd3..a88c018a1e75d2 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 @@ -11,6 +11,7 @@ + @@ -18,7 +19,7 @@ - + From f855af845dbfd66869932844ce42bb12e949b446 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Wed, 26 May 2021 15:17:58 -0700 Subject: [PATCH 7/8] Address review feedback; reenable tests, and simplify object metadata init --- .../FunctionalTests/JsonContext/Person.cs | 9 +- .../gen/JsonSourceGenerator.Emitter.cs | 19 +- .../gen/TypeGenerationSpec.cs | 9 - .../System.Text.Json/ref/System.Text.Json.cs | 3 +- .../src/System.Text.Json.csproj | 2 +- .../JsonMetadataServicesConverter.cs | 3 +- .../Metadata/JsonMetadataServices.cs | 33 +- .../Metadata/JsonTypeInfoInternalOfT.cs | 56 +- .../Serialization/Metadata/JsonTypeInfoOfT.cs | 2 +- .../ContextClasses.cs | 49 +- .../MetadataAndSerializationContextTests.cs | 106 +- .../MetadataContextTests.cs | 104 +- .../MixedModeContextTests.cs | 338 ++--- .../RealWorldContextTests.cs | 1128 ++++++++--------- .../SerializationContextTests.cs | 568 ++++----- .../MetadataTests/JsonContext/HighLowTemps.cs | 9 +- .../JsonContext/WeatherForecastWithPOCOs.cs | 9 +- .../MetadataTests.JsonMetadataServices.cs | 23 +- 18 files changed, 1214 insertions(+), 1256 deletions(-) rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/{Metadata => Converters}/JsonMetadataServicesConverter.cs (98%) diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs index 4f7f2dfb7d99ae..36785e95bd05da 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs @@ -23,15 +23,14 @@ public JsonTypeInfo Person } else { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); - _Person = objectInfo; - - JsonMetadataServices.InitializeObjectInfo( - objectInfo, + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo( + Options, createObjectFunc: static () => new Person(), PersonPropInitFunc, default, serializeFunc: null); + + _Person = objectInfo; } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 176673823ad876..95b3beb24abdb9 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -474,14 +474,6 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) ? $"createObjectFunc: static () => new {typeMetadata.TypeRef}()" : "createObjectFunc: null"; - List? properties = typeMetadata.PropertiesMetadata; - - StringBuilder sb = new(); - - sb.Append($@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>({OptionsInstanceVariableName}); - _{typeFriendlyName} = objectInfo; -"); - string propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}"; string? propMetadataInitFuncSource = null; string propMetadataInitFuncNamedArg; @@ -490,6 +482,8 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) string? serializeFuncSource = null; string serializeFuncNamedArg; + List? properties = typeMetadata.PropertiesMetadata; + if (typeMetadata.GenerateMetadata) { propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, properties); @@ -510,15 +504,14 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) serializeFuncNamedArg = @"serializeFunc: null"; } - sb.Append($@" - {JsonMetadataServicesTypeRef}.InitializeObjectInfo( - objectInfo, + string objectInfoInitSource = $@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>( + {OptionsInstanceVariableName}, {createObjectFuncTypeArg}, {propMetadataInitFuncNamedArg}, {GetNumberHandlingAsStr(typeMetadata.NumberHandling)}, - {serializeFuncNamedArg});"); + {serializeFuncNamedArg}); - string objectInfoInitSource = sb.ToString(); + _{typeFriendlyName} = objectInfo;"; string additionalSource; if (propMetadataInitFuncSource == null || serializeFuncSource == null) diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index b957bb25bbc8d3..20155f0e5b4858 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -11,8 +11,6 @@ namespace System.Text.Json.SourceGeneration [DebuggerDisplay("Type={Type}, ClassType={ClassType}")] internal class TypeGenerationSpec { - private bool _hasBeenInitialized; - /// /// Fully qualified assembly name, prefixed with "global::", e.g. global::System.Numerics.BigInteger. /// @@ -73,13 +71,6 @@ public void Initialize( TypeGenerationSpec? nullableUnderlyingTypeMetadata, string? converterInstantiationLogic) { - if (_hasBeenInitialized) - { - throw new InvalidOperationException("Type metadata has already been initialized."); - } - - _hasBeenInitialized = true; - TypeRef = $"global::{typeRef}"; TypeInfoPropertyName = typeInfoPropertyName; Type = type; 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 624fceab68fbc7..85155d2440484f 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -898,12 +898,11 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateArrayInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { 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) where T : notnull { 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.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, 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, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } 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 static void InitializeObjectInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo info, System.Func? createObjectFunc, System.Func? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where T : notnull { } } public abstract partial class JsonPropertyInfo { 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 2b3bdafcf171a5..ed42782ca6148d 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -92,7 +92,7 @@ - + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs similarity index 98% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs index 8fca70ffd985a2..d28134eae452a1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs @@ -68,12 +68,13 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializer { JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; + Debug.Assert(options == jsonTypeInfo.Options); + if (!state.SupportContinuation && jsonTypeInfo is JsonTypeInfo info && info.Serialize != null && info.Options._context?.CanUseSerializationLogic == true) { - Debug.Assert(info.Serialize != null); info.Serialize(writer, value); return true; } 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 779915ec18877b..14fd4034ceabd2 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 @@ -91,39 +91,20 @@ public static JsonPropertyInfo CreatePropertyInfo( /// Creates metadata for a complex class or struct. /// /// The to initialize the metadata with. - /// The type of the class or struct. - /// Thrown when, is null. - /// A instance representing the class or struct. - public static JsonTypeInfo CreateObjectInfo(JsonSerializerOptions options) where T : notnull - => new JsonTypeInfoInternal(options, ConverterStrategy.Object); - - /// - /// Initializes metadata for a complex class or struct. - /// - /// The type of the class or struct - /// The metadata instance to augment with serialization related information. /// 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. - /// Thrown when, , or is null. - /// Thrown when , does not represent a complex class or struct type. - public static void InitializeObjectInfo( - JsonTypeInfo info, + /// The type of the class or struct. + /// Thrown when and are both 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 - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - ((JsonTypeInfoInternal)info).InitializeAsObject(createObjectFunc, propInitFunc, numberHandling, serializeFunc); - Debug.Assert(info.PropertyInfoForTypeInfo!.ConverterStrategy == ConverterStrategy.Object); - } + Action? serializeFunc) where T : notnull + => new JsonTypeInfoInternal(options, createObjectFunc, propInitFunc, numberHandling, serializeFunc); /// /// 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/JsonTypeInfoInternalOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs index b08371976801e9..b016ef98757db1 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 @@ -19,6 +19,35 @@ 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) + { + 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); +#pragma warning restore CS8714 + + PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options); + NumberHandling = numberHandling; + PropInitFunc = propInitFunc; + Serialize = serializeFunc; + SetCreateObjectFunc(createObjectFunc); + } + /// /// Creates serialization metadata for a . /// @@ -68,33 +97,6 @@ public JsonTypeInfoInternal( SetCreateObjectFunc(createObjectFunc); } - /// - /// Initializes serialization metadata for a . - /// - public void InitializeAsObject( - Func? createObjectFunc, - Func? propInitFunc, - JsonNumberHandling numberHandling, - Action? serializeFunc) - { - 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); -#pragma warning restore CS8714 - - PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options); - NumberHandling = numberHandling; - PropInitFunc = propInitFunc; - Serialize = serializeFunc; - SetCreateObjectFunc(createObjectFunc); - } - private void SetCreateObjectFunc(Func? createObjectFunc) { if (createObjectFunc != null) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 762555889e2800..208ac31539c222 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -26,6 +26,6 @@ internal JsonTypeInfo() /// A method that serializes an instance of using /// values specified at design time. /// - public Action? Serialize { get; internal set; } + public Action? Serialize { get; private protected set; } } } 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 1f310d2500bdc5..e7cde2239aaf82 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 @@ -7,26 +7,26 @@ namespace System.Text.Json.SourceGeneration.Tests { - //public interface ITestContext - //{ - // public JsonTypeInfo Location { get; } - // public JsonTypeInfo RepeatedLocation { get; } - // public JsonTypeInfo ActiveOrUpcomingEvent { get; } - // public JsonTypeInfo CampaignSummaryViewModel { get; } - // public JsonTypeInfo IndexViewModel { get; } - // public JsonTypeInfo WeatherForecastWithPOCOs { get; } - // public JsonTypeInfo EmptyPoco { get; } - // public JsonTypeInfo HighLowTemps { get; } - // public JsonTypeInfo MyType { get; } - // public JsonTypeInfo MyType2 { get; } - // public JsonTypeInfo MyIntermediateType { get; } - // public JsonTypeInfo HighLowTempsImmutable { get; } - // public JsonTypeInfo MyNestedClass { get; } - // public JsonTypeInfo MyNestedNestedClass { get; } - // public JsonTypeInfo ObjectArray { get; } - // public JsonTypeInfo String { get; } - // public JsonTypeInfo ClassWithEnumAndNullable { get; } - //} + public interface ITestContext + { + public JsonTypeInfo Location { get; } + public JsonTypeInfo RepeatedLocation { get; } + public JsonTypeInfo ActiveOrUpcomingEvent { get; } + public JsonTypeInfo CampaignSummaryViewModel { get; } + public JsonTypeInfo IndexViewModel { get; } + public JsonTypeInfo WeatherForecastWithPOCOs { get; } + public JsonTypeInfo EmptyPoco { get; } + public JsonTypeInfo HighLowTemps { get; } + public JsonTypeInfo MyType { get; } + public JsonTypeInfo MyType2 { get; } + public JsonTypeInfo MyIntermediateType { get; } + public JsonTypeInfo HighLowTempsImmutable { get; } + public JsonTypeInfo MyNestedClass { get; } + public JsonTypeInfo MyNestedNestedClass { get; } + public JsonTypeInfo ObjectArray { get; } + public JsonTypeInfo String { get; } + public JsonTypeInfo ClassWithEnumAndNullable { get; } + } internal partial class JsonContext : JsonSerializerContext { @@ -64,15 +64,14 @@ public JsonTypeInfo JsonMessage { if (_JsonMessage == null) { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); - _JsonMessage = objectInfo; - - JsonMetadataServices.InitializeObjectInfo( - objectInfo, + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo( + Options, createObjectFunc: static () => new JsonMessage(), propInitFunc: null, default, serializeFunc: JsonMessageSerialize); + + _JsonMessage = objectInfo; } return _JsonMessage; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs index fc40dd30b56388..a5ffbfbd3514fb 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs @@ -1,57 +1,57 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -//using System.Text.Json.Serialization; -//using Xunit; +using System.Text.Json.Serialization; +using Xunit; -//namespace System.Text.Json.SourceGeneration.Tests -//{ -// [JsonSerializable(typeof(Location))] -// [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] -// [JsonSerializable(typeof(ActiveOrUpcomingEvent))] -// [JsonSerializable(typeof(CampaignSummaryViewModel))] -// [JsonSerializable(typeof(IndexViewModel))] -// [JsonSerializable(typeof(WeatherForecastWithPOCOs))] -// [JsonSerializable(typeof(EmptyPoco))] -// // Ensure no errors when type of member in previously specified object graph is passed as input type to generator. -// [JsonSerializable(typeof(HighLowTemps))] -// [JsonSerializable(typeof(MyType))] -// [JsonSerializable(typeof(MyType2))] -// [JsonSerializable(typeof(MyIntermediateType))] -// [JsonSerializable(typeof(HighLowTempsImmutable))] -// [JsonSerializable(typeof(ContextTests.MyNestedClass))] -// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass))] -// [JsonSerializable(typeof(object[]))] -// [JsonSerializable(typeof(string))] -// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable))] -// internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext -// { -// } +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location))] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent))] + [JsonSerializable(typeof(CampaignSummaryViewModel))] + [JsonSerializable(typeof(IndexViewModel))] + [JsonSerializable(typeof(WeatherForecastWithPOCOs))] + [JsonSerializable(typeof(EmptyPoco))] + // Ensure no errors when type of member in previously specified object graph is passed as input type to generator. + [JsonSerializable(typeof(HighLowTemps))] + [JsonSerializable(typeof(MyType))] + [JsonSerializable(typeof(MyType2))] + [JsonSerializable(typeof(MyIntermediateType))] + [JsonSerializable(typeof(HighLowTempsImmutable))] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass))] + [JsonSerializable(typeof(object[]))] + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] + internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext + { + } -// public sealed class MetadataAndSerializationContextTests : ContextTests -// { -// public MetadataAndSerializationContextTests() : base(MetadataAndSerializationContext.Default, (options) => new MetadataAndSerializationContext(options)) { } + public sealed class MetadataAndSerializationContextTests : RealWorldContextTests + { + public MetadataAndSerializationContextTests() : base(MetadataAndSerializationContext.Default, (options) => new MetadataAndSerializationContext(options)) { } -// [Fact] -// public override void EnsureFastPathGeneratedAsExpected() -// { -// Assert.NotNull(MetadataAndSerializationContext.Default.Location.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.RepeatedLocation.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.ActiveOrUpcomingEvent.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.CampaignSummaryViewModel.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.IndexViewModel.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.WeatherForecastWithPOCOs.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.EmptyPoco.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTemps.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize); -// Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize); -// Assert.Null(MetadataAndSerializationContext.Default.String.Serialize); -// Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize); -// } -// } -//} + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.NotNull(MetadataAndSerializationContext.Default.Location.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.RepeatedLocation.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.CampaignSummaryViewModel.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.IndexViewModel.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.EmptyPoco.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTemps.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataAndSerializationContext.Default.String.Serialize); + Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs index 26325229b1afbc..5d4b469c8dac19 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs @@ -1,56 +1,56 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -//using System.Text.Json.Serialization; -//using Xunit; +using System.Text.Json.Serialization; +using Xunit; -//namespace System.Text.Json.SourceGeneration.Tests -//{ -// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] -// internal partial class MetadataContext : JsonSerializerContext, ITestContext -// { -// } +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] + internal partial class MetadataContext : JsonSerializerContext, ITestContext + { + } -// public sealed class MetadataContextTests : ContextTests -// { -// public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { } + public sealed class MetadataContextTests : RealWorldContextTests + { + public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { } -// [Fact] -// public override void EnsureFastPathGeneratedAsExpected() -// { -// Assert.Null(MetadataContext.Default.Location.Serialize); -// Assert.Null(MetadataContext.Default.RepeatedLocation.Serialize); -// Assert.Null(MetadataContext.Default.ActiveOrUpcomingEvent.Serialize); -// Assert.Null(MetadataContext.Default.CampaignSummaryViewModel.Serialize); -// Assert.Null(MetadataContext.Default.IndexViewModel.Serialize); -// Assert.Null(MetadataContext.Default.WeatherForecastWithPOCOs.Serialize); -// Assert.Null(MetadataContext.Default.EmptyPoco.Serialize); -// Assert.Null(MetadataContext.Default.HighLowTemps.Serialize); -// Assert.Null(MetadataContext.Default.MyType.Serialize); -// Assert.Null(MetadataContext.Default.MyType2.Serialize); -// Assert.Null(MetadataContext.Default.MyIntermediateType.Serialize); -// Assert.Null(MetadataContext.Default.HighLowTempsImmutable.Serialize); -// Assert.Null(MetadataContext.Default.MyNestedClass.Serialize); -// Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize); -// Assert.Null(MetadataContext.Default.ObjectArray.Serialize); -// Assert.Null(MetadataContext.Default.String.Serialize); -// Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize); -// } -// } -//} + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.Null(MetadataContext.Default.Location.Serialize); + Assert.Null(MetadataContext.Default.RepeatedLocation.Serialize); + Assert.Null(MetadataContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.Null(MetadataContext.Default.CampaignSummaryViewModel.Serialize); + Assert.Null(MetadataContext.Default.IndexViewModel.Serialize); + Assert.Null(MetadataContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.Null(MetadataContext.Default.EmptyPoco.Serialize); + Assert.Null(MetadataContext.Default.HighLowTemps.Serialize); + Assert.Null(MetadataContext.Default.MyType.Serialize); + Assert.Null(MetadataContext.Default.MyType2.Serialize); + Assert.Null(MetadataContext.Default.MyIntermediateType.Serialize); + Assert.Null(MetadataContext.Default.HighLowTempsImmutable.Serialize); + Assert.Null(MetadataContext.Default.MyNestedClass.Serialize); + Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(MetadataContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataContext.Default.String.Serialize); + Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs index 56cf429c8c8706..a92759a9ec8db2 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs @@ -1,169 +1,169 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. - -//using System.Text.Json.Serialization; -//using Xunit; - -//namespace System.Text.Json.SourceGeneration.Tests -//{ -// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] -// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] -// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] -// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] -// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] -// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] -// internal partial class MixedModeContext : JsonSerializerContext, ITestContext -// { -// } - -// public sealed class MixedModeContextTests : ContextTests -// { -// public MixedModeContextTests() : base(MixedModeContext.Default, (options) => new MixedModeContext(options)) { } - -// [Fact] -// public override void EnsureFastPathGeneratedAsExpected() -// { -// Assert.Null(MixedModeContext.Default.Location.Serialize); -// Assert.NotNull(MixedModeContext.Default.RepeatedLocation.Serialize); -// Assert.NotNull(MixedModeContext.Default.CampaignSummaryViewModel.Serialize); -// Assert.Null(MixedModeContext.Default.IndexViewModel.Serialize); -// Assert.Null(MixedModeContext.Default.WeatherForecastWithPOCOs.Serialize); -// Assert.NotNull(MixedModeContext.Default.EmptyPoco.Serialize); -// Assert.NotNull(MixedModeContext.Default.HighLowTemps.Serialize); -// Assert.NotNull(MixedModeContext.Default.MyType.Serialize); -// Assert.NotNull(MixedModeContext.Default.MyType2.Serialize); -// Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize); -// Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize); -// Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize); -// Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize); -// Assert.Null(MixedModeContext.Default.ObjectArray.Serialize); -// Assert.Null(MixedModeContext.Default.String.Serialize); -// Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize); -// } - -// [Fact] -// public override void RoundTripIndexViewModel() -// { -// IndexViewModel expected = CreateIndexViewModel(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); - -// IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); -// VerifyIndexViewModel(expected, obj); -// } - -// [Fact] -// public override void RoundTripCampaignSummaryViewModel() -// { -// CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); - -// CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); -// VerifyCampaignSummaryViewModel(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); -// } - -// [Fact] -// public override void RoundTripCollectionsDictionary() -// { -// WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); - -// WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); -// VerifyWeatherForecastWithPOCOs(expected, obj); -// } - -// [Fact] -// public override void RoundTripEmptyPoco() -// { -// EmptyPoco expected = CreateEmptyPoco(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); - -// EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); -// VerifyEmptyPoco(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); -// } - -// [Fact] -// public override void RoundTripTypeNameClash() -// { -// RepeatedTypes.Location expected = CreateRepeatedLocation(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); - -// RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); -// VerifyRepeatedLocation(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); -// } - -// [Fact] -// public override void HandlesNestedTypes() -// { -// string json = @"{""MyInt"":5}"; -// MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); -// Assert.Equal(5, obj.MyInt); -// Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - -// MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); -// Assert.Equal(5, obj2.MyInt); -// Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); -// } - -// [Fact] -// public override void SerializeObjectArray() -// { -// IndexViewModel index = CreateIndexViewModel(); -// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - -// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); -// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - -// JsonElement indexAsJsonElement = (JsonElement)arr[0]; -// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; -// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); -// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); -// } - -// [Fact] -// public override void SerializeObjectArray_WithCustomOptions() -// { -// IndexViewModel index = CreateIndexViewModel(); -// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - -// ITestContext context = SerializationContextWithCamelCase.Default; -// Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); - -// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); -// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - -// JsonElement indexAsJsonElement = (JsonElement)arr[0]; -// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - -// ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); -// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); -// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); -// } -// } -//} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + internal partial class MixedModeContext : JsonSerializerContext, ITestContext + { + } + + public sealed class MixedModeContextTests : RealWorldContextTests + { + public MixedModeContextTests() : base(MixedModeContext.Default, (options) => new MixedModeContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.Null(MixedModeContext.Default.Location.Serialize); + Assert.NotNull(MixedModeContext.Default.RepeatedLocation.Serialize); + Assert.NotNull(MixedModeContext.Default.CampaignSummaryViewModel.Serialize); + Assert.Null(MixedModeContext.Default.IndexViewModel.Serialize); + Assert.Null(MixedModeContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(MixedModeContext.Default.EmptyPoco.Serialize); + Assert.NotNull(MixedModeContext.Default.HighLowTemps.Serialize); + Assert.NotNull(MixedModeContext.Default.MyType.Serialize); + Assert.NotNull(MixedModeContext.Default.MyType2.Serialize); + Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize); + Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize); + Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize); + Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(MixedModeContext.Default.ObjectArray.Serialize); + Assert.Null(MixedModeContext.Default.String.Serialize); + Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize); + } + + [Fact] + public override void RoundTripIndexViewModel() + { + IndexViewModel expected = CreateIndexViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); + + IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); + VerifyIndexViewModel(expected, obj); + } + + [Fact] + public override void RoundTripCampaignSummaryViewModel() + { + CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); + VerifyCampaignSummaryViewModel(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); + } + + [Fact] + public override void RoundTripCollectionsDictionary() + { + WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); + + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); + VerifyWeatherForecastWithPOCOs(expected, obj); + } + + [Fact] + public override void RoundTripEmptyPoco() + { + EmptyPoco expected = CreateEmptyPoco(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + + EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); + VerifyEmptyPoco(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); + } + + [Fact] + public override void RoundTripTypeNameClash() + { + RepeatedTypes.Location expected = CreateRepeatedLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); + VerifyRepeatedLocation(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); + } + + [Fact] + public override void HandlesNestedTypes() + { + string json = @"{""MyInt"":5}"; + MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); + Assert.Equal(5, obj.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); + Assert.Equal(5, obj2.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); + } + + [Fact] + public override void SerializeObjectArray() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); + } + + [Fact] + public override void SerializeObjectArray_WithCustomOptions() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + ITestContext context = SerializationContextWithCamelCase.Default; + Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + + ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); + } + } +} 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 a64bf4220122f7..83902a98966c73 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 @@ -1,565 +1,565 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. +// 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.IO; -//using System.Linq; -//using System.Reflection; -//using System.Text.Json.Serialization; -//using System.Text.Json.Serialization.Metadata; -//using Xunit; - -//namespace System.Text.Json.SourceGeneration.Tests -//{ -// public abstract class ContextTests -// { -// protected ITestContext DefaultContext { get; } -// private Func _contextCreator; - -// public ContextTests(ITestContext defaultContext, Func contextCreator) -// { -// DefaultContext = defaultContext; -// _contextCreator = contextCreator; -// } - -// public abstract void EnsureFastPathGeneratedAsExpected(); - -// [Fact] -// public virtual void RoundTripLocation() -// { -// Location expected = CreateLocation(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.Location); -// Location obj = JsonSerializer.Deserialize(json, DefaultContext.Location); -// VerifyLocation(expected, obj); -// } - -// [Fact] -// public virtual void RoundTripIndexViewModel() -// { -// IndexViewModel expected = CreateIndexViewModel(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); -// IndexViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel); - -// VerifyIndexViewModel(expected, obj); -// } - -// [Fact] -// public virtual void RoundTripCampaignSummaryViewModel() -// { -// CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); -// CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel); - -// VerifyCampaignSummaryViewModel(expected, obj); -// } - -// [Fact] -// public virtual void RoundTripActiveOrUpcomingEvent() -// { -// ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); -// ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent); - -// VerifyActiveOrUpcomingEvent(expected, obj); -// } - -// [Fact] -// public virtual void RoundTripCollectionsDictionary() -// { -// WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); -// WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs); - -// VerifyWeatherForecastWithPOCOs(expected, obj); -// } - -// [Fact] -// public virtual void RoundTripEmptyPoco() -// { -// EmptyPoco expected = CreateEmptyPoco(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); -// EmptyPoco obj = JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco); - -// VerifyEmptyPoco(expected, obj); -// } - -// [Fact] -// public virtual void RoundTripTypeNameClash() -// { -// RepeatedTypes.Location expected = CreateRepeatedLocation(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); -// RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation); - -// VerifyRepeatedLocation(expected, obj); -// } - -// protected static Location CreateLocation() -// { -// return new Location -// { -// Id = 1234, -// Address1 = "The Street Name", -// Address2 = "20/11", -// City = "The City", -// State = "The State", -// PostalCode = "abc-12", -// Name = "Nonexisting", -// PhoneNumber = "+0 11 222 333 44", -// Country = "The Greatest" -// }; -// } - -// protected static void VerifyLocation(Location expected, Location obj) -// { -// Assert.Equal(expected.Address1, obj.Address1); -// Assert.Equal(expected.Address2, obj.Address2); -// Assert.Equal(expected.City, obj.City); -// Assert.Equal(expected.State, obj.State); -// Assert.Equal(expected.PostalCode, obj.PostalCode); -// Assert.Equal(expected.Name, obj.Name); -// Assert.Equal(expected.PhoneNumber, obj.PhoneNumber); -// Assert.Equal(expected.Country, obj.Country); -// } - -// protected static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() -// { -// return new ActiveOrUpcomingEvent -// { -// Id = 10, -// CampaignManagedOrganizerName = "Name FamilyName", -// CampaignName = "The very new campaign", -// Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", -// EndDate = DateTime.UtcNow.AddYears(1), -// Name = "Just a name", -// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", -// StartDate = DateTime.UtcNow -// }; -// } - -// protected static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj) -// { -// Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName); -// Assert.Equal(expected.CampaignName, obj.CampaignName); -// Assert.Equal(expected.Description, obj.Description); -// Assert.Equal(expected.EndDate, obj.EndDate); -// Assert.Equal(expected.Id, obj.Id); -// Assert.Equal(expected.ImageUrl, obj.ImageUrl); -// Assert.Equal(expected.Name, obj.Name); -// Assert.Equal(expected.StartDate, obj.StartDate); -// } - -// protected static CampaignSummaryViewModel CreateCampaignSummaryViewModel() -// { -// return new CampaignSummaryViewModel -// { -// Description = "Very nice campaign", -// Headline = "The Headline", -// Id = 234235, -// OrganizationName = "The Company XYZ", -// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", -// Title = "Promoting Open Source" -// }; -// } - -// protected static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj) -// { -// Assert.Equal(expected.Description, obj.Description); -// Assert.Equal(expected.Headline, obj.Headline); -// Assert.Equal(expected.Id, obj.Id); -// Assert.Equal(expected.ImageUrl, obj.ImageUrl); -// Assert.Equal(expected.OrganizationName, obj.OrganizationName); -// Assert.Equal(expected.Title, obj.Title); -// } - -// protected static IndexViewModel CreateIndexViewModel() -// { -// return new IndexViewModel -// { -// IsNewAccount = false, -// FeaturedCampaign = new CampaignSummaryViewModel -// { -// Description = "Very nice campaign", -// Headline = "The Headline", -// Id = 234235, -// OrganizationName = "The Company XYZ", -// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", -// Title = "Promoting Open Source" -// }, -// ActiveOrUpcomingEvents = Enumerable.Repeat( -// new ActiveOrUpcomingEvent -// { -// Id = 10, -// CampaignManagedOrganizerName = "Name FamilyName", -// CampaignName = "The very new campaign", -// Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", -// EndDate = DateTime.UtcNow.AddYears(1), -// Name = "Just a name", -// ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", -// StartDate = DateTime.UtcNow -// }, -// count: 20).ToList() -// }; -// } - -// protected static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj) -// { -// Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count); -// for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++) -// { -// VerifyActiveOrUpcomingEvent(expected.ActiveOrUpcomingEvents[i], obj.ActiveOrUpcomingEvents[i]); -// } - -// VerifyCampaignSummaryViewModel(expected.FeaturedCampaign, obj.FeaturedCampaign); -// Assert.Equal(expected.HasFeaturedCampaign, obj.HasFeaturedCampaign); -// Assert.Equal(expected.IsNewAccount, obj.IsNewAccount); -// } - -// protected static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() -// { -// return new WeatherForecastWithPOCOs -// { -// Date = DateTime.Parse("2019-08-01T00:00:00-07:00"), -// TemperatureCelsius = 25, -// Summary = "Hot", -// DatesAvailable = new List -// { -// DateTimeOffset.Parse("2019-08-01T00:00:00-07:00"), -// DateTimeOffset.Parse("2019-08-02T00:00:00-07:00"), -// }, -// TemperatureRanges = new Dictionary { -// { -// "Cold", -// new HighLowTemps -// { -// High = 20, -// Low = -10, -// } -// }, -// { -// "Hot", -// new HighLowTemps -// { -// High = 60, -// Low = 20, -// } -// }, -// }, -// SummaryWords = new string[] { "Cool", "Windy", "Humid" }, -// }; -// } - -// protected static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj) -// { -// Assert.Equal(expected.Date, obj.Date); -// Assert.Equal(expected.TemperatureCelsius, obj.TemperatureCelsius); -// Assert.Equal(expected.Summary, obj.Summary); -// Assert.Equal(expected.DatesAvailable.Count, obj.DatesAvailable.Count); -// for (int i = 0; i < expected.DatesAvailable.Count; i++) -// { -// Assert.Equal(expected.DatesAvailable[i], obj.DatesAvailable[i]); -// } -// List> expectedTemperatureRanges = expected.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); -// List> objTemperatureRanges = obj.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); -// Assert.Equal(expectedTemperatureRanges.Count, objTemperatureRanges.Count); -// for (int i = 0; i < expectedTemperatureRanges.Count; i++) -// { -// Assert.Equal(expectedTemperatureRanges[i].Key, objTemperatureRanges[i].Key); -// Assert.Equal(expectedTemperatureRanges[i].Value.Low, objTemperatureRanges[i].Value.Low); -// Assert.Equal(expectedTemperatureRanges[i].Value.High, objTemperatureRanges[i].Value.High); -// } -// Assert.Equal(expected.SummaryWords.Length, obj.SummaryWords.Length); -// for (int i = 0; i < expected.SummaryWords.Length; i++) -// { -// Assert.Equal(expected.SummaryWords[i], obj.SummaryWords[i]); -// } -// } - -// protected static RepeatedTypes.Location CreateRepeatedLocation() -// { -// return new RepeatedTypes.Location -// { -// FakeId = 1234, -// FakeAddress1 = "The Street Name", -// FakeAddress2 = "20/11", -// FakeCity = "The City", -// FakeState = "The State", -// FakePostalCode = "abc-12", -// FakeName = "Nonexisting", -// FakePhoneNumber = "+0 11 222 333 44", -// FakeCountry = "The Greatest" -// }; -// } - -// protected static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) -// { -// Assert.Equal(expected.FakeAddress1, obj.FakeAddress1); -// Assert.Equal(expected.FakeAddress2, obj.FakeAddress2); -// Assert.Equal(expected.FakeCity, obj.FakeCity); -// Assert.Equal(expected.FakeState, obj.FakeState); -// Assert.Equal(expected.FakePostalCode, obj.FakePostalCode); -// Assert.Equal(expected.FakeName, obj.FakeName); -// Assert.Equal(expected.FakePhoneNumber, obj.FakePhoneNumber); -// Assert.Equal(expected.FakeCountry, obj.FakeCountry); -// } - -// protected static EmptyPoco CreateEmptyPoco() => new EmptyPoco(); - -// protected static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj) -// { -// Assert.NotNull(expected); -// Assert.NotNull(obj); -// } - -// [Fact] -// public virtual void NestedSameTypeWorks() -// { -// MyType myType = new() { Type = new() }; -// string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); -// myType = JsonSerializer.Deserialize(json, DefaultContext.MyType); -// Assert.Equal(json, JsonSerializer.Serialize(myType, DefaultContext.MyType)); - -// MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; -// json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); -// myType2 = JsonSerializer.Deserialize(json, DefaultContext.MyType2); -// Assert.Equal(json, JsonSerializer.Serialize(myType2, DefaultContext.MyType2)); -// } - -// [Fact] -// public virtual void SerializeObjectArray() -// { -// IndexViewModel index = CreateIndexViewModel(); -// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - -// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); -// object[] arr = JsonSerializer.Deserialize(json, DefaultContext.ObjectArray); - -// JsonElement indexAsJsonElement = (JsonElement)arr[0]; -// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; -// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), DefaultContext.IndexViewModel)); -// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), DefaultContext.CampaignSummaryViewModel)); -// } - -// [Fact] -// public virtual void SerializeObjectArray_WithCustomOptions() -// { -// IndexViewModel index = CreateIndexViewModel(); -// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - -// JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; -// ITestContext context = _contextCreator(options); - -// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); -// object[] arr = JsonSerializer.Deserialize(json, context.ObjectArray); - -// JsonElement indexAsJsonElement = (JsonElement)arr[0]; -// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; -// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), context.IndexViewModel)); -// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), context.CampaignSummaryViewModel)); -// } - -// [Fact] -// public virtual void SerializeObjectArray_SimpleTypes_WithCustomOptions() -// { -// JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; -// ITestContext context = _contextCreator(options); - -// string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); -// object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)context); - -// JsonElement hello = (JsonElement)arr[0]; -// JsonElement world = (JsonElement)arr[1]; -// Assert.Equal("\"Hello\"", hello.GetRawText()); -// Assert.Equal("\"World\"", world.GetRawText()); -// } - -// [Fact] -// public virtual void HandlesNestedTypes() -// { -// string json = @"{""MyInt"":5}"; -// MyNestedClass obj = JsonSerializer.Deserialize(json, DefaultContext.MyNestedClass); -// Assert.Equal(5, obj.MyInt); -// Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - -// MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, DefaultContext.MyNestedNestedClass); -// Assert.Equal(5, obj2.MyInt); -// Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); -// } - -// public class MyNestedClass -// { -// public int MyInt { get; set; } - -// public class MyNestedNestedClass -// { -// public int MyInt { get; set; } -// } -// } - -// [Fact] -// public void ConstructingFromOptionsKeepsReference() -// { -// JsonStringEnumConverter converter = new(); -// JsonSerializerOptions options = new() -// { -// PropertyNameCaseInsensitive = true, -// Converters = { converter } -// }; - -// JsonSerializerContext context = (JsonSerializerContext)_contextCreator(options); -// Assert.Same(options, context.Options); -// Assert.Equal(options.PropertyNameCaseInsensitive, context.Options.PropertyNameCaseInsensitive); -// Assert.Same(converter, context.Options.Converters[0]); -// } - -// [Fact] -// public void JsonContextDefaultClonesDefaultOptions() -// { -// JsonSerializerContext context = (JsonSerializerContext)DefaultContext; -// Assert.Equal(0, context.Options.Converters.Count); -// } - -// [Fact] -// public void JsonContextOptionsNotMutableAfterConstruction() -// { -// JsonSerializerContext context = (JsonSerializerContext)DefaultContext; -// InvalidOperationException ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); -// string exAsStr = ex.ToString(); -// Assert.Contains("JsonSerializerOptions", exAsStr); -// Assert.Contains("JsonSerializerContext", exAsStr); - -// context = (JsonSerializerContext)_contextCreator(new JsonSerializerOptions()); -// ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); -// exAsStr = ex.ToString(); -// Assert.Contains("JsonSerializerOptions", exAsStr); -// Assert.Contains("JsonSerializerContext", exAsStr); -// } - -// [Fact] -// public virtual void ParameterizedConstructor() -// { -// string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); -// Assert.Contains(@"""High"":1", json); -// Assert.Contains(@"""Low"":2", json); - -// // Deserialization not supported for now. -// Assert.Throws(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable)); -// } - -// [Fact] -// public virtual void EnumAndNullable() -// { -// RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); -// RunTest(new ClassWithEnumAndNullable()); - -// void RunTest(ClassWithEnumAndNullable expected) -// { -// string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); -// ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, DefaultContext.ClassWithEnumAndNullable); -// Assert.Equal(expected.Day, actual.Day); -// Assert.Equal(expected.NullableDay, actual.NullableDay); -// } -// } - -// public class ClassWithEnumAndNullable -// { -// public DayOfWeek Day { get; set; } -// public DayOfWeek? NullableDay { get; set; } -// } - -// [Fact] -// public void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent() -// { -// object[] objArr = new object[] { new MyStruct() }; - -// // Metadata not generated for MyStruct without JsonSerializableAttribute. -// NotSupportedException ex = Assert.Throws( -// () => JsonSerializer.Serialize(objArr, DefaultContext.ObjectArray)); -// string exAsStr = ex.ToString(); -// Assert.Contains(typeof(MyStruct).ToString(), exAsStr); -// Assert.Contains("JsonSerializerOptions", exAsStr); - -// // This test uses reflection to: -// // - Access JsonSerializerOptions.s_defaultSimpleConverters -// // - Access JsonSerializerOptions.s_defaultFactoryConverters -// // - Access JsonSerializerOptions._typeInfoCreationFunc -// // -// // If any of them changes, this test will need to be kept in sync. - -// // Confirm built-in converters not set. -// AssertFieldNull("s_defaultSimpleConverters", optionsInstance: null); -// AssertFieldNull("s_defaultFactoryConverters", optionsInstance: null); - -// // Confirm type info dynamic creator not set. -// AssertFieldNull("_typeInfoCreationFunc", ((JsonSerializerContext)DefaultContext).Options); - -// static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInstance) -// { -// BindingFlags bindingFlags = BindingFlags.NonPublic | (optionsInstance == null ? BindingFlags.Static : BindingFlags.Instance); -// FieldInfo fieldInfo = typeof(JsonSerializerOptions).GetField(fieldName, bindingFlags); -// Assert.NotNull(fieldInfo); -// Assert.Null(fieldInfo.GetValue(optionsInstance)); -// } -// } - -// private const string ExceptionMessageFromCustomContext = "Exception thrown from custom context."; - -// [Fact] -// public void GetTypeInfoCalledDuringPolymorphicSerialization() -// { -// CustomContext context = new(new JsonSerializerOptions()); - -// // Empty array is fine since we don't need metadata for children. -// Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), context.ObjectArray)); -// Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), typeof(object[]), context)); - -// // GetTypeInfo method called to get metadata for element run-time type. -// object[] objArr = new object[] { new MyStruct() }; - -// InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, context.ObjectArray)); -// Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); - -// ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, typeof(object[]), context)); -// Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); -// } - -// internal struct MyStruct { } - -// internal class CustomContext : JsonSerializerContext -// { -// public CustomContext(JsonSerializerOptions options) : base(options, null) { } - -// private JsonTypeInfo _object; -// public JsonTypeInfo Object => _object ??= JsonMetadataServices.CreateValueInfo(Options, JsonMetadataServices.ObjectConverter); - -// private JsonTypeInfo _objectArray; -// public JsonTypeInfo ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo(Options, Object, default, serializeFunc: null); - -// public override JsonTypeInfo GetTypeInfo(Type type) -// { -// if (type == typeof(object[])) -// { -// return ObjectArray; -// } - -// throw new InvalidOperationException(ExceptionMessageFromCustomContext); -// } -// } - -// protected static void AssertFastPathLogicCorrect(string expectedJson, T value, JsonTypeInfo typeInfo) -// { -// using MemoryStream ms = new(); -// using Utf8JsonWriter writer = new(ms); -// typeInfo.Serialize!(writer, value); -// writer.Flush(); - -// JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray())); -// } -// } -//} +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public abstract class RealWorldContextTests + { + protected ITestContext DefaultContext { get; } + private Func _contextCreator; + + public RealWorldContextTests(ITestContext defaultContext, Func contextCreator) + { + DefaultContext = defaultContext; + _contextCreator = contextCreator; + } + + public abstract void EnsureFastPathGeneratedAsExpected(); + + [Fact] + public virtual void RoundTripLocation() + { + Location expected = CreateLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.Location); + Location obj = JsonSerializer.Deserialize(json, DefaultContext.Location); + VerifyLocation(expected, obj); + } + + [Fact] + public virtual void RoundTripIndexViewModel() + { + IndexViewModel expected = CreateIndexViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); + IndexViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel); + + VerifyIndexViewModel(expected, obj); + } + + [Fact] + public virtual void RoundTripCampaignSummaryViewModel() + { + CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel); + + VerifyCampaignSummaryViewModel(expected, obj); + } + + [Fact] + public virtual void RoundTripActiveOrUpcomingEvent() + { + ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); + ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent); + + VerifyActiveOrUpcomingEvent(expected, obj); + } + + [Fact] + public virtual void RoundTripCollectionsDictionary() + { + WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs); + + VerifyWeatherForecastWithPOCOs(expected, obj); + } + + [Fact] + public virtual void RoundTripEmptyPoco() + { + EmptyPoco expected = CreateEmptyPoco(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); + EmptyPoco obj = JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco); + + VerifyEmptyPoco(expected, obj); + } + + [Fact] + public virtual void RoundTripTypeNameClash() + { + RepeatedTypes.Location expected = CreateRepeatedLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation); + + VerifyRepeatedLocation(expected, obj); + } + + protected static Location CreateLocation() + { + return new Location + { + Id = 1234, + Address1 = "The Street Name", + Address2 = "20/11", + City = "The City", + State = "The State", + PostalCode = "abc-12", + Name = "Nonexisting", + PhoneNumber = "+0 11 222 333 44", + Country = "The Greatest" + }; + } + + protected static void VerifyLocation(Location expected, Location obj) + { + Assert.Equal(expected.Address1, obj.Address1); + Assert.Equal(expected.Address2, obj.Address2); + Assert.Equal(expected.City, obj.City); + Assert.Equal(expected.State, obj.State); + Assert.Equal(expected.PostalCode, obj.PostalCode); + Assert.Equal(expected.Name, obj.Name); + Assert.Equal(expected.PhoneNumber, obj.PhoneNumber); + Assert.Equal(expected.Country, obj.Country); + } + + protected static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() + { + return new ActiveOrUpcomingEvent + { + Id = 10, + CampaignManagedOrganizerName = "Name FamilyName", + CampaignName = "The very new campaign", + Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", + EndDate = DateTime.UtcNow.AddYears(1), + Name = "Just a name", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + StartDate = DateTime.UtcNow + }; + } + + protected static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj) + { + Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName); + Assert.Equal(expected.CampaignName, obj.CampaignName); + Assert.Equal(expected.Description, obj.Description); + Assert.Equal(expected.EndDate, obj.EndDate); + Assert.Equal(expected.Id, obj.Id); + Assert.Equal(expected.ImageUrl, obj.ImageUrl); + Assert.Equal(expected.Name, obj.Name); + Assert.Equal(expected.StartDate, obj.StartDate); + } + + protected static CampaignSummaryViewModel CreateCampaignSummaryViewModel() + { + return new CampaignSummaryViewModel + { + Description = "Very nice campaign", + Headline = "The Headline", + Id = 234235, + OrganizationName = "The Company XYZ", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + Title = "Promoting Open Source" + }; + } + + protected static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj) + { + Assert.Equal(expected.Description, obj.Description); + Assert.Equal(expected.Headline, obj.Headline); + Assert.Equal(expected.Id, obj.Id); + Assert.Equal(expected.ImageUrl, obj.ImageUrl); + Assert.Equal(expected.OrganizationName, obj.OrganizationName); + Assert.Equal(expected.Title, obj.Title); + } + + protected static IndexViewModel CreateIndexViewModel() + { + return new IndexViewModel + { + IsNewAccount = false, + FeaturedCampaign = new CampaignSummaryViewModel + { + Description = "Very nice campaign", + Headline = "The Headline", + Id = 234235, + OrganizationName = "The Company XYZ", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + Title = "Promoting Open Source" + }, + ActiveOrUpcomingEvents = Enumerable.Repeat( + new ActiveOrUpcomingEvent + { + Id = 10, + CampaignManagedOrganizerName = "Name FamilyName", + CampaignName = "The very new campaign", + Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", + EndDate = DateTime.UtcNow.AddYears(1), + Name = "Just a name", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + StartDate = DateTime.UtcNow + }, + count: 20).ToList() + }; + } + + protected static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj) + { + Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count); + for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++) + { + VerifyActiveOrUpcomingEvent(expected.ActiveOrUpcomingEvents[i], obj.ActiveOrUpcomingEvents[i]); + } + + VerifyCampaignSummaryViewModel(expected.FeaturedCampaign, obj.FeaturedCampaign); + Assert.Equal(expected.HasFeaturedCampaign, obj.HasFeaturedCampaign); + Assert.Equal(expected.IsNewAccount, obj.IsNewAccount); + } + + protected static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() + { + return new WeatherForecastWithPOCOs + { + Date = DateTime.Parse("2019-08-01T00:00:00-07:00"), + TemperatureCelsius = 25, + Summary = "Hot", + DatesAvailable = new List + { + DateTimeOffset.Parse("2019-08-01T00:00:00-07:00"), + DateTimeOffset.Parse("2019-08-02T00:00:00-07:00"), + }, + TemperatureRanges = new Dictionary { + { + "Cold", + new HighLowTemps + { + High = 20, + Low = -10, + } + }, + { + "Hot", + new HighLowTemps + { + High = 60, + Low = 20, + } + }, + }, + SummaryWords = new string[] { "Cool", "Windy", "Humid" }, + }; + } + + protected static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj) + { + Assert.Equal(expected.Date, obj.Date); + Assert.Equal(expected.TemperatureCelsius, obj.TemperatureCelsius); + Assert.Equal(expected.Summary, obj.Summary); + Assert.Equal(expected.DatesAvailable.Count, obj.DatesAvailable.Count); + for (int i = 0; i < expected.DatesAvailable.Count; i++) + { + Assert.Equal(expected.DatesAvailable[i], obj.DatesAvailable[i]); + } + List> expectedTemperatureRanges = expected.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); + List> objTemperatureRanges = obj.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); + Assert.Equal(expectedTemperatureRanges.Count, objTemperatureRanges.Count); + for (int i = 0; i < expectedTemperatureRanges.Count; i++) + { + Assert.Equal(expectedTemperatureRanges[i].Key, objTemperatureRanges[i].Key); + Assert.Equal(expectedTemperatureRanges[i].Value.Low, objTemperatureRanges[i].Value.Low); + Assert.Equal(expectedTemperatureRanges[i].Value.High, objTemperatureRanges[i].Value.High); + } + Assert.Equal(expected.SummaryWords.Length, obj.SummaryWords.Length); + for (int i = 0; i < expected.SummaryWords.Length; i++) + { + Assert.Equal(expected.SummaryWords[i], obj.SummaryWords[i]); + } + } + + protected static RepeatedTypes.Location CreateRepeatedLocation() + { + return new RepeatedTypes.Location + { + FakeId = 1234, + FakeAddress1 = "The Street Name", + FakeAddress2 = "20/11", + FakeCity = "The City", + FakeState = "The State", + FakePostalCode = "abc-12", + FakeName = "Nonexisting", + FakePhoneNumber = "+0 11 222 333 44", + FakeCountry = "The Greatest" + }; + } + + protected static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) + { + Assert.Equal(expected.FakeAddress1, obj.FakeAddress1); + Assert.Equal(expected.FakeAddress2, obj.FakeAddress2); + Assert.Equal(expected.FakeCity, obj.FakeCity); + Assert.Equal(expected.FakeState, obj.FakeState); + Assert.Equal(expected.FakePostalCode, obj.FakePostalCode); + Assert.Equal(expected.FakeName, obj.FakeName); + Assert.Equal(expected.FakePhoneNumber, obj.FakePhoneNumber); + Assert.Equal(expected.FakeCountry, obj.FakeCountry); + } + + protected static EmptyPoco CreateEmptyPoco() => new EmptyPoco(); + + protected static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj) + { + Assert.NotNull(expected); + Assert.NotNull(obj); + } + + [Fact] + public virtual void NestedSameTypeWorks() + { + MyType myType = new() { Type = new() }; + string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); + myType = JsonSerializer.Deserialize(json, DefaultContext.MyType); + Assert.Equal(json, JsonSerializer.Serialize(myType, DefaultContext.MyType)); + + MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; + json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); + myType2 = JsonSerializer.Deserialize(json, DefaultContext.MyType2); + Assert.Equal(json, JsonSerializer.Serialize(myType2, DefaultContext.MyType2)); + } + + [Fact] + public virtual void SerializeObjectArray() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, DefaultContext.ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), DefaultContext.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), DefaultContext.CampaignSummaryViewModel)); + } + + [Fact] + public virtual void SerializeObjectArray_WithCustomOptions() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + ITestContext context = _contextCreator(options); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, context.ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), context.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), context.CampaignSummaryViewModel)); + } + + [Fact] + public virtual void SerializeObjectArray_SimpleTypes_WithCustomOptions() + { + JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + ITestContext context = _contextCreator(options); + + string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); + object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)context); + + JsonElement hello = (JsonElement)arr[0]; + JsonElement world = (JsonElement)arr[1]; + Assert.Equal("\"Hello\"", hello.GetRawText()); + Assert.Equal("\"World\"", world.GetRawText()); + } + + [Fact] + public virtual void HandlesNestedTypes() + { + string json = @"{""MyInt"":5}"; + MyNestedClass obj = JsonSerializer.Deserialize(json, DefaultContext.MyNestedClass); + Assert.Equal(5, obj.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, DefaultContext.MyNestedNestedClass); + Assert.Equal(5, obj2.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); + } + + public class MyNestedClass + { + public int MyInt { get; set; } + + public class MyNestedNestedClass + { + public int MyInt { get; set; } + } + } + + [Fact] + public void ConstructingFromOptionsKeepsReference() + { + JsonStringEnumConverter converter = new(); + JsonSerializerOptions options = new() + { + PropertyNameCaseInsensitive = true, + Converters = { converter } + }; + + JsonSerializerContext context = (JsonSerializerContext)_contextCreator(options); + Assert.Same(options, context.Options); + Assert.Equal(options.PropertyNameCaseInsensitive, context.Options.PropertyNameCaseInsensitive); + Assert.Same(converter, context.Options.Converters[0]); + } + + [Fact] + public void JsonContextDefaultClonesDefaultOptions() + { + JsonSerializerContext context = (JsonSerializerContext)DefaultContext; + Assert.Equal(0, context.Options.Converters.Count); + } + + [Fact] + public void JsonContextOptionsNotMutableAfterConstruction() + { + JsonSerializerContext context = (JsonSerializerContext)DefaultContext; + InvalidOperationException ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); + string exAsStr = ex.ToString(); + Assert.Contains("JsonSerializerOptions", exAsStr); + Assert.Contains("JsonSerializerContext", exAsStr); + + context = (JsonSerializerContext)_contextCreator(new JsonSerializerOptions()); + ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); + exAsStr = ex.ToString(); + Assert.Contains("JsonSerializerOptions", exAsStr); + Assert.Contains("JsonSerializerContext", exAsStr); + } + + [Fact] + public virtual void ParameterizedConstructor() + { + string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); + Assert.Contains(@"""High"":1", json); + Assert.Contains(@"""Low"":2", json); + + // Deserialization not supported for now. + Assert.Throws(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable)); + } + + [Fact] + public virtual void EnumAndNullable() + { + RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); + RunTest(new ClassWithEnumAndNullable()); + + void RunTest(ClassWithEnumAndNullable expected) + { + string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); + ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, DefaultContext.ClassWithEnumAndNullable); + Assert.Equal(expected.Day, actual.Day); + Assert.Equal(expected.NullableDay, actual.NullableDay); + } + } + + public class ClassWithEnumAndNullable + { + public DayOfWeek Day { get; set; } + public DayOfWeek? NullableDay { get; set; } + } + + [Fact] + public void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent() + { + object[] objArr = new object[] { new MyStruct() }; + + // Metadata not generated for MyStruct without JsonSerializableAttribute. + NotSupportedException ex = Assert.Throws( + () => JsonSerializer.Serialize(objArr, DefaultContext.ObjectArray)); + string exAsStr = ex.ToString(); + Assert.Contains(typeof(MyStruct).ToString(), exAsStr); + Assert.Contains("JsonSerializerOptions", exAsStr); + + // This test uses reflection to: + // - Access JsonSerializerOptions.s_defaultSimpleConverters + // - Access JsonSerializerOptions.s_defaultFactoryConverters + // - Access JsonSerializerOptions._typeInfoCreationFunc + // + // If any of them changes, this test will need to be kept in sync. + + // Confirm built-in converters not set. + AssertFieldNull("s_defaultSimpleConverters", optionsInstance: null); + AssertFieldNull("s_defaultFactoryConverters", optionsInstance: null); + + // Confirm type info dynamic creator not set. + AssertFieldNull("_typeInfoCreationFunc", ((JsonSerializerContext)DefaultContext).Options); + + static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInstance) + { + BindingFlags bindingFlags = BindingFlags.NonPublic | (optionsInstance == null ? BindingFlags.Static : BindingFlags.Instance); + FieldInfo fieldInfo = typeof(JsonSerializerOptions).GetField(fieldName, bindingFlags); + Assert.NotNull(fieldInfo); + Assert.Null(fieldInfo.GetValue(optionsInstance)); + } + } + + private const string ExceptionMessageFromCustomContext = "Exception thrown from custom context."; + + [Fact] + public void GetTypeInfoCalledDuringPolymorphicSerialization() + { + CustomContext context = new(new JsonSerializerOptions()); + + // Empty array is fine since we don't need metadata for children. + Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), context.ObjectArray)); + Assert.Equal("[]", JsonSerializer.Serialize(Array.Empty(), typeof(object[]), context)); + + // GetTypeInfo method called to get metadata for element run-time type. + object[] objArr = new object[] { new MyStruct() }; + + InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, context.ObjectArray)); + Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); + + ex = Assert.Throws(() => JsonSerializer.Serialize(objArr, typeof(object[]), context)); + Assert.Contains(ExceptionMessageFromCustomContext, ex.ToString()); + } + + internal struct MyStruct { } + + internal class CustomContext : JsonSerializerContext + { + public CustomContext(JsonSerializerOptions options) : base(options, null) { } + + private JsonTypeInfo _object; + public JsonTypeInfo Object => _object ??= JsonMetadataServices.CreateValueInfo(Options, JsonMetadataServices.ObjectConverter); + + private JsonTypeInfo _objectArray; + public JsonTypeInfo ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo(Options, Object, default, serializeFunc: null); + + public override JsonTypeInfo GetTypeInfo(Type type) + { + if (type == typeof(object[])) + { + return ObjectArray; + } + + throw new InvalidOperationException(ExceptionMessageFromCustomContext); + } + } + + protected static void AssertFastPathLogicCorrect(string expectedJson, T value, JsonTypeInfo typeInfo) + { + using MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + typeInfo.Serialize!(writer, value); + writer.Flush(); + + JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray())); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs index a03b103ceebd21..45ce3f358df378 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs @@ -1,284 +1,284 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. - -//using System.Text.Json.Serialization; -//using Xunit; - -//namespace System.Text.Json.SourceGeneration.Tests -//{ -// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] -// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] -// internal partial class SerializationContext : JsonSerializerContext, ITestContext -// { -// } - -// [JsonSerializerOptions(NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)] -// [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] -// [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] -// [JsonSerializable(typeof(ContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] -// internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext -// { -// } - -// public sealed class SerializationContextTests : ContextTests -// { -// public SerializationContextTests() : base(SerializationContext.Default, (options) => new SerializationContext(options)) { } - -// [Fact] -// public override void EnsureFastPathGeneratedAsExpected() -// { -// Assert.NotNull(SerializationContext.Default.Location.Serialize); -// Assert.NotNull(SerializationContext.Default.RepeatedLocation.Serialize); -// Assert.NotNull(SerializationContext.Default.ActiveOrUpcomingEvent.Serialize); -// Assert.NotNull(SerializationContext.Default.CampaignSummaryViewModel.Serialize); -// Assert.NotNull(SerializationContext.Default.IndexViewModel.Serialize); -// Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); -// Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); -// Assert.NotNull(SerializationContext.Default.HighLowTemps.Serialize); -// Assert.NotNull(SerializationContext.Default.MyType.Serialize); -// Assert.NotNull(SerializationContext.Default.MyType2.Serialize); -// Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize); -// Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize); -// Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize); -// Assert.NotNull(SerializationContext.Default.MyNestedNestedClass.Serialize); -// Assert.Null(SerializationContext.Default.ObjectArray.Serialize); -// Assert.Null(SerializationContext.Default.String.Serialize); -// Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize); -// } - -// [Fact] -// public override void RoundTripLocation() -// { -// Location expected = CreateLocation(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.Location); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); - -// Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location); -// VerifyLocation(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.Location); -// } - -// [Fact] -// public override void RoundTripIndexViewModel() -// { -// IndexViewModel expected = CreateIndexViewModel(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); - -// IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); -// VerifyIndexViewModel(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.IndexViewModel); -// } - -// [Fact] -// public override void RoundTripCampaignSummaryViewModel() -// { -// CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); - -// CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); -// VerifyCampaignSummaryViewModel(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); -// } - -// [Fact] -// public override void RoundTripActiveOrUpcomingEvent() -// { -// ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); - -// ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent); -// VerifyActiveOrUpcomingEvent(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.ActiveOrUpcomingEvent); -// } - -// [Fact] -// public override void RoundTripCollectionsDictionary() -// { -// WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); - -// WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); -// VerifyWeatherForecastWithPOCOs(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.WeatherForecastWithPOCOs); -// } - -// [Fact] -// public override void RoundTripEmptyPoco() -// { -// EmptyPoco expected = CreateEmptyPoco(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); - -// EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); -// VerifyEmptyPoco(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); -// } - -// [Fact] -// public override void RoundTripTypeNameClash() -// { -// RepeatedTypes.Location expected = CreateRepeatedLocation(); - -// string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); - -// RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); -// VerifyRepeatedLocation(expected, obj); - -// AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); -// } - -// [Fact] -// public override void NestedSameTypeWorks() -// { -// MyType myType = new() { Type = new() }; -// string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); -// myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType); -// AssertFastPathLogicCorrect(json, myType, DefaultContext.MyType); - -// MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; -// json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); -// myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType2); -// AssertFastPathLogicCorrect(json, myType2, DefaultContext.MyType2); -// } - -// [Fact] -// public override void SerializeObjectArray() -// { -// IndexViewModel index = CreateIndexViewModel(); -// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - -// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); -// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - -// JsonElement indexAsJsonElement = (JsonElement)arr[0]; -// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; -// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); -// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); -// } - -// [Fact] -// public override void SerializeObjectArray_WithCustomOptions() -// { -// IndexViewModel index = CreateIndexViewModel(); -// CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); - -// ITestContext context = SerializationContextWithCamelCase.Default; -// Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); - -// string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); -// // Verify JSON was written with camel casing. -// Assert.Contains("activeOrUpcomingEvents", json); -// Assert.Contains("featuredCampaign", json); -// Assert.Contains("description", json); -// Assert.Contains("organizationName", json); - -// object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); - -// JsonElement indexAsJsonElement = (JsonElement)arr[0]; -// JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - -// ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); -// VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); -// VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); -// } - -// [Fact] -// public override void SerializeObjectArray_SimpleTypes_WithCustomOptions() -// { -// JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; -// ITestContext context = new SerializationContext(options); - -// string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); -// object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataContext.Default)); - -// JsonElement hello = (JsonElement)arr[0]; -// JsonElement world = (JsonElement)arr[1]; -// Assert.Equal("\"Hello\"", hello.GetRawText()); -// Assert.Equal("\"World\"", world.GetRawText()); -// } - -// [Fact] -// public override void HandlesNestedTypes() -// { -// string json = @"{""MyInt"":5}"; -// MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); -// Assert.Equal(5, obj.MyInt); -// Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - -// MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); -// Assert.Equal(5, obj2.MyInt); -// Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); -// } - -// [Fact] -// public override void EnumAndNullable() -// { -// RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); -// RunTest(new ClassWithEnumAndNullable()); - -// void RunTest(ClassWithEnumAndNullable expected) -// { -// string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); -// ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ClassWithEnumAndNullable); -// Assert.Equal(expected.Day, actual.Day); -// Assert.Equal(expected.NullableDay, actual.NullableDay); -// } -// } - -// [Fact] -// public override void ParameterizedConstructor() -// { -// string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); -// Assert.Contains(@"""High"":1", json); -// Assert.Contains(@"""Low"":2", json); - -// JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable)); -// } -// } -//} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using Xunit; + +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] + internal partial class SerializationContext : JsonSerializerContext, ITestContext + { + } + + [JsonSerializerOptions(NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)] + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] + internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext + { + } + + public sealed class SerializationContextTests : RealWorldContextTests + { + public SerializationContextTests() : base(SerializationContext.Default, (options) => new SerializationContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.NotNull(SerializationContext.Default.Location.Serialize); + Assert.NotNull(SerializationContext.Default.RepeatedLocation.Serialize); + Assert.NotNull(SerializationContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.NotNull(SerializationContext.Default.CampaignSummaryViewModel.Serialize); + Assert.NotNull(SerializationContext.Default.IndexViewModel.Serialize); + Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(SerializationContext.Default.HighLowTemps.Serialize); + Assert.NotNull(SerializationContext.Default.MyType.Serialize); + Assert.NotNull(SerializationContext.Default.MyType2.Serialize); + Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize); + Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize); + Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize); + Assert.NotNull(SerializationContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(SerializationContext.Default.ObjectArray.Serialize); + Assert.Null(SerializationContext.Default.String.Serialize); + Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize); + } + + [Fact] + public override void RoundTripLocation() + { + Location expected = CreateLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.Location); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); + + Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location); + VerifyLocation(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.Location); + } + + [Fact] + public override void RoundTripIndexViewModel() + { + IndexViewModel expected = CreateIndexViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); + + IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); + VerifyIndexViewModel(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.IndexViewModel); + } + + [Fact] + public override void RoundTripCampaignSummaryViewModel() + { + CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); + + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); + VerifyCampaignSummaryViewModel(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); + } + + [Fact] + public override void RoundTripActiveOrUpcomingEvent() + { + ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); + + ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent); + VerifyActiveOrUpcomingEvent(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.ActiveOrUpcomingEvent); + } + + [Fact] + public override void RoundTripCollectionsDictionary() + { + WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); + + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); + VerifyWeatherForecastWithPOCOs(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.WeatherForecastWithPOCOs); + } + + [Fact] + public override void RoundTripEmptyPoco() + { + EmptyPoco expected = CreateEmptyPoco(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); + + EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); + VerifyEmptyPoco(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); + } + + [Fact] + public override void RoundTripTypeNameClash() + { + RepeatedTypes.Location expected = CreateRepeatedLocation(); + + string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); + + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); + VerifyRepeatedLocation(expected, obj); + + AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); + } + + [Fact] + public override void NestedSameTypeWorks() + { + MyType myType = new() { Type = new() }; + string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); + myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType); + AssertFastPathLogicCorrect(json, myType, DefaultContext.MyType); + + MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; + json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); + myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType2); + AssertFastPathLogicCorrect(json, myType2, DefaultContext.MyType2); + } + + [Fact] + public override void SerializeObjectArray() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); + } + + [Fact] + public override void SerializeObjectArray_WithCustomOptions() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + ITestContext context = SerializationContextWithCamelCase.Default; + Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); + // Verify JSON was written with camel casing. + Assert.Contains("activeOrUpcomingEvents", json); + Assert.Contains("featuredCampaign", json); + Assert.Contains("description", json); + Assert.Contains("organizationName", json); + + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + + ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel)); + } + + [Fact] + public override void SerializeObjectArray_SimpleTypes_WithCustomOptions() + { + JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + ITestContext context = new SerializationContext(options); + + string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); + object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataContext.Default)); + + JsonElement hello = (JsonElement)arr[0]; + JsonElement world = (JsonElement)arr[1]; + Assert.Equal("\"Hello\"", hello.GetRawText()); + Assert.Equal("\"World\"", world.GetRawText()); + } + + [Fact] + public override void HandlesNestedTypes() + { + string json = @"{""MyInt"":5}"; + MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); + Assert.Equal(5, obj.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); + + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); + Assert.Equal(5, obj2.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); + } + + [Fact] + public override void EnumAndNullable() + { + RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday }); + RunTest(new ClassWithEnumAndNullable()); + + void RunTest(ClassWithEnumAndNullable expected) + { + string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); + ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ClassWithEnumAndNullable); + Assert.Equal(expected.Day, actual.Day); + Assert.Equal(expected.NullableDay, actual.NullableDay); + } + } + + [Fact] + public override void ParameterizedConstructor() + { + string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable); + Assert.Contains(@"""High"":1", json); + Assert.Contains(@"""Low"":2", json); + + JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable)); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs index c89c682d541090..39899a5b91e571 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs @@ -22,15 +22,14 @@ public JsonTypeInfo HighLowTemps } else { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); - _HighLowTemps = objectInfo; - - JsonMetadataServices.InitializeObjectInfo( - objectInfo, + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo( + Options, createObjectFunc: static () => new HighLowTemps(), HighLowTempsPropInitFunc, default, serializeFunc: null); + + _HighLowTemps = objectInfo; } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs index 601b99aeb3683e..f75200bf8b5508 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs @@ -22,15 +22,14 @@ public JsonTypeInfo WeatherForecastWithPOCOs } else { - JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo(Options); - _WeatherForecastWithPOCOs = objectInfo; - - JsonMetadataServices.InitializeObjectInfo( - objectInfo, + JsonTypeInfo objectInfo = JsonMetadataServices.CreateObjectInfo( + Options, createObjectFunc: static () => new WeatherForecastWithPOCOs(), WeatherForecastWithPOCOsPropInitFunc, default, serializeFunc: null); + + _WeatherForecastWithPOCOs = objectInfo; } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs index 28269894c1d2e3..227aab7bf3abbb 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs @@ -105,40 +105,35 @@ private class MyDerivedClass : MyClass { } public void CreateObjectInfo() { JsonSerializerOptions options = new(); - JsonTypeInfo info = JsonMetadataServices.CreateObjectInfo(options); // Null options - ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreateObjectInfo(null!)); - Assert.Contains("options", ane.ToString()); - - // Null info - ane = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( - info: null, + ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreateObjectInfo( + options: null, createObjectFunc: null, propInitFunc: (context) => Array.Empty(), numberHandling: default, serializeFunc: null)); - Assert.Contains("info", ane.ToString()); + Assert.Contains("options", ane.ToString()); // Null prop init func is fine if serialize func is provided. - JsonMetadataServices.InitializeObjectInfo( - info, + JsonMetadataServices.CreateObjectInfo( + options, createObjectFunc: null, propInitFunc: null, numberHandling: default, serializeFunc: (writer, obj) => { }); // Null serialize func is fine if prop init func is provided. - JsonMetadataServices.InitializeObjectInfo( - info, + JsonMetadataServices.CreateObjectInfo( + options, createObjectFunc: null, propInitFunc: (context) => Array.Empty(), numberHandling: default, serializeFunc: null); // Null prop init func and serialize func - InvalidOperationException ioe = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( - info, + InvalidOperationException ioe = Assert.Throws(() => JsonMetadataServices.CreateObjectInfo( + options, createObjectFunc: null, propInitFunc: null, numberHandling: default, From f937f0ce12d3fcc00dfde581d360dccbc0fb5b9e Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Wed, 26 May 2021 21:00:22 -0700 Subject: [PATCH 8/8] Fix formatting --- .../gen/JsonSourceGenerator.Emitter.cs | 88 ++++++++++--------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 95b3beb24abdb9..251aa2a18f8d6d 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -127,12 +127,16 @@ namespace {_currentContext.ContextType.Namespace} string declarationSource = $@" {declarationList[declarationCount - 1 - i]} {{"; - sb.Append(IndentSource(declarationSource, numIndentations: i + 1)); + sb.Append($@" +{IndentSource(declarationSource, numIndentations: i + 1)} +"); } // Add the core implementation for the derived context class. - string partialContextImplementation = $@"{generatedCodeAttributeSource}{declarationList[0]} -{{{IndentSource(source, Math.Max(1, declarationCount - 1))} + string partialContextImplementation = $@" +{generatedCodeAttributeSource}{declarationList[0]} +{{ + {IndentSource(source, Math.Max(1, declarationCount - 1))} }}"; sb.AppendLine(IndentSource(partialContextImplementation, numIndentations: declarationCount)); @@ -419,12 +423,12 @@ private string GenerateFastPathFuncForEnumerable(string typeInfoRef, string seri string serializationLogic = $@"{WriterVarName}.WriteStartArray(); - for (int i = 0; i < {ValueVarName}.{lengthPropName}; i++) - {{ - {elementSerializationLogic} - }} + for (int i = 0; i < {ValueVarName}.{lengthPropName}; i++) + {{ + {elementSerializationLogic} + }} - {WriterVarName}.WriteEndArray();"; + {WriterVarName}.WriteEndArray();"; return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, canBeNull); } @@ -455,12 +459,12 @@ private string GenerateFastPathFuncForDictionary( string serializationLogic = $@"{WriterVarName}.WriteStartObject(); - foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName}) - {{ - {elementSerializationLogic} - }} + foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName}) + {{ + {elementSerializationLogic} + }} - {WriterVarName}.WriteEndObject();"; + {WriterVarName}.WriteEndObject();"; return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, canBeNull); } @@ -544,12 +548,12 @@ private string GeneratePropMetadataInitFunc( sb.Append($@" - private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerContextTypeRef} context) - {{ - {contextTypeRef} {JsonContextVarName} = ({contextTypeRef})context; - {JsonSerializerOptionsTypeRef} options = context.Options; +private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerContextTypeRef} context) +{{ + {contextTypeRef} {JsonContextVarName} = ({contextTypeRef})context; + {JsonSerializerOptionsTypeRef} options = context.Options; - {JsonPropertyInfoTypeRef}[] {PropVarName} = {propertyArrayInstantiationValue}; + {JsonPropertyInfoTypeRef}[] {PropVarName} = {propertyArrayInstantiationValue}; "); if (properties != null) @@ -604,25 +608,25 @@ private string GeneratePropMetadataInitFunc( string memberTypeCompilableName = memberTypeMetadata.TypeRef; sb.Append($@" - {PropVarName}[{i}] = {JsonMetadataServicesTypeRef}.CreatePropertyInfo<{memberTypeCompilableName}>( - options, - isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()}, - declaringType: typeof({memberMetadata.DeclaringTypeRef}), - {typeTypeInfoNamedArg}, - {converterNamedArg}, - {getterNamedArg}, - {setterNamedArg}, - {ignoreConditionNamedArg}, - numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, - propertyName: ""{clrPropertyName}"", - {jsonPropertyNameNamedArg}); - "); + {PropVarName}[{i}] = {JsonMetadataServicesTypeRef}.CreatePropertyInfo<{memberTypeCompilableName}>( + options, + isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()}, + declaringType: typeof({memberMetadata.DeclaringTypeRef}), + {typeTypeInfoNamedArg}, + {converterNamedArg}, + {getterNamedArg}, + {setterNamedArg}, + {ignoreConditionNamedArg}, + numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, + propertyName: ""{clrPropertyName}"", + {jsonPropertyNameNamedArg}); + "); } } sb.Append(@$" - return {PropVarName}; - }}"); + return {PropVarName}; +}}"); return sb.ToString(); } @@ -767,21 +771,21 @@ private string GenerateFastPathFuncForType(string serializeMethodName, string ty { return $@" - private static void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {typeInfoTypeRef} {ValueVarName}) - {{ - {GetEarlyNullCheckSource(canBeNull)} - {serializationLogic} - }}"; +private static void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {typeInfoTypeRef} {ValueVarName}) +{{ + {GetEarlyNullCheckSource(canBeNull)} + {serializationLogic} +}}"; } private string GetEarlyNullCheckSource(bool canBeNull) { return canBeNull ? $@"if ({ValueVarName} == null) - {{ - {WriterVarName}.WriteNullValue(); - return; - }} + {{ + {WriterVarName}.WriteNullValue(); + return; + }} " : null; }