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)
{
}
}
else
{
- JsonTypeInfo<Person> objectInfo = JsonMetadataServices.CreateObjectInfo<Person>();
- _Person = objectInfo;
-
- JsonMetadataServices.InitializeObjectInfo(
- objectInfo,
+ JsonTypeInfo<Person> objectInfo = JsonMetadataServices.CreateObjectInfo<Person>(
Options,
createObjectFunc: static () => new Person(),
PersonPropInitFunc,
- default);
+ default,
+ serializeFunc: null);
+
+ _Person = objectInfo;
}
}
/// <summary>
/// The base class of serialization attributes.
/// </summary>
- public abstract class JsonAttribute : Attribute { }
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ abstract class JsonAttribute : Attribute { }
}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// The <see cref="Json.JsonNamingPolicy"/> to be used at run-time.
+ /// </summary>
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ enum JsonKnownNamingPolicy
+ {
+ /// <summary>
+ /// Specifies that JSON property names should not be converted.
+ /// </summary>
+ Unspecified = 0,
+
+ /// <summary>
+ /// Specifies that the built-in <see cref="Json.JsonNamingPolicy.CamelCase"/> be used to convert JSON property names.
+ /// </summary>
+ BuiltInCamelCase = 1
+ }
+}
/// <summary>
/// Determines the naming policy used to convert a string-based name to another format, such as a camel-casing format.
/// </summary>
- public abstract class JsonNamingPolicy
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ abstract class JsonNamingPolicy
{
/// <summary>
/// Initializes a new instance of <see cref="JsonNamingPolicy"/>.
/// </summary>
public static JsonNamingPolicy CamelCase { get; } = new JsonCamelCaseNamingPolicy();
- internal static JsonNamingPolicy Default { get; } = new JsonDefaultNamingPolicy();
-
/// <summary>
/// When overridden in a derived class, converts the specified name according to the policy.
/// </summary>
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Instructs the System.Text.Json source generator to assume the specified
+ /// options will be used at run-time via <see cref="JsonSerializerOptions"/>.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ class JsonSerializerOptionsAttribute : JsonAttribute
+ {
+ /// <summary>
+ /// Specifies the default ignore condition.
+ /// </summary>
+ public JsonIgnoreCondition DefaultIgnoreCondition { get; set; }
+
+ /// <summary>
+ /// Specifies whether to ignore read-only fields.
+ /// </summary>
+ public bool IgnoreReadOnlyFields { get; set; }
+
+ /// <summary>
+ /// Specifies whether to ignore read-only properties.
+ /// </summary>
+ public bool IgnoreReadOnlyProperties { get; set; }
+
+ /// <summary>
+ /// Specifies whether to ignore custom converters provided at run-time.
+ /// </summary>
+ public bool IgnoreRuntimeCustomConverters { get; set; }
+
+ /// <summary>
+ /// Specifies whether to include fields for serialization and deserialization.
+ /// </summary>
+ public bool IncludeFields { get; set; }
+
+ /// <summary>
+ /// Specifies a built-in naming polices to convert JSON property names with.
+ /// </summary>
+ public JsonKnownNamingPolicy NamingPolicy { get; set; }
+
+ /// <summary>
+ /// Specifies whether JSON output should be pretty-printed.
+ /// </summary>
+ public bool WriteIndented { get; set; }
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// The generation mode for the System.Text.Json source generator.
+ /// </summary>
+ [Flags]
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ enum JsonSourceGenerationMode
+ {
+ /// <summary>
+ /// 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 <see cref="JsonSerializerOptionsAttribute"/>.
+ /// </summary>
+ /// <remarks>
+ /// This mode supports all <see cref="JsonSerializer"/> features.
+ /// </remarks>
+ MetadataAndSerialization = 0,
+
+ /// <summary>
+ /// Instructs the JSON source generator to generate type-metadata initialization logic.
+ /// </summary>
+ /// <remarks>
+ /// This mode supports all <see cref="JsonSerializer"/> features.
+ /// </remarks>
+ Metadata = 1,
+
+ /// <summary>
+ /// Instructs the JSON source generator to generate serialization logic.
+ /// </summary>
+ /// <remarks>
+ /// This mode supports only a subset of <see cref="JsonSerializer"/> features.
+ /// </remarks>
+ Serialization = 2
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using System.Text.Json.SourceGeneration.Reflection;
+
+namespace System.Text.Json.SourceGeneration
+{
+ /// <summary>
+ /// Represents the set of input types and options needed to provide an
+ /// implementation for a user-provided JsonSerializerContext-derived type.
+ /// </summary>
+ internal sealed class ContextGenerationSpec
+ {
+ public JsonSerializerOptionsAttribute SerializerOptions { get; init; }
+
+ public Type ContextType { get; init; }
+
+ public List<TypeGenerationSpec>? RootSerializableTypes { get; init; }
+
+ public List<string> ContextClassDeclarationList { get; init; }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public HashSet<TypeGenerationSpec> TypesWithMetadataGenerated { get; } = new();
+
+ /// <summary>
+ /// Cache of runtime property names (statically determined) found accross the object graph of the JsonSerializerContext.
+ /// </summary>
+ public HashSet<string> RuntimePropertyNames { get; } = new();
+
+ public string ContextTypeRef => $"global::{ContextType.GetUniqueCompilableTypeName()}";
+ }
+}
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;
{
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 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}"")]
+";
- private const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration";
+ // global::fully.qualified.name for referenced types
+ 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";
private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor(
id: "SYSLIB1030",
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;
- /// <summary>
- /// 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.
- /// </summary>
- private readonly HashSet<TypeMetadata> _typesWithMetadataGenerated = new();
+ private ContextGenerationSpec _currentContext = null!;
- /// <summary>
- /// Types that were specified with System.Text.Json.Serialization.JsonSerializableAttribute.
- /// </summary>
- private readonly Dictionary<string, TypeMetadata> _rootSerializableTypes = null!;
+ private readonly SourceGenerationSpec _generationSpec = null!;
- public Emitter(in GeneratorExecutionContext executionContext, Dictionary<string, TypeMetadata> rootSerializableTypes)
+ public Emitter(in GeneratorExecutionContext executionContext, SourceGenerationSpec generationSpec)
{
_executionContext = executionContext;
- _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration";
- _rootSerializableTypes = rootSerializableTypes;
+ _generationSpec = generationSpec;
}
public void Emit()
{
- foreach (KeyValuePair<string, TypeMetadata> 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;
+
+ List<string> declarationList = _currentContext.ContextClassDeclarationList;
+ int declarationCount = declarationList.Count;
+ Debug.Assert(declarationCount >= 1);
+
+ StringBuilder sb = new();
- // Add base default instance source.
- _executionContext.AddSource("JsonContext.g.cs", SourceText.From(GetBaseJsonContextImplementation(), Encoding.UTF8));
+ sb.Append($@"// <auto-generated/>
- // Add GetJsonTypeInfo override implementation.
- _executionContext.AddSource("JsonContext.GetJsonTypeInfo.g.cs", SourceText.From(GetGetTypeInfoImplementation(), Encoding.UTF8));
+namespace {_currentContext.ContextType.Namespace}
+{{");
+
+ for (int i = 0; i < declarationCount - 1; i++)
+ {
+ string declarationSource = $@"
+{declarationList[declarationCount - 1 - i]}
+{{";
+ 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))}
+}}";
+ 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 GenerateTypeMetadata(TypeMetadata typeMetadata)
+ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec)
{
- Debug.Assert(typeMetadata != null);
+ Debug.Assert(typeGenerationSpec != null);
+
+ HashSet<TypeGenerationSpec> 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);
}
}
}
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:
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);
+ {TypeTypeRef} 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";
}
}
// 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 GenerateFastPathFuncForEnumerable(string typeInfoRef, string serializeMethodName, bool canBeNull, bool isArray, TypeGenerationSpec valueTypeGenerationSpec)
+ {
+ string? writerMethodToCall = GetWriterMethod(valueTypeGenerationSpec.Type);
+ string valueToWrite = $"{ValueVarName}[i]";
+ string lengthPropName = isArray ? "Length" : "Count";
+
+ string elementSerializationLogic;
+ if (writerMethodToCall != null)
+ {
+ elementSerializationLogic = $"{writerMethodToCall}Value({valueToWrite});";
+ }
+ else
+ {
+ elementSerializationLogic = GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic);
+ }
+
+ string serializationLogic = $@"{WriterVarName}.WriteStartArray();
+
+ for (int i = 0; i < {ValueVarName}.{lengthPropName}; i++)
+ {{
+ {elementSerializationLogic}
+ }}
+
+ {WriterVarName}.WriteEndArray();";
+
+ return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, canBeNull);
}
- private string GenerateForObject(TypeMetadata typeMetadata)
+ private string GenerateFastPathFuncForDictionary(
+ string typeInfoRef,
+ string serializeMethodName,
+ bool canBeNull,
+ TypeGenerationSpec keyTypeGenerationSpec,
+ TypeGenerationSpec valueTypeGenerationSpec)
{
- string typeCompilableName = typeMetadata.CompilableName;
- string typeFriendlyName = typeMetadata.FriendlyName;
+ const string pairVarName = "pair";
+ string keyToWrite = $"{pairVarName}.Key";
+ string valueToWrite = $"{pairVarName}.Value";
+
+ string? writerMethodToCall = GetWriterMethod(valueTypeGenerationSpec.Type);
+ string elementSerializationLogic;
+
+ if (writerMethodToCall != null)
+ {
+ elementSerializationLogic = $"{writerMethodToCall}({keyToWrite}, {valueToWrite});";
+ }
+ else
+ {
+ elementSerializationLogic = $@"{WriterVarName}.WritePropertyName({keyToWrite});
+ {GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic)}";
+ }
+
+ string serializationLogic = $@"{WriterVarName}.WriteStartObject();
+
+ foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName})
+ {{
+ {elementSerializationLogic}
+ }}
+
+ {WriterVarName}.WriteEndObject();";
+
+ return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, 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<PropertyMetadata>? properties = typeMetadata.PropertiesMetadata;
+ string propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}";
+ string? propMetadataInitFuncSource = null;
+ string propMetadataInitFuncNamedArg;
- StringBuilder sb = new();
+ string serializeMethodName = $"{typeFriendlyName}{SerializeMethodNameSuffix}";
+ string? serializeFuncSource = null;
+ string serializeFuncNamedArg;
- sb.Append($@"JsonTypeInfo<{typeCompilableName}> objectInfo = {JsonMetadataServicesClassName}.CreateObjectInfo<{typeCompilableName}>();
- _{typeFriendlyName} = objectInfo;
-");
+ List<PropertyGenerationSpec>? properties = typeMetadata.PropertiesMetadata;
- string propInitFuncVarName = $"{typeFriendlyName}{PropInitFuncVarName}";
+ if (typeMetadata.GenerateMetadata)
+ {
+ propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, properties);
+ propMetadataInitFuncNamedArg = $@"propInitFunc: {propInitMethodName}";
+ }
+ else
+ {
+ propMetadataInitFuncNamedArg = @"propInitFunc: null";
+ }
- sb.Append($@"
- {JsonMetadataServicesClassName}.InitializeObjectInfo(
- objectInfo,
- {OptionsInstanceVariableName},
- {createObjectFuncTypeArg},
- {propInitFuncVarName},
- {GetNumberHandlingAsStr(typeMetadata.NumberHandling)});");
+ if (typeMetadata.GenerateSerializationLogic)
+ {
+ serializeFuncSource = GenerateFastPathFuncForObject(typeCompilableName, serializeMethodName, typeMetadata.CanBeNull, properties);
+ serializeFuncNamedArg = $@"serializeFunc: {serializeMethodName}";
+ }
+ else
+ {
+ serializeFuncNamedArg = @"serializeFunc: null";
+ }
+
+ string objectInfoInitSource = $@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>(
+ {OptionsInstanceVariableName},
+ {createObjectFuncTypeArg},
+ {propMetadataInitFuncNamedArg},
+ {GetNumberHandlingAsStr(typeMetadata.NumberHandling)},
+ {serializeFuncNamedArg});
- string metadataInitSource = sb.ToString();
- string? propInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitFuncVarName, properties);
+ _{typeFriendlyName} = objectInfo;";
- return GenerateForType(typeMetadata, metadataInitSource, propInitFuncSource);
+ string additionalSource;
+ if (propMetadataInitFuncSource == null || serializeFuncSource == null)
+ {
+ additionalSource = propMetadataInitFuncSource ?? serializeFuncSource;
+ }
+ else
+ {
+ additionalSource = @$"{propMetadataInitFuncSource}{serializeFuncSource}";
+ }
+
+ return GenerateForType(typeMetadata, objectInfoInitSource, additionalSource);
}
private string GeneratePropMetadataInitFunc(
bool declaringTypeIsValueType,
- string propInitFuncVarName,
- List<PropertyMetadata>? properties)
+ string propInitMethodName,
+ List<PropertyGenerationSpec>? 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}";
? @$"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}";
setterNamedArg = "setter: null";
}
- JsonIgnoreCondition? ignoreCondition = memberMetadata.IgnoreCondition;
+ JsonIgnoreCondition? ignoreCondition = memberMetadata.DefaultIgnoreCondition;
string ignoreConditionNamedArg = ignoreCondition.HasValue
? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}"
: "ignoreCondition: default";
? "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<PropertyGenerationSpec>? properties)
+ {
+ 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 = GetWriterMethod(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? GetWriterMethod(Type type)
+ {
+ string? method;
+ if (_generationSpec.IsStringBasedType(type))
+ {
+ method = $"{WriterVarName}.WriteString";
+ }
+ else if (type == _generationSpec.BooleanType)
+ {
+ method = $"{WriterVarName}.WriteBoolean";
+ }
+ else if (type == _generationSpec.ByteArrayType)
+ {
+ method = $"{WriterVarName}.WriteBase64String";
+ }
+ else if (type == _generationSpec.CharType)
+ {
+ method = $"{WriterVarName}.WriteString";
+ }
+ else if (_generationSpec.IsNumberType(type))
+ {
+ method = $"{WriterVarName}.WriteNumber";
+ }
+ else
+ {
+ method = null;
+ }
+
+ return method;
+ }
+
+ 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
+ {
+ None,
+ Null,
+ Default,
+ }
+
+ private string WrapSerializationLogicInDefaultCheckIfRequired(string serializationLogic, string propValue, DefaultCheckType defaultCheckType)
+ {
+ if (defaultCheckType == DefaultCheckType.None)
+ {
+ return serializationLogic;
+ }
+
+ string defaultLiteral = defaultCheckType == DefaultCheckType.Null ? "null" : "default";
+ return $@"
+ if ({propValue} != {defaultLiteral})
+ {{{serializationLogic}
+ }}";
+ }
+
+ private string[] GetRuntimePropNames(List<PropertyGenerationSpec>? properties, JsonKnownNamingPolicy namingPolicy)
+ {
+ if (properties == null)
+ {
+ return Array.Empty<string>();
+ }
+
+ 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 typeCompilableName = typeMetadata.CompilableName;
- string typeFriendlyName = typeMetadata.FriendlyName;
+ string runtimePropName;
- return @$"{GetUsingStatementsString(typeMetadata)}
+ 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}>";
-namespace {_generationNamespace}
+ return @$"private {typeInfoPropertyTypeRef} _{typeFriendlyName};
+public {typeInfoPropertyTypeRef} {typeFriendlyName}
{{
- {JsonContextDeclarationSource}
+ get
{{
- private JsonTypeInfo<{typeCompilableName}> _{typeFriendlyName};
- public JsonTypeInfo<{typeCompilableName}> {typeFriendlyName}
+ if (_{typeFriendlyName} == null)
{{
- get
- {{
- if (_{typeFriendlyName} == null)
- {{
- {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))}
- }}
+ {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))}
+ }}
- return _{typeFriendlyName};
- }}
- }}{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}
-{{
- {JsonContextDeclarationSource}
- {{
- private static JsonContext s_default;
- public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions());
+ sb.Append(@$"{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 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 (!_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)
- {{
- System.Collections.Generic.IList<JsonConverter> converters = {OptionsInstanceVariableName}.Converters;
+ return @$"private {JsonConverterTypeRef} {RuntimeCustomConverterFetchingMethodName}({TypeTypeRef} type)
+{{
+ {IListTypeRef}<{JsonConverterTypeRef}> converters = {OptionsInstanceVariableName}.Converters;
- for (int i = 0; i < converters.Count; i++)
- {{
- JsonConverter converter = converters[i];
+ for (int i = 0; i < converters.Count; i++)
+ {{
+ {JsonConverterTypeRef} converter = converters[i];
- if (converter.CanConvert(type))
+ if (converter.CanConvert(type))
+ {{
+ if (converter is {JsonConverterFactoryTypeRef} factory)
+ {{
+ converter = factory.CreateConverter(type, {OptionsInstanceVariableName});
+ if (converter == null || converter is {JsonConverterFactoryTypeRef})
{{
- if (converter is JsonConverterFactory factory)
- {{
- 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."");
- }}
- }}
-
- return converter;
+ throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance."");
}}
}}
- return null;
- }}";
+ return converter;
+ }}
+ }}
+
+ return null;
+}}";
}
private string GetGetTypeInfoImplementation()
{
StringBuilder sb = new();
- HashSet<string> usingStatements = new();
-
- foreach (TypeMetadata metadata in _rootSerializableTypes.Values)
- {
- usingStatements.UnionWith(GetUsingStatements(metadata));
- }
-
- sb.Append(@$"{GetUsingStatementsString(usingStatements)}
-
-namespace {_generationNamespace}
-{{
- {JsonContextDeclarationSource}
- {{
- public override JsonTypeInfo GetTypeInfo(System.Type 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 (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!;
+}");
return sb.ToString();
}
- private static string GetUsingStatementsString(TypeMetadata typeMetadata)
+ private string GetPropertyNameInitialization()
{
- HashSet<string> 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<string> usingStatements)
- {
- string[] usingsArr = usingStatements.ToArray();
- Array.Sort(usingsArr);
- return string.Join("\n", usingsArr);
- }
-
- private static HashSet<string> GetUsingStatements(TypeMetadata typeMetadata)
- {
- HashSet<string> 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"));
-
- // Add imports to root type.
- usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace));
-
- switch (typeMetadata.ClassType)
- {
- 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;
- }
+ StringBuilder sb = new();
- void AddUsingStatementsForType(TypeMetadata typeMetadata)
+ foreach (string propName in _currentContext.RuntimePropertyNames)
{
- usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace));
-
- if (typeMetadata.CollectionKeyTypeMetadata != null)
- {
- Debug.Assert(typeMetadata.CollectionValueTypeMetadata != null);
- usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionKeyTypeMetadata.Type.Namespace));
- }
-
- if (typeMetadata.CollectionValueTypeMetadata != null)
- {
- usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionValueTypeMetadata.Type.Namespace));
- }
+ sb.Append($@"
+private static {JsonEncodedTextTypeRef} {propName}PropName = {JsonEncodedTextTypeRef}.Encode(""{propName}"");");
}
- return usingStatements;
+ return sb.ToString();
}
- private static string FormatAsUsingStatement(string @namespace) => $"using {@namespace};";
+ private static string IndentSource(string source, int numIndentations)
+ {
+ Debug.Assert(numIndentations >= 1);
+ return source.Replace(Environment.NewLine, $"{Environment.NewLine}{new string(' ', 4 * numIndentations)}"); // 4 spaces per indentation.
+ }
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}>";
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
{
private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";
- private readonly Compilation _compilation;
+ private readonly GeneratorExecutionContext _executionContext;
private readonly MetadataLoadContextInternal _metadataLoadContext;
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;
/// <summary>
/// Type information for member types in input object graphs.
/// </summary>
- private readonly Dictionary<Type, TypeMetadata> _typeMetadataCache = new();
+ private readonly Dictionary<Type, TypeGenerationSpec> _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<>));
_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));
PopulateKnownTypes();
}
- public Dictionary<string, TypeMetadata>? GetRootSerializableTypes(List<CompilationUnitSyntax> compilationUnits)
+ public SourceGenerationSpec? GetGenerationSpec(List<ClassDeclarationSyntax> classDeclarationSyntaxList)
{
- TypeExtensions.NullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>));
+ 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");
- 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<string, TypeMetadata>? rootTypes = null;
+ List<ContextGenerationSpec>? contextGenSpecList = null;
- foreach (CompilationUnitSyntax compilationUnit in compilationUnits)
+ foreach (ClassDeclarationSyntax classDeclarationSyntax in classDeclarationSyntaxList)
{
- SemanticModel compilationSemanticModel = _compilation.GetSemanticModel(compilationUnit.SyntaxTree);
+ CompilationUnitSyntax compilationUnitSyntax = classDeclarationSyntax.FirstAncestorOrSelf<CompilationUnitSyntax>();
+ SemanticModel compilationSemanticModel = compilation.GetSemanticModel(compilationUnitSyntax.SyntaxTree);
+
+ if (!DerivesFromJsonSerializerContext(classDeclarationSyntax, jsonSerializerContextSymbol, compilationSemanticModel))
+ {
+ continue;
+ }
+
+ List<TypeGenerationSpec>? rootTypes = null;
+ JsonSerializerOptionsAttribute? options = null;
- foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists)
+ 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<SyntaxNode> attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax);
-
- ITypeSymbol? typeSymbol = null;
- string? typeInfoPropertyName = null;
+ INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
- 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<TypeGenerationSpec>()).Add(metadata);
}
- else if (i == 1)
+ }
+ else if (jsonSerializerOptionsAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default))
+ {
+ options = GetSerializerOptions(attributeSyntax);
+ }
+ }
+
+ if (rootTypes == null)
+ {
+ // No types were indicated with [JsonSerializable]
+ continue;
+ }
+
+ INamedTypeSymbol contextTypeSymbol = (INamedTypeSymbol)compilationSemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
+ Debug.Assert(contextTypeSymbol != null);
+
+ if (!TryGetClassDeclarationList(contextTypeSymbol, out List<string> 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<ContextGenerationSpec>();
+ 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.
+ _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 directly from JsonSerializerContext.
+ private bool DerivesFromJsonSerializerContext(
+ ClassDeclarationSyntax classDeclarationSyntax,
+ INamedTypeSymbol jsonSerializerContextSymbol,
+ SemanticModel compilationSemanticModel)
+ {
+ SeparatedSyntaxList<BaseTypeSyntax>? 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;
+ }
+ }
+
+ return match != null;
+ }
+
+ private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhenAttribute(true)] out List<string> classDeclarationList)
+ {
+ classDeclarationList = new();
+
+ INamedTypeSymbol currentSymbol = typeSymbol;
+
+ while (currentSymbol != null)
+ {
+ 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))
{
- // Obtain the optional TypeInfoPropertyName string property on the attribute, if present.
- SyntaxNode? typeInfoPropertyNameNode = node.ChildNodes().ElementAtOrDefault(1);
- if (typeInfoPropertyNameNode != null)
- {
- typeInfoPropertyName = typeInfoPropertyNameNode.GetFirstToken().ValueText;
- }
+ isPartial = true;
}
+ }
- i++;
+ if (!isPartial)
+ {
+ classDeclarationList = null;
+ return false;
}
- if (typeSymbol == null)
+ declarationElements[tokenCount] = "class";
+ declarationElements[tokenCount + 1] = currentSymbol.Name;
+
+ classDeclarationList.Add(string.Join(" ", declarationElements));
+ }
+
+ currentSymbol = currentSymbol.ContainingType;
+ }
+
+ Debug.Assert(classDeclarationList.Count > 0);
+ return true;
+ }
+
+ private TypeGenerationSpec? GetRootSerializableType(SemanticModel compilationSemanticModel, AttributeSyntax attributeSyntax)
+ {
+ IEnumerable<SyntaxNode> 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<SyntaxNode> childNodes = node.ChildNodes();
- Type type = new TypeWrapper(typeSymbol, _metadataLoadContext);
- if (type.Namespace == "<global namespace>")
+ NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax;
+ Debug.Assert(propertyNameNode != null);
+
+ SyntaxNode? propertyValueMode = childNodes.ElementAtOrDefault(1);
+ if (propertyNameNode.Name.Identifier.ValueText == "TypeInfoPropertyName")
+ {
+ typeInfoPropertyName = propertyValueMode.GetFirstToken().ValueText;
+ }
+ else
{
- // 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;
+ Debug.Assert(propertyNameNode.Name.Identifier.ValueText == "GenerationMode");
+ generationMode = (JsonSourceGenerationMode)Enum.Parse(typeof(JsonSourceGenerationMode), propertyValueMode.GetLastToken().ValueText);
}
+ }
+ }
- rootTypes ??= new Dictionary<string, TypeMetadata>();
- rootTypes[type.FullName] = GetOrAddTypeMetadata(type, typeInfoPropertyName);
+ if (typeSymbol == null)
+ {
+ return null;
+ }
+
+ Type type = typeSymbol.AsType(_metadataLoadContext);
+ if (type.Namespace == "<global 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<SyntaxNode> attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax);
+
+ JsonSerializerOptionsAttribute options = new();
+
+ foreach (AttributeArgumentSyntax node in attributeArguments)
+ {
+ IEnumerable<SyntaxNode> 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<PropertyMetadata>? propertiesMetadata = null;
+ List<PropertyGenerationSpec>? propertiesMetadata = null;
CollectionType collectionType = CollectionType.NotApplicable;
ObjectConstructionStrategy constructionStrategy = default;
JsonNumberHandling? numberHandling = null;
- bool containsOnlyPrimitives = true;
bool foundDesignTimeCustomConverter = false;
string? converterInstatiationLogic = null;
{
classType = ClassType.KnownType;
}
- else if (type.IsNullableValueType(out nullableUnderlyingType))
+ else if (type.IsNullableValueType(_nullableOfTType, out nullableUnderlyingType))
{
Debug.Assert(nullableUnderlyingType != null);
classType = ClassType.Nullable;
foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
{
- PropertyMetadata metadata = GetPropertyMetadata(propertyInfo);
+ PropertyGenerationSpec metadata = GetPropertyGenerationSpec(propertyInfo);
// Ignore indexers.
if (propertyInfo.GetIndexParameters().Length > 0)
continue;
}
- 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);
}
}
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<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo);
}
Type memberCLRType;
- bool hasGetter;
- bool hasSetter;
+ bool isReadOnly;
+ bool canUseGetter;
+ bool canUseSetter;
bool getterIsVirtual = false;
bool setterIsVirtual = false;
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;
}
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
};
}
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)
{
_knownTypes.Add(_metadataLoadContext.Resolve(typeof(Version)));
}
-
- private bool IsPrimitive(Type type)
- => _knownTypes.Contains(type) && type != _uriType && type != _versionType;
}
}
}
public sealed partial class JsonSourceGenerator : ISourceGenerator
{
/// <summary>
- /// Helper for unit tests.
- /// </summary>
- public Dictionary<string, Type>? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Key, p => p.Value.Type);
- private Dictionary<string, TypeMetadata>? _rootTypes;
-
- /// <summary>
/// Registers a syntax resolver to receive compilation units.
/// </summary>
/// <param name="context"></param>
/// <param name="executionContext"></param>
public void Execute(GeneratorExecutionContext executionContext)
{
+ //if (!Diagnostics.Debugger.IsAttached) { Diagnostics.Debugger.Launch(); };
SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver;
- List<CompilationUnitSyntax> compilationUnits = receiver.CompilationUnits;
- if (compilationUnits == null)
+ List<ClassDeclarationSyntax>? contextClasses = receiver.ClassDeclarationSyntaxList;
+ if (contextClasses == null)
{
return;
}
- Parser parser = new(executionContext.Compilation);
- _rootTypes = parser.GetRootSerializableTypes(receiver.CompilationUnits);
-
- if (_rootTypes != null)
+ Parser parser = new(executionContext);
+ 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 const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration";
+
+ /// <summary>
+ /// Helper for unit tests.
+ /// </summary>
+ public Dictionary<string, Type>? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type);
+ private List<TypeGenerationSpec>? _rootTypes;
+
+ private sealed class SyntaxReceiver : ISyntaxReceiver
{
- public List<CompilationUnitSyntax>? CompilationUnits { get; private set; }
+ public List<ClassDeclarationSyntax>? ClassDeclarationSyntaxList { get; private set; }
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
- if (syntaxNode is CompilationUnitSyntax compilationUnit)
+ if (syntaxNode is ClassDeclarationSyntax cds)
{
- (CompilationUnits ??= new List<CompilationUnitSyntax>()).Add(compilationUnit);
+ (ClassDeclarationSyntaxList ??= new List<ClassDeclarationSyntax>()).Add(cds);
}
}
}
namespace System.Text.Json.SourceGeneration
{
[DebuggerDisplay("Name={Name}, Type={TypeMetadata}")]
- internal class PropertyMetadata
+ internal sealed class PropertyGenerationSpec
{
/// <summary>
/// The CLR name of the property.
public string? JsonPropertyName { get; init; }
/// <summary>
+ /// Whether the property has a set method.
+ /// </summary>
+ public bool IsReadOnly { get; init; }
+
+ /// <summary>
/// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified)
/// getter that can be referenced in generated source code.
/// </summary>
- public bool HasGetter { get; init; }
+ public bool CanUseGetter { get; init; }
/// <summary>
/// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified)
/// setter that can be referenced in generated source code.
/// </summary>
- public bool HasSetter { get; init; }
+ public bool CanUseSetter { get; init; }
public bool GetterIsVirtual { get; init; }
/// <summary>
/// The <see cref="JsonIgnoreCondition"/> for the property.
/// </summary>
- public JsonIgnoreCondition? IgnoreCondition { get; init; }
+ public JsonIgnoreCondition? DefaultIgnoreCondition { get; init; }
/// <summary>
/// The <see cref="JsonNumberHandling"/> for the property.
public bool HasJsonInclude { get; init; }
/// <summary>
- /// Metadata for the property's type.
+ /// Generation specification for the property's type.
/// </summary>
- public TypeMetadata TypeMetadata { get; init; }
+ public TypeGenerationSpec TypeGenerationSpec { get; init; }
/// <summary>
/// Compilable name of the property's declaring type.
/// </summary>
- public string DeclaringTypeCompilableName { get; init; }
+ public string DeclaringTypeRef { get; init; }
/// <summary>
/// Source code to instantiate design-time specified custom converter.
return compilableName.Replace(".", "").Replace("<", "").Replace(">", "").Replace(",", "").Replace("[]", "Array");
}
- public static Type NullableOfTType { 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;
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;
+ }
+
+ underlyingType = null;
+ 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;
<data name="TypeNotSupportedTitle" xml:space="preserve">
<value>Did not generate serialization metadata for type.</value>
</data>
+ <data name="ContextClassesMustBePartialMessageFormat" xml:space="preserve">
+ <value>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</value>
+ </data>
+ <data name="ContextClassesMustBePartialTitle" xml:space="preserve">
+ <value>Derived 'JsonSerializerContext' types and all containing types must be partial.</value>
+ </data>
</root>
\ No newline at end of file
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="cs" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="de" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="es" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="fr" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="it" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="ja" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="ko" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="pl" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="pt-BR" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="ru" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="tr" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="zh-Hans" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="zh-Hant" original="../Strings.resx">
<body>
+ <trans-unit id="ContextClassesMustBePartialMessageFormat">
+ <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+ <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+ <note />
+ </trans-unit>
+ <trans-unit id="ContextClassesMustBePartialTitle">
+ <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+ <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+ <note />
+ </trans-unit>
<trans-unit id="DuplicateTypeNameMessageFormat">
<source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
<target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.Text.Json.SourceGeneration
+{
+ internal sealed class SourceGenerationSpec
+ {
+ public List<ContextGenerationSpec> ContextGenerationSpecList { get; init; }
+
+ public Type BooleanType { get; init; }
+ public Type ByteArrayType { get; init; }
+ public Type CharType { 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<Type> 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);
+ }
+}
<ItemGroup>
<Compile Include="$(CommonPath)System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />
+ <Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
+ <Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
+ <Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
<Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
+ <Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
<Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
+ <Compile Include="..\Common\JsonSerializerOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerOptionsAttribute.cs" />
+ <Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
<Compile Include="ClassType.cs" />
<Compile Include="CollectionType.cs" />
<Compile Include="JsonSourceGenerator.cs" />
<Compile Include="JsonSourceGenerator.Emitter.cs" />
<Compile Include="JsonSourceGenerator.Parser.cs" />
<Compile Include="ObjectConstructionStrategy.cs" />
- <Compile Include="PropertyMetadata.cs" />
+ <Compile Include="PropertyGenerationSpec.cs" />
<Compile Include="Reflection\AssemblyWrapper.cs" />
<Compile Include="Reflection\TypeExtensions.cs" />
<Compile Include="Reflection\FieldInfoWrapper.cs" />
<Compile Include="Reflection\ReflectionExtensions.cs" />
<Compile Include="Reflection\RoslynExtensions.cs" />
<Compile Include="Reflection\TypeWrapper.cs" />
- <Compile Include="TypeMetadata.cs" />
+ <Compile Include="ContextGenerationSpec.cs" />
+ <Compile Include="SourceGenerationSpec.cs" />
+ <Compile Include="TypeGenerationSpec.cs" />
</ItemGroup>
</Project>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.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
+ {
+ /// <summary>
+ /// Fully qualified assembly name, prefixed with "global::", e.g. global::System.Numerics.BigInteger.
+ /// </summary>
+ public string TypeRef { get; private set; }
+
+ /// <summary>
+ /// The name of the public JsonTypeInfo<T> 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.
+ /// </summary>
+ 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<PropertyGenerationSpec>? 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<PropertyGenerationSpec>? propertiesMetadata,
+ CollectionType collectionType,
+ TypeGenerationSpec? collectionKeyTypeMetadata,
+ TypeGenerationSpec? collectionValueTypeMetadata,
+ ObjectConstructionStrategy constructionStrategy,
+ TypeGenerationSpec? nullableUnderlyingTypeMetadata,
+ string? converterInstantiationLogic)
+ {
+ 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()
+ {
+ 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;
+ }
+ }
+}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections.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<PropertyMetadata>? 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<PropertyMetadata>? 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;
- }
- }
-}
{
public JsonIncludeAttribute() { }
}
+ public enum JsonKnownNamingPolicy
+ {
+ Unspecified = 0,
+ BuiltInCamelCase = 1,
+ }
[System.FlagsAttribute]
public enum JsonNumberHandling
{
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() { }
public static System.Text.Json.Serialization.JsonConverter<ulong> UInt64Converter { get { throw null; } }
public static System.Text.Json.Serialization.JsonConverter<System.Uri> UriConverter { get { throw null; } }
public static System.Text.Json.Serialization.JsonConverter<System.Version> VersionConverter { get { throw null; } }
- public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(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<TCollection> CreateDictionaryInfo<TCollection, TKey, TValue>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection> 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<TKey, TValue> where TKey : notnull { throw null; }
- public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.List<TElement> { throw null; }
- public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>() where T : notnull { throw null; }
+ public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TElement[]>? serializeFunc) { throw null; }
+ public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateDictionaryInfo<TCollection, TKey, TValue>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection> createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.Dictionary<TKey, TValue> where TKey : notnull { throw null; }
+ public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.List<TElement> { throw null; }
+ public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Func<T>? createObjectFunc, System.Func<System.Text.Json.Serialization.JsonSerializerContext, System.Text.Json.Serialization.Metadata.JsonPropertyInfo[]>? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, T>? serializeFunc) where T : notnull { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo<T>(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter<T>? converter, System.Func<object, T>? getter, System.Action<object, T>? 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<T> CreateValueInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; }
public static System.Text.Json.Serialization.JsonConverter<T> GetEnumConverter<T>(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; }
public static System.Text.Json.Serialization.JsonConverter<T?> GetNullableConverter<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> underlyingTypeInfo) where T : struct { throw null; }
- public static void InitializeObjectInfo<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> info, System.Text.Json.JsonSerializerOptions options, System.Func<T>? createObjectFunc, System.Func<System.Text.Json.Serialization.JsonSerializerContext, System.Text.Json.Serialization.Metadata.JsonPropertyInfo[]> propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where T : notnull { }
}
public abstract partial class JsonPropertyInfo
{
public abstract partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
{
internal JsonTypeInfo() { }
+ public System.Action<System.Text.Json.Utf8JsonWriter, T>? Serialize { get { throw null; } }
}
}
<data name="NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty" xml:space="preserve">
<value>A custom converter for JsonObject is not allowed on an extension property.</value>
</data>
+ <data name="PropInitAndSerializeFuncsNull" xml:space="preserve">
+ <value>'propInitFunc' and 'serializeFunc' cannot both be 'null'. </value>
+ </data>
+ <data name="NoMetadataForTypeProperties" xml:space="preserve">
+ <value>'JsonSerializerContext' '{0}' did not provide property metadata for type '{1}'.</value>
+ </data>
+ <data name="NoDefaultOptionsForContext" xml:space="preserve">
+ <value>To specify a serialization implementation for type '{0}'', context '{0}' must specify default options.</value>
+ </data>
</root>
\ No newline at end of file
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;netcoreapp3.0;net461</TargetFrameworks>
<ItemGroup>
<Compile Include="$(CommonPath)System\HexConverter.cs" Link="Common\System\HexConverter.cs" />
<Compile Include="$(CommonPath)System\Text\Json\PooledByteBufferWriter.cs" Link="Common\System\Text\Json\PooledByteBufferWriter.cs" />
+ <Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
+ <Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
+ <Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
<Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
+ <Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
<Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
+ <Compile Include="..\Common\JsonSerializerOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerOptionsAttribute.cs" />
+ <Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
<Compile Include="System\Text\Json\BitStack.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.DbRow.cs" />
<Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
<Compile Include="System\Text\Json\Serialization\Arguments.cs" />
<Compile Include="System\Text\Json\Serialization\ArgumentState.cs" />
- <Compile Include="System\Text\Json\Serialization\Attributes\JsonAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonConstructorAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonConverterAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonExtensionDataAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonNumberHandlingAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyNameAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonSerializableAttribute.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectSourceGenConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\Converters\JsonMetadataServicesConverter.cs" />
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UriConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\VersionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceHandler.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverter.ReadAhead.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverterOfT.ReadCore.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverterOfT.WriteCore.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverterOfT.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonDefaultNamingPolicy.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonResumableConverterOfT.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleMetadata.cs" />
/// 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.
/// </summary>
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class JsonSerializableAttribute : JsonAttribute
{
/// <summary>
+ /// Initializes a new instance of <see cref="JsonSerializableAttribute"/> with the specified type.
+ /// </summary>
+ /// <param name="type">The type to generate source code for.</param>
+ public JsonSerializableAttribute(Type type) { }
+
+ /// <summary>
/// The name of the property for the generated <see cref="JsonTypeInfo{T}"/> for
/// the type on the generated, derived <see cref="JsonSerializerContext"/> type.
/// </summary>
public string? TypeInfoPropertyName { get; set; }
/// <summary>
- /// Initializes a new instance of <see cref="JsonSerializableAttribute"/> with the specified type.
+ /// Determines what the source generator should generate for the type.
/// </summary>
- /// <param name="type">The type to generate source code for.</param>
- public JsonSerializableAttribute(Type type) { }
+ public JsonSourceGenerationMode GenerationMode { get; set; }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json.Serialization.Converters
+{
+ /// <summary>
+ /// Provides a mechanism to invoke "fast-path" serialization logic via
+ /// <see cref="JsonTypeInfo{T}.Serialize"/>. This type holds an optional
+ /// reference to an actual <see cref="JsonConverter{T}"/> for the type
+ /// <typeparamref name="T"/>, to provide a fallback when the fast path cannot be used.
+ /// </summary>
+ /// <typeparam name="T">The type to converter</typeparam>
+ internal sealed class JsonMetadataServicesConverter<T> : JsonResumableConverter<T>
+ {
+ private readonly Func<JsonConverter<T>> _converterCreator;
+
+ private readonly ConverterStrategy _converterStrategy;
+
+ private readonly Type? _keyType;
+ private readonly Type? _elementType;
+
+ private JsonConverter<T>? _converter;
+
+ // A backing converter for when fast-path logic cannot be used.
+ private JsonConverter<T> 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 JsonMetadataServicesConverter(Func<JsonConverter<T>> 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;
+
+ Debug.Assert(options == jsonTypeInfo.Options);
+
+ if (!state.SupportContinuation &&
+ jsonTypeInfo is JsonTypeInfo<T> info &&
+ info.Serialize != null &&
+ info.Options._context?.CanUseSerializationLogic == true)
+ {
+ info.Serialize(writer, value);
+ return true;
+ }
+
+ if (_converterStrategy == ConverterStrategy.Object && jsonTypeInfo.PropertyCacheArray == null)
+ {
+ jsonTypeInfo.InitializeSerializePropCache();
+ }
+
+ return Converter.OnTryWrite(writer, value, options, ref state);
+ }
+ }
+}
{
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)
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)
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;
// 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;
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!;
}
}
- 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;
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;
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Text.Json.Serialization.Metadata;
-
-namespace System.Text.Json.Serialization.Converters
-{
- /// <summary>
- /// Implementation of <cref>JsonObjectConverter{T}</cref> for source-generated converters.
- /// </summary>
- internal sealed class ObjectSourceGenConverter<T> : ObjectDefaultConverter<T> 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);
- }
- }
-}
+++ /dev/null
-// 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;
- }
-}
// 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
{
/// <summary>
private static void WriteUsingMetadata<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
{
- WriteStack state = default;
- state.Initialize(jsonTypeInfo, supportContinuation: false);
+ if (jsonTypeInfo is JsonTypeInfo<TValue> typedInfo &&
+ typedInfo.Serialize != null &&
+ typedInfo.Options._context?.CanUseSerializationLogic == true)
+ {
+ 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<TValue>(in TValue value)
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract partial class JsonSerializerContext
{
+ private bool? _canUseSerializationLogic;
+ private JsonSerializerOptions? _defaultOptions;
+
internal JsonSerializerOptions? _options;
/// <summary>
+ /// Indicates whether pre-generated serialization logic for types in the context
+ /// is compatible with the run-time specified <see cref="JsonSerializerOptions"/>.
+ /// </summary>
+ internal bool CanUseSerializationLogic
+ {
+ get
+ {
+ if (!_canUseSerializationLogic.HasValue)
+ {
+ if (_defaultOptions == null)
+ {
+ _canUseSerializationLogic = false;
+ }
+ else
+ {
+ _canUseSerializationLogic =
+ // Guard against unsupported features
+ Options.Converters.Count == 0 &&
+ 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;
+ }
+ }
+
+ /// <summary>
/// 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.
/// </summary>
/// <summary>
/// Creates an instance of <see cref="JsonSerializerContext"/> and binds it with the indicated <see cref="JsonSerializerOptions"/>.
/// </summary>
- /// <param name="options">The run-time provided options for the context instance.</param>
+ /// <param name="instanceOptions">The run-time provided options for the context instance.</param>
+ /// <param name="defaultOptions">The default run-time options for the context. It's values are defined at design-time via <see cref="JsonSerializerOptionsAttribute"/>.</param>
/// <remarks>
- /// If no options are passed, then no options are set until the context is bound using <see cref="JsonSerializerOptions.AddContext{TContext}"/>,
+ /// If no instance options are passed, then no options are set until the context is bound using <see cref="JsonSerializerOptions.AddContext{TContext}"/>,
/// or until <see cref="Options"/> is called, where a new options instance is created and bound.
/// </remarks>
- protected JsonSerializerContext(JsonSerializerOptions? options)
+ protected JsonSerializerContext(JsonSerializerOptions? instanceOptions, JsonSerializerOptions? defaultOptions)
{
- if (options != null)
+ if (instanceOptions != null)
{
- if (options._context != null)
+ if (instanceOptions._context != null)
{
ThrowHelper.ThrowInvalidOperationException_JsonSerializerOptionsAlreadyBoundToContext();
}
- _options = options;
- options._context = this;
+ _options = instanceOptions;
+ instanceOptions._context = this;
}
+
+ _defaultOptions = defaultOptions;
}
/// <summary>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <param name="elementInfo">A <see cref="JsonTypeInfo"/> instance representing the element type.</param>
/// <param name="numberHandling">The <see cref="JsonNumberHandling"/> option to apply to number collection elements.</param>
+ /// <param name="serializeFunc">An optimized serialization implementation assuming pre-determined <see cref="JsonSerializerOptionsAttribute"/> defaults.</param>
/// <returns></returns>
public static JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(
JsonSerializerOptions options,
JsonTypeInfo elementInfo,
- JsonNumberHandling numberHandling)
+ JsonNumberHandling numberHandling,
+ Action<Utf8JsonWriter, TElement[]>? serializeFunc)
=> new JsonTypeInfoInternal<TElement[]>(
options,
createObjectFunc: null,
- new ArrayConverter<TElement[], TElement>(),
+ () => new ArrayConverter<TElement[], TElement>(),
elementInfo,
- numberHandling);
+ numberHandling,
+ serializeFunc,
+ typeof(TElement));
/// <summary>
/// Creates metadata for types assignable to <see cref="List{T}"/>.
/// <param name="createObjectFunc">A <see cref="Func{TResult}"/> to create an instance of the list when deserializing.</param>
/// <param name="elementInfo">A <see cref="JsonTypeInfo"/> instance representing the element type.</param>
/// <param name="numberHandling">The <see cref="JsonNumberHandling"/> option to apply to number collection elements.</param>
+ /// <param name="serializeFunc">An optimized serialization implementation assuming pre-determined <see cref="JsonSerializerOptionsAttribute"/> defaults.</param>
/// <returns></returns>
public static JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(
JsonSerializerOptions options,
Func<TCollection>? createObjectFunc,
JsonTypeInfo elementInfo,
- JsonNumberHandling numberHandling)
+ JsonNumberHandling numberHandling,
+ Action<Utf8JsonWriter, TCollection>? serializeFunc)
where TCollection : List<TElement>
=> new JsonTypeInfoInternal<TCollection>(
options,
createObjectFunc,
- new ListOfTConverter<TCollection, TElement>(),
+ () => new ListOfTConverter<TCollection, TElement>(),
elementInfo,
- numberHandling);
+ numberHandling,
+ serializeFunc,
+ typeof(TElement));
/// <summary>
/// Creates metadata for types assignable to <see cref="Dictionary{TKey, TValue}"/>.
/// <param name="keyInfo">A <see cref="JsonTypeInfo"/> instance representing the key type.</param>
/// <param name="valueInfo">A <see cref="JsonTypeInfo"/> instance representing the value type.</param>
/// <param name="numberHandling">The <see cref="JsonNumberHandling"/> option to apply to number collection elements.</param>
+ /// <param name="serializeFunc">An optimized serialization implementation assuming pre-determined <see cref="JsonSerializerOptionsAttribute"/> defaults.</param>
/// <returns></returns>
public static JsonTypeInfo<TCollection> CreateDictionaryInfo<TCollection, TKey, TValue>(
JsonSerializerOptions options,
Func<TCollection> createObjectFunc,
JsonTypeInfo keyInfo,
JsonTypeInfo valueInfo,
- JsonNumberHandling numberHandling)
+ JsonNumberHandling numberHandling,
+ Action<Utf8JsonWriter, TCollection>? serializeFunc)
where TCollection : Dictionary<TKey, TValue>
where TKey : notnull
=> new JsonTypeInfoInternal<TCollection>(
options,
createObjectFunc,
- new DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>(),
+ () => new DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>(),
keyInfo,
valueInfo,
- numberHandling);
+ numberHandling,
+ serializeFunc,
+ typeof(TKey),
+ typeof(TValue));
}
}
/// Creates a <see cref="JsonConverter{T}"/> instance that converts <typeparamref name="T"/> values.
/// </summary>
/// <typeparam name="T">The generic definition for the enum type.</typeparam>
- /// <param name="options"></param>
+ /// <param name="options">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization.</param>
/// <returns></returns>
public static JsonConverter<T> GetEnumConverter<T>(JsonSerializerOptions options) where T : struct, Enum
=> new EnumConverter<T>(EnumConverterOptions.AllowNumbers, options ?? throw new ArgumentNullException(nameof(options)));
/// <summary>
/// Creates a <see cref="JsonConverter{T}"/> instance that converts <typeparamref name="T?"/> values.
/// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="underlyingTypeInfo"></param>
+ /// <typeparam name="T">The generic definition for the underlying nullable type.</typeparam>
+ /// <param name="underlyingTypeInfo">Serialization metadata for the underlying nullable type.</param>
/// <returns></returns>
public static JsonConverter<T?> GetNullableConverter<T>(JsonTypeInfo<T> underlyingTypeInfo) where T : struct
{
/// Creates metadata for a property or field.
/// </summary>
/// <typeparam name="T">The type that the converter for the property returns or accepts when converting JSON data.</typeparam>
+ /// <param name="options">The <see cref="JsonSerializerOptions"/> to initialize the metadata with.</param>
+ /// <param name="isProperty">Whether the CLR member is a property or field.</param>
+ /// <param name="declaringType">The declaring type of the property or field.</param>
+ /// <param name="propertyTypeInfo">The <see cref="JsonTypeInfo"/> info for the property or field's type.</param>
+ /// <param name="converter">A <see cref="JsonConverter"/> for the property or field, specified by <see cref="JsonConverterAttribute"/>.</param>
+ /// <param name="getter">Provides a mechanism to get the property or field's value.</param>
+ /// <param name="setter">Provides a mechanism to set the property or field's value.</param>
+ /// <param name="ignoreCondition">Specifies a condition for the property to be ignored.</param>
+ /// <param name="numberHandling">If the property or field is a number, specifies how it should processed when serializing and deserializing.</param>
+ /// <param name="propertyName">The CLR name of the property or field.</param>
+ /// <param name="jsonPropertyName">The name to be used when processing the property or field, specified by <see cref="JsonPropertyNameAttribute"/>.</param>
/// <returns>A <see cref="JsonPropertyInfo"/> instance intialized with the provided metadata.</returns>
public static JsonPropertyInfo CreatePropertyInfo<T>(
JsonSerializerOptions options,
/// <summary>
/// Creates metadata for a complex class or struct.
/// </summary>
+ /// <param name="options">The <see cref="JsonSerializerOptions"/> to initialize the metadata with.</param>
+ /// <param name="createObjectFunc">Provides a mechanism to create an instance of the class or struct when deserializing.</param>
+ /// <param name="propInitFunc">Provides a mechanism to initialize metadata for properties and fields of the class or struct.</param>
+ /// <param name="serializeFunc">Provides a serialization implementation for instances of the class or struct which assumes options specified by <see cref="JsonSerializerOptionsAttribute"/>.</param>
+ /// <param name="numberHandling">Specifies how number properties and fields should be processed when serializing and deserializing.</param>
/// <typeparam name="T">The type of the class or struct.</typeparam>
+ /// <exception cref="InvalidOperationException">Thrown when <paramref name="options"/> and <paramref name="propInitFunc"/> are both null.</exception>
/// <returns>A <see cref="JsonTypeInfo{T}"/> instance representing the class or struct.</returns>
- public static JsonTypeInfo<T> CreateObjectInfo<T>() where T : notnull => new JsonTypeInfoInternal<T>();
-
- /// <summary>
- /// Initializes metadata for a class or struct.
- /// </summary>
- /// <typeparam name="T">The type of the class or struct</typeparam>
- /// <param name="info"></param>
- /// <param name="options"></param>
- /// <param name="createObjectFunc"></param>
- /// <param name="propInitFunc"></param>
- /// <param name="numberHandling"></param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/>, <paramref name="info"/>, or <paramref name="propInitFunc"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="info"/>, does not represent a complex class or struct type.</exception>
- public static void InitializeObjectInfo<T>(
- JsonTypeInfo<T> info,
+ public static JsonTypeInfo<T> CreateObjectInfo<T>(
JsonSerializerOptions options,
Func<T>? createObjectFunc,
- Func<JsonSerializerContext, JsonPropertyInfo[]> propInitFunc,
- JsonNumberHandling numberHandling)
- where T : notnull
- {
- if (info == null)
- {
- 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));
- }
-
- if (propInitFunc == null)
- {
- throw new ArgumentNullException(nameof(propInitFunc));
- }
-
- ((JsonTypeInfoInternal<T>)info).InitializeAsObject(options, createObjectFunc, propInitFunc, numberHandling);
- Debug.Assert(info.PropertyInfoForTypeInfo!.ConverterStrategy == ConverterStrategy.Object);
- }
+ Func<JsonSerializerContext, JsonPropertyInfo[]>? propInitFunc,
+ JsonNumberHandling numberHandling,
+ Action<Utf8JsonWriter, T>? serializeFunc) where T : notnull
+ => new JsonTypeInfoInternal<T>(options, createObjectFunc, propInitFunc, numberHandling, serializeFunc);
/// <summary>
/// Creates metadata for a primitive or a type with a custom converter.
/// <returns>A <see cref="JsonTypeInfo{T}"/> instance representing the type.</returns>
public static JsonTypeInfo<T> CreateValueInfo<T>(JsonSerializerOptions options, JsonConverter converter)
{
- JsonTypeInfo<T> info = new JsonTypeInfoInternal<T>(options);
+ JsonTypeInfo<T> info = new JsonTypeInfoInternal<T>(options, ConverterStrategy.Value);
info.PropertyInfoForTypeInfo = CreateJsonPropertyInfoForClassInfo(typeof(T), info, converter, options);
return info;
}
internal void InitializeSerializePropCache()
{
- Debug.Assert(PropInitFunc != null);
- Debug.Assert(Options._context != null);
+ JsonSerializerContext? context = Options._context;
- PropertyCacheArray = PropInitFunc(Options._context);
+ Debug.Assert(context != null);
+ Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object);
+
+ if (PropInitFunc == null)
+ {
+ ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(context, Type);
+ return;
+ }
+
+ PropertyCacheArray = PropInitFunc(context);
}
internal void InitializeDeserializePropCache()
{
+ Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object);
+
if (PropertyCacheArray == null)
{
InitializeSerializePropCache();
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!;
}
internal sealed class JsonTypeInfoInternal<T> : JsonTypeInfo<T>
{
/// <summary>
- /// Creates serialization metadata for a <see cref="ConverterStrategy.Object"/>.
+ /// Creates serialization metadata given JsonSerializerOptions and a ConverterStrategy.
/// </summary>
- public JsonTypeInfoInternal() : base(typeof(T), null!, ConverterStrategy.Object)
+ public JsonTypeInfoInternal(JsonSerializerOptions options, ConverterStrategy converterStrategy)
+ : base(typeof(T), options, converterStrategy)
{
}
/// <summary>
- /// Creates serialization metadata for a <see cref="ConverterStrategy.Value"/>.
+ /// Creates serialization metadata for an object.
/// </summary>
- public JsonTypeInfoInternal(JsonSerializerOptions options)
- : base (typeof(T), options, ConverterStrategy.Value)
+ public JsonTypeInfoInternal(
+ JsonSerializerOptions options,
+ Func<T>? createObjectFunc,
+ Func<JsonSerializerContext, JsonPropertyInfo[]>? propInitFunc,
+ JsonNumberHandling numberHandling,
+ Action<Utf8JsonWriter, T>? 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<T>(() => new ObjectDefaultConverter<T>(), 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);
}
/// <summary>
public JsonTypeInfoInternal(
JsonSerializerOptions options,
Func<T>? createObjectFunc,
- JsonConverter<T> converter,
- JsonTypeInfo elementInfo,
- JsonNumberHandling numberHandling) : base(typeof(T), options, ConverterStrategy.Enumerable)
+ Func<JsonConverter<T>> converterCreator,
+ JsonTypeInfo? elementInfo,
+ JsonNumberHandling numberHandling,
+ Action<Utf8JsonWriter, T>? serializeFunc,
+ Type elementType) : base(typeof(T), options, ConverterStrategy.Enumerable)
{
+ JsonConverter<T> converter = new JsonMetadataServicesConverter<T>(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);
}
public JsonTypeInfoInternal(
JsonSerializerOptions options,
Func<T>? createObjectFunc,
- JsonConverter<T> converter,
- JsonTypeInfo keyInfo,
- JsonTypeInfo valueInfo,
- JsonNumberHandling numberHandling) : base(typeof(T), options, ConverterStrategy.Dictionary)
+ Func<JsonConverter<T>> converterCreator,
+ JsonTypeInfo? keyInfo,
+ JsonTypeInfo? valueInfo,
+ JsonNumberHandling numberHandling,
+ Action<Utf8JsonWriter, T>? serializeFunc,
+ Type keyType,
+ Type elementType) : base(typeof(T), options, ConverterStrategy.Dictionary)
{
+ JsonConverter<T> converter = new JsonMetadataServicesConverter<T>(converterCreator, ConverterStrategy.Dictionary, keyType, elementType);
+
KeyType = converter.KeyType;
- KeyTypeInfo = keyInfo ?? throw new ArgumentNullException(nameof(keyInfo)); ;
+ ElementType = converter.ElementType;
+ KeyTypeInfo = keyInfo ?? throw new ArgumentNullException(nameof(keyInfo));
ElementType = converter.ElementType;
ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo));
NumberHandling = numberHandling;
PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options);
- SetCreateObjectFunc(createObjectFunc);
- }
-
- /// <summary>
- /// Initializes serialization metadata for a <see cref="ConverterStrategy.Object"/>.
- /// </summary>
- public void InitializeAsObject(
- JsonSerializerOptions options,
- Func<T>? createObjectFunc,
- Func<JsonSerializerContext, JsonPropertyInfo[]> propInitFunc,
- JsonNumberHandling numberHandling)
- {
- Options = options;
-
-#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<T>();
-#pragma warning restore CS8714
-
- PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options);
- NumberHandling = numberHandling;
- PropInitFunc = propInitFunc;
+ Serialize = serializeFunc;
SetCreateObjectFunc(createObjectFunc);
}
{
Debug.Assert(false, "This constructor should not be called.");
}
+
+ /// <summary>
+ /// A method that serializes an instance of <typeparamref name="T"/> using
+ /// <see cref="JsonSerializerOptionsAttribute"/> values specified at design time.
+ /// </summary>
+ public Action<Utf8JsonWriter, T>? Serialize { get; private protected set; }
}
}
{
throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type));
}
+
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ 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));
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.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<string>();
+
+ 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;
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+ public interface ITestContext
+ {
+ public JsonTypeInfo<Location> Location { get; }
+ public JsonTypeInfo<RepeatedTypes.Location> RepeatedLocation { get; }
+ public JsonTypeInfo<ActiveOrUpcomingEvent> ActiveOrUpcomingEvent { get; }
+ public JsonTypeInfo<CampaignSummaryViewModel> CampaignSummaryViewModel { get; }
+ public JsonTypeInfo<IndexViewModel> IndexViewModel { get; }
+ public JsonTypeInfo<WeatherForecastWithPOCOs> WeatherForecastWithPOCOs { get; }
+ public JsonTypeInfo<EmptyPoco> EmptyPoco { get; }
+ public JsonTypeInfo<HighLowTemps> HighLowTemps { get; }
+ public JsonTypeInfo<MyType> MyType { get; }
+ public JsonTypeInfo<MyType2> MyType2 { get; }
+ public JsonTypeInfo<MyIntermediateType> MyIntermediateType { get; }
+ public JsonTypeInfo<HighLowTempsImmutable> HighLowTempsImmutable { get; }
+ public JsonTypeInfo<RealWorldContextTests.MyNestedClass> MyNestedClass { get; }
+ public JsonTypeInfo<RealWorldContextTests.MyNestedClass.MyNestedNestedClass> MyNestedNestedClass { get; }
+ public JsonTypeInfo<object[]> ObjectArray { get; }
+ public JsonTypeInfo<string> String { get; }
+ public JsonTypeInfo<RealWorldContextTests.ClassWithEnumAndNullable> 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> _JsonMessage;
+ public JsonTypeInfo<JsonMessage> JsonMessage
+ {
+ get
+ {
+ if (_JsonMessage == null)
+ {
+ JsonTypeInfo<JsonMessage> objectInfo = JsonMetadataServices.CreateObjectInfo<JsonMessage>(
+ Options,
+ createObjectFunc: static () => new JsonMessage(),
+ propInitFunc: null,
+ default,
+ serializeFunc: JsonMessageSerialize);
+
+ _JsonMessage = objectInfo;
+ }
+
+ return _JsonMessage;
+ }
+ }
+
+ private static void JsonMessageSerialize(Utf8JsonWriter writer, JsonMessage value) => throw new NotImplementedException();
+ }
+
+ [JsonSerializable(typeof(Dictionary<string, string>))]
+ [JsonSerializable(typeof(Dictionary<int, string>))]
+ [JsonSerializable(typeof(Dictionary<string, JsonMessage>))]
+ internal partial class DictionaryTypeContext : JsonSerializerContext { }
+
+ [JsonSerializable(typeof(JsonMessage))]
+ public partial class PublicContext : JsonSerializerContext { }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.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 { }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.Text.Json
+{
+ internal static partial class JsonTestHelper
+ {
+ internal static void AssertThrows_PropMetadataInit(Action action, Type type)
+ {
+ var ex = Assert.Throws<InvalidOperationException>(action);
+ string exAsStr = ex.ToString();
+ Assert.Contains(type.ToString(), exAsStr);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.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(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 : 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);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.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(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 : 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);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.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<MyNestedClass>(json, ((ITestContext)MetadataContext.Default).MyNestedClass);
+ Assert.Equal(5, obj.MyInt);
+ Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass));
+
+ MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize<MyNestedClass.MyNestedNestedClass>(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));
+ }
+ }
+}
// 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 abstract class RealWorldContextTests
{
+ protected ITestContext DefaultContext { get; }
+ private Func<JsonSerializerOptions, ITestContext> _contextCreator;
+
+ public RealWorldContextTests(ITestContext defaultContext, Func<JsonSerializerOptions, ITestContext> 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
{
};
}
- 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);
Assert.Equal(expected.Country, obj.Country);
}
- private static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent()
+ protected static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent()
{
return new ActiveOrUpcomingEvent
{
};
}
- 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);
Assert.Equal(expected.StartDate, obj.StartDate);
}
- private static CampaignSummaryViewModel CreateCampaignSummaryViewModel()
+ protected static CampaignSummaryViewModel CreateCampaignSummaryViewModel()
{
return new CampaignSummaryViewModel
{
};
}
- 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);
Assert.Equal(expected.Title, obj.Title);
}
- private static IndexViewModel CreateIndexViewModel()
+ protected static IndexViewModel CreateIndexViewModel()
{
return new IndexViewModel
{
};
}
- 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++)
Assert.Equal(expected.IsNewAccount, obj.IsNewAccount);
}
- private static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs()
+ protected static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs()
{
return new WeatherForecastWithPOCOs
{
};
}
- 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);
}
}
- private static RepeatedTypes.Location CreateRepeatedLocation()
+ protected static RepeatedTypes.Location CreateRepeatedLocation()
{
return new RepeatedTypes.Location
{
};
}
- 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);
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);
}
[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];
}
[Fact]
- public static void HandlesNestedTypes()
+ public virtual void HandlesNestedTypes()
{
string json = @"{""MyInt"":5}";
- MyNestedClass obj = JsonSerializer.Deserialize<MyNestedClass>(json, JsonContext.Default.MyNestedClass);
+ MyNestedClass obj = JsonSerializer.Deserialize<MyNestedClass>(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<MyNestedClass.MyNestedNestedClass>(json, JsonContext.Default.MyNestedNestedClass);
+ MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize<MyNestedClass.MyNestedNestedClass>(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
}
[Fact]
- public static void ConstructingFromOptionsKeepsReference()
+ public void ConstructingFromOptionsKeepsReference()
{
JsonStringEnumConverter converter = new();
JsonSerializerOptions options = new()
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<InvalidOperationException>(() => 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<InvalidOperationException>(() => context.Options.PropertyNameCaseInsensitive = true);
exAsStr = ex.ToString();
Assert.Contains("JsonSerializerOptions", exAsStr);
}
[Fact]
- public static void ParameterizedConstructor()
+ public virtual 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<NotSupportedException>(() => JsonSerializer.Deserialize(json, JsonContext.Default.HighLowTempsImmutable));
+ Assert.Throws<NotSupportedException>(() => 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);
}
}
[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<NotSupportedException>(
- () => 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);
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)
{
private const string ExceptionMessageFromCustomContext = "Exception thrown from custom context.";
[Fact]
- public static void GetTypeInfoCalledDuringPolymorphicSerialization()
+ public void GetTypeInfoCalledDuringPolymorphicSerialization()
{
CustomContext context = new(new JsonSerializerOptions());
internal class CustomContext : JsonSerializerContext
{
- public CustomContext(JsonSerializerOptions options) : base(options) { }
+ public CustomContext(JsonSerializerOptions options) : base(options, null) { }
private JsonTypeInfo<object> _object;
public JsonTypeInfo<object> Object => _object ??= JsonMetadataServices.CreateValueInfo<object>(Options, JsonMetadataServices.ObjectConverter);
private JsonTypeInfo<object[]> _objectArray;
- public JsonTypeInfo<object[]> ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo<object>(Options, Object, default);
+ public JsonTypeInfo<object[]> ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo<object>(Options, Object, default, serializeFunc: null);
public override JsonTypeInfo GetTypeInfo(Type type)
{
throw new InvalidOperationException(ExceptionMessageFromCustomContext);
}
}
+
+ protected static void AssertFastPathLogicCorrect<T>(string expectedJson, T value, JsonTypeInfo<T> typeInfo)
+ {
+ using MemoryStream ms = new();
+ using Utf8JsonWriter writer = new(ms);
+ typeInfo.Serialize!(writer, value);
+ writer.Flush();
+
+ JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray()));
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.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<MyNestedClass>(json, ((ITestContext)MetadataContext.Default).MyNestedClass);
+ Assert.Equal(5, obj.MyInt);
+ Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass));
+
+ MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize<MyNestedClass.MyNestedNestedClass>(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));
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.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<NotImplementedException>(() => JsonSerializer.Serialize(message, JsonContext.Default.JsonMessage));
+ Assert.Throws<NotImplementedException>(() => 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<object[]> 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<object[]> GetIncompatibleOptions()
+ {
+ yield return new object[] { new JsonSerializerOptions() };
+ yield return new object[] { new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.Never } };
+ yield return new object[] { new JsonSerializerOptions { IgnoreReadOnlyFields = true } };
+ }
+ }
+}
</ItemGroup>
<ItemGroup>
+ <Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />
+ <Compile Include="ContextClasses.cs" />
+ <Compile Include="JsonSerializerContextTests.cs" />
+ <Compile Include="JsonTestHelper.cs" />
+ <Compile Include="MetadataAndSerializationContextTests.cs" />
+ <Compile Include="MetadataContextTests.cs" />
+ <Compile Include="MixedModeContextTests.cs" />
+ <Compile Include="SerializationContextTests.cs" />
+ <Compile Include="SerializationLogicTests.cs" />
<Compile Include="TestClasses.cs" />
- <Compile Include="JsonSourceGeneratorTests.cs" />
+ <Compile Include="RealWorldContextTests.cs" />
</ItemGroup>
<Target Name="FixIncrementalCoreCompileWithAnalyzers" BeforeTargets="CoreCompile">
// 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
{
{
public MyType Type = new();
}
+
+ public class JsonMessage
+ {
+ public string Message { get; set; }
+ public int Length => Message?.Length ?? 0; // Read-only property
+ }
}
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
+using System.Text.Encodings.Web;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
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),
};
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
{
// 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;
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; }
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; }
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; }
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; }
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; }
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() { }
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]
namespace System.Text.Json
{
- internal static class JsonTestHelper
+ internal static partial class JsonTestHelper
{
#if BUILDING_INBOX_LIBRARY
public const string DoubleFormatString = null;
=> 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<string>();
-
- 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;
- }
- }
}
}
}
else
{
- _Dictionary = JsonMetadataServices.CreateDictionaryInfo<Dictionary<string, HighLowTemps>, string, HighLowTemps>(Options, () => new Dictionary<string, HighLowTemps>(), this.String, this.HighLowTemps, default);
+ _Dictionary = JsonMetadataServices.CreateDictionaryInfo<Dictionary<string, HighLowTemps>, string, HighLowTemps>(Options, () => new Dictionary<string, HighLowTemps>(), this.String, this.HighLowTemps, default, serializeFunc: null);
}
}
}
else
{
- JsonTypeInfo<HighLowTemps> objectInfo = JsonMetadataServices.CreateObjectInfo<HighLowTemps>();
- _HighLowTemps = objectInfo;
-
- JsonMetadataServices.InitializeObjectInfo(
- objectInfo,
+ JsonTypeInfo<HighLowTemps> objectInfo = JsonMetadataServices.CreateObjectInfo<HighLowTemps>(
Options,
createObjectFunc: static () => new HighLowTemps(),
HighLowTempsPropInitFunc,
- default);
+ default,
+ serializeFunc: null);
+
+ _HighLowTemps = objectInfo;
}
}
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)
{
}
}
else
{
- _ListSystemDateTimeOffset = JsonMetadataServices.CreateListInfo<List<DateTimeOffset>, DateTimeOffset>(Options, () => new List<DateTimeOffset>(), this.DateTimeOffset, default);
+ _ListSystemDateTimeOffset = JsonMetadataServices.CreateListInfo<List<DateTimeOffset>, DateTimeOffset>(Options, () => new List<DateTimeOffset>(), this.DateTimeOffset, default, serializeFunc: null);
}
}
}
else
{
- _StringArray = JsonMetadataServices.CreateArrayInfo<string>(Options, this.String, default);
+ _StringArray = JsonMetadataServices.CreateArrayInfo<string>(Options, this.String, default, serializeFunc: null);
}
}
}
else
{
- JsonTypeInfo<WeatherForecastWithPOCOs> objectInfo = JsonMetadataServices.CreateObjectInfo<WeatherForecastWithPOCOs>();
- _WeatherForecastWithPOCOs = objectInfo;
-
- JsonMetadataServices.InitializeObjectInfo(
- objectInfo,
+ JsonTypeInfo<WeatherForecastWithPOCOs> objectInfo = JsonMetadataServices.CreateObjectInfo<WeatherForecastWithPOCOs>(
Options,
createObjectFunc: static () => new WeatherForecastWithPOCOs(),
WeatherForecastWithPOCOsPropInitFunc,
- default);
+ default,
+ serializeFunc: null);
+
+ _WeatherForecastWithPOCOs = objectInfo;
}
}
{
JsonSerializerOptions options = new();
- JsonTypeInfo<MyClass> info = JsonMetadataServices.CreateObjectInfo<MyClass>();
-
- // Null info
- ArgumentNullException ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.InitializeObjectInfo<MyClass>(
- info: null,
- options: options,
+ // Null options
+ ArgumentNullException ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.CreateObjectInfo<MyClass>(
+ options: null,
createObjectFunc: null,
propInitFunc: (context) => Array.Empty<JsonPropertyInfo>(),
- numberHandling: default));
- Assert.Contains("info", ane.ToString());
+ numberHandling: default,
+ serializeFunc: null));
+ Assert.Contains("options", ane.ToString());
- // Info is not for object converter strategy
- ArgumentException ae = Assert.Throws<ArgumentException>(() => JsonMetadataServices.InitializeObjectInfo(
- info: JsonMetadataServices.CreateValueInfo<MyClass>(options, new DerivedClassConverter()),
- options: options,
+ // Null prop init func is fine if serialize func is provided.
+ JsonMetadataServices.CreateObjectInfo<MyClass>(
+ options,
createObjectFunc: null,
- propInitFunc: (context) => Array.Empty<JsonPropertyInfo>(),
- numberHandling: default));
- Assert.Contains("info", ae.ToString());
+ propInitFunc: null,
+ numberHandling: default,
+ serializeFunc: (writer, obj) => { });
- // Null options
- ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.InitializeObjectInfo(
- info: info,
- options: null,
+ // Null serialize func is fine if prop init func is provided.
+ JsonMetadataServices.CreateObjectInfo<MyClass>(
+ options,
createObjectFunc: null,
propInitFunc: (context) => Array.Empty<JsonPropertyInfo>(),
- numberHandling: default));
- Assert.Contains("options", ane.ToString());
+ numberHandling: default,
+ serializeFunc: null);
- // Null prop init func.
- ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.InitializeObjectInfo(
- info: info,
- options: options,
+ // Null prop init func and serialize func
+ InvalidOperationException ioe = Assert.Throws<InvalidOperationException>(() => JsonMetadataServices.CreateObjectInfo<MyClass>(
+ options,
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]
ArgumentNullException ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.CreateArrayInfo<int>(
options: null,
elementInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
- numberHandling: default));
+ numberHandling: default,
+ serializeFunc: null));
Assert.Contains("options", ane.ToString());
// Null element info
ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.CreateArrayInfo<int>(
- options: options,
+ options,
elementInfo: null,
- numberHandling: default));
+ numberHandling: default,
+ serializeFunc: null));
Assert.Contains("elementInfo", ane.ToString());
}
options: null,
createObjectFunc: null,
elementInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
- numberHandling: default));
+ numberHandling: default,
+ serializeFunc: null));
Assert.Contains("options", ane.ToString());
// Null element info
options: options,
createObjectFunc: null,
elementInfo: null,
- numberHandling: default));
+ numberHandling: default,
+ serializeFunc: null));
Assert.Contains("elementInfo", ane.ToString());
}
createObjectFunc: null,
keyInfo: JsonMetadataServices.CreateValueInfo<string>(options, JsonMetadataServices.StringConverter),
valueInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
- numberHandling: default));
+ numberHandling: default,
+ serializeFunc: null));
Assert.Contains("options", ane.ToString());
// Null key info
createObjectFunc: null,
keyInfo: null,
valueInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
- numberHandling: default));
+ numberHandling: default,
+ serializeFunc: null));
Assert.Contains("keyInfo", ane.ToString());
// Null value info
createObjectFunc: null,
keyInfo: JsonMetadataServices.CreateValueInfo<string>(options, JsonMetadataServices.StringConverter),
valueInfo: null,
- numberHandling: default));
+ numberHandling: default,
+ serializeFunc: null));
Assert.Contains("valueInfo", ane.ToString());
}
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();
}
}
</PropertyGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)System\IO\WrappedMemoryStream.cs" Link="CommonTest\System\IO\WrappedMemoryStream.cs" />
+ <Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />
<Compile Include="BitStackTests.cs" />
<Compile Include="BufferFactory.cs" />
<Compile Include="BufferSegment.cs" />