sealed class JsonSourceGenerationOptionsAttribute : JsonAttribute
{
/// <summary>
+ /// Constructs a new <see cref="JsonSourceGenerationOptionsAttribute"/> instance.
+ /// </summary>
+ public JsonSourceGenerationOptionsAttribute() { }
+
+ /// <summary>
+ /// Constructs a new <see cref="JsonSourceGenerationOptionsAttribute"/> instance with a predefined set of options determined by the specified <see cref="JsonSerializerDefaults"/>.
+ /// </summary>
+ /// <param name="defaults">The <see cref="JsonSerializerDefaults"/> to reason about.</param>
+ /// <exception cref="ArgumentOutOfRangeException">Invalid <paramref name="defaults"/> parameter.</exception>
+ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults)
+ {
+ // Constructor kept in sync with equivalent overload in JsonSerializerOptions
+
+ if (defaults is JsonSerializerDefaults.Web)
+ {
+ PropertyNameCaseInsensitive = true;
+ PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase;
+ NumberHandling = JsonNumberHandling.AllowReadingFromString;
+ }
+ else if (defaults is not JsonSerializerDefaults.General)
+ {
+ throw new ArgumentOutOfRangeException(nameof(defaults));
+ }
+ }
+
+ /// <summary>
+ /// Defines whether an extra comma at the end of a list of JSON values in an object or array
+ /// is allowed (and ignored) within the JSON payload being deserialized.
+ /// </summary>
+ public bool AllowTrailingCommas { get; set; }
+
+ /// <summary>
+ /// Specifies a list of custom converter types to be used.
+ /// </summary>
+ public Type[]? Converters { get; set; }
+
+ /// <summary>
+ /// The default buffer size in bytes used when creating temporary buffers.
+ /// </summary>
+ public int DefaultBufferSize { get; set; }
+
+ /// <summary>
/// Specifies the default ignore condition.
/// </summary>
public JsonIgnoreCondition DefaultIgnoreCondition { get; set; }
/// <summary>
+ /// Specifies the policy used to convert a dictionary key to another format, such as camel-casing.
+ /// </summary>
+ public JsonKnownNamingPolicy DictionaryKeyPolicy { get; set; }
+
+ /// <summary>
/// Specifies whether to ignore read-only fields.
/// </summary>
public bool IgnoreReadOnlyFields { get; set; }
public bool IncludeFields { get; set; }
/// <summary>
+ /// Gets or sets the maximum depth allowed when serializing or deserializing JSON, with the default (i.e. 0) indicating a max depth of 64.
+ /// </summary>
+ public int MaxDepth { get; set; }
+
+ /// <summary>
+ /// Specifies how number types should be handled when serializing or deserializing.
+ /// </summary>
+ public JsonNumberHandling NumberHandling { get; set; }
+
+ /// <summary>
+ /// Specifies preferred object creation handling for properties when deserializing JSON.
+ /// </summary>
+ public JsonObjectCreationHandling PreferredObjectCreationHandling { get; set; }
+
+ /// <summary>
+ /// Determines whether a property name uses a case-insensitive comparison during deserialization.
+ /// </summary>
+ public bool PropertyNameCaseInsensitive { get; set; }
+
+ /// <summary>
/// Specifies a built-in naming polices to convert JSON property names with.
/// </summary>
public JsonKnownNamingPolicy PropertyNamingPolicy { get; set; }
/// <summary>
+ /// Defines how JSON comments are handled during deserialization.
+ /// </summary>
+ public JsonCommentHandling ReadCommentHandling { get; set; }
+
+ /// <summary>
+ /// Defines how deserializing a type declared as an <see cref="object"/> is handled during deserialization.
+ /// </summary>
+ public JsonUnknownTypeHandling UnknownTypeHandling { get; set; }
+
+ /// <summary>
+ /// Determines how <see cref="JsonSerializer"/> handles JSON properties that
+ /// cannot be mapped to a specific .NET member when deserializing object types.
+ /// </summary>
+ public JsonUnmappedMemberHandling UnmappedMemberHandling { get; set; }
+
+ /// <summary>
/// Specifies whether JSON output should be pretty-printed.
/// </summary>
public bool WriteIndented { get; set; }
private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions";
private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext";
private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter";
+ private const string JsonCommentHandlingTypeRef = "global::System.Text.Json.JsonCommentHandling";
private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter";
private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory";
private const string JsonCollectionInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues";
private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition";
+ private const string JsonSerializerDefaultsTypeRef = "global::System.Text.Json.JsonSerializerDefaults";
private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling";
private const string JsonObjectCreationHandlingTypeRef = "global::System.Text.Json.Serialization.JsonObjectCreationHandling";
private const string JsonUnmappedMemberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonUnmappedMemberHandling";
+ private const string JsonUnknownTypeHandlingTypeRef = "global::System.Text.Json.Serialization.JsonUnknownTypeHandling";
private const string JsonMetadataServicesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonMetadataServices";
private const string JsonObjectInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues";
private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues";
};
{{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{createCollectionMethodExpr}};
- {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}};
+ {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{FormatNumberHandling(typeGenerationSpec.NumberHandling)}};
""");
GenerateTypeInfoFactoryFooter(writer);
};
{{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}});
- {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}};
+ {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{FormatNumberHandling(typeMetadata.NumberHandling)}};
""");
if (typeMetadata is { UnmappedMemberHandling: not null } or { PreferredPropertyObjectCreationHandling: not null })
if (typeMetadata.UnmappedMemberHandling != null)
{
- writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};");
+ writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{UnmappedMemberHandlingPropName} = {FormatUnmappedMemberHandling(typeMetadata.UnmappedMemberHandling.Value)};");
}
if (typeMetadata.PreferredPropertyObjectCreationHandling != null)
{
- writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};");
+ writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {FormatObjectCreationHandling(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};");
}
}
IgnoreCondition = {{ignoreConditionNamedArg}},
HasJsonInclude = {{FormatBool(property.HasJsonInclude)}},
IsExtensionData = {{FormatBool(property.IsExtensionData)}},
- NumberHandling = {{GetNumberHandlingAsStr(property.NumberHandling)}},
+ NumberHandling = {{FormatNumberHandling(property.NumberHandling)}},
PropertyName = {{FormatStringLiteral(property.MemberName)}},
JsonPropertyName = {{FormatStringLiteral(property.JsonPropertyName)}}
};
if (property.ObjectCreationHandling != null)
{
- writer.WriteLine($"properties[{i}].ObjectCreationHandling = {GetObjectCreationHandlingAsStr(property.ObjectCreationHandling.Value)};");
+ writer.WriteLine($"properties[{i}].ObjectCreationHandling = {FormatObjectCreationHandling(property.ObjectCreationHandling.Value)};");
}
writer.WriteLine();
continue;
}
- string runtimePropName = propertyGenSpec.RuntimePropertyName;
- string propNameVarName = propertyGenSpec.PropertyNameVarName;
+ string effectiveJsonPropertyName = propertyGenSpec.EffectiveJsonPropertyName;
+ string propertyNameFieldName = propertyGenSpec.PropertyNameFieldName;
// Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation.
- Debug.Assert(!_propertyNames.TryGetValue(runtimePropName, out string? existingName) || existingName == propNameVarName);
- _propertyNames.TryAdd(runtimePropName, propNameVarName);
+ Debug.Assert(!_propertyNames.TryGetValue(effectiveJsonPropertyName, out string? existingName) || existingName == propertyNameFieldName);
+ _propertyNames.TryAdd(effectiveJsonPropertyName, propertyNameFieldName);
DefaultCheckType defaultCheckType = GetDefaultCheckType(contextSpec, propertyGenSpec);
break;
}
- GenerateSerializePropertyStatement(writer, propertyTypeSpec, propNameVarName, propValueExpr);
+ GenerateSerializePropertyStatement(writer, propertyTypeSpec, propertyNameFieldName, propValueExpr);
if (defaultCheckType != DefaultCheckType.None)
{
private static DefaultCheckType GetDefaultCheckType(ContextGenerationSpec contextSpec, PropertyGenerationSpec propertySpec)
{
- return (propertySpec.DefaultIgnoreCondition ?? contextSpec.DefaultIgnoreCondition) switch
+ return (propertySpec.DefaultIgnoreCondition ?? contextSpec.GeneratedOptionsSpec?.DefaultIgnoreCondition) switch
{
JsonIgnoreCondition.WhenWritingNull => propertySpec.PropertyType.CanBeNull ? DefaultCheckType.Null : DefaultCheckType.None,
JsonIgnoreCondition.WhenWritingDefault => propertySpec.PropertyType.CanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default,
SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec, isPrimaryContextSourceFile: true);
- GetLogicForDefaultSerializerOptionsInit(contextSpec, writer);
+ GetLogicForDefaultSerializerOptionsInit(contextSpec.GeneratedOptionsSpec, writer);
writer.WriteLine();
return CompleteSourceFileAndReturnText(writer);
}
- private static void GetLogicForDefaultSerializerOptionsInit(ContextGenerationSpec contextSpec, SourceWriter writer)
+ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOptionsSpec? optionsSpec, SourceWriter writer)
{
- string? namingPolicyName = contextSpec.PropertyNamingPolicy switch
+ const string DefaultOptionsFieldDecl = $"private readonly static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName}";
+
+ if (optionsSpec is null)
{
- JsonKnownNamingPolicy.CamelCase => nameof(JsonNamingPolicy.CamelCase),
- JsonKnownNamingPolicy.SnakeCaseLower => nameof(JsonNamingPolicy.SnakeCaseLower),
- JsonKnownNamingPolicy.SnakeCaseUpper => nameof(JsonNamingPolicy.SnakeCaseUpper),
- JsonKnownNamingPolicy.KebabCaseLower => nameof(JsonNamingPolicy.KebabCaseLower),
- JsonKnownNamingPolicy.KebabCaseUpper => nameof(JsonNamingPolicy.KebabCaseUpper),
- _ => null,
- };
+ writer.WriteLine($"{DefaultOptionsFieldDecl} = new();");
+ return;
+ }
- string namingPolicy = namingPolicyName != null
- ? $"{JsonNamingPolicyTypeRef}.{namingPolicyName}"
- : "null";
+ if (optionsSpec.Defaults is JsonSerializerDefaults defaults)
+ {
+ writer.WriteLine($"{DefaultOptionsFieldDecl} = new({FormatJsonSerializerDefaults(defaults)})");
+ }
+ else
+ {
+ writer.WriteLine($"{DefaultOptionsFieldDecl} = new()");
+ }
- writer.WriteLine($$"""
- private readonly static {{JsonSerializerOptionsTypeRef}} {{DefaultOptionsStaticVarName}} = new()
+ writer.WriteLine('{');
+ writer.Indentation++;
+
+ if (optionsSpec.AllowTrailingCommas is bool allowTrailingCommas)
+ writer.WriteLine($"AllowTrailingCommas = {FormatBool(allowTrailingCommas)},");
+
+ if (optionsSpec.Converters is { Count: > 0 } converters)
+ {
+ writer.WriteLine("Converters =");
+ writer.WriteLine('{');
+ writer.Indentation++;
+
+ foreach (TypeRef converter in converters)
{
- DefaultIgnoreCondition = {{JsonIgnoreConditionTypeRef}}.{{contextSpec.DefaultIgnoreCondition}},
- IgnoreReadOnlyFields = {{FormatBool(contextSpec.IgnoreReadOnlyFields)}},
- IgnoreReadOnlyProperties = {{FormatBool(contextSpec.IgnoreReadOnlyProperties)}},
- IncludeFields = {{FormatBool(contextSpec.IncludeFields)}},
- WriteIndented = {{FormatBool(contextSpec.WriteIndented)}},
- PropertyNamingPolicy = {{namingPolicy}}
+ writer.WriteLine($"new {converter.FullyQualifiedName}(),");
+ }
+
+ writer.Indentation--;
+ writer.WriteLine("},");
+ }
+
+ if (optionsSpec.DefaultBufferSize is int defaultBufferSize)
+ writer.WriteLine($"DefaultBufferSize = {defaultBufferSize},");
+
+ if (optionsSpec.DefaultIgnoreCondition is JsonIgnoreCondition defaultIgnoreCondition)
+ writer.WriteLine($"DefaultIgnoreCondition = {FormatIgnoreCondition(defaultIgnoreCondition)},");
+
+ if (optionsSpec.DictionaryKeyPolicy is JsonKnownNamingPolicy dictionaryKeyPolicy)
+ writer.WriteLine($"DictionaryKeyPolicy = {FormatNamingPolicy(dictionaryKeyPolicy)},");
+
+ if (optionsSpec.IgnoreReadOnlyFields is bool ignoreReadOnlyFields)
+ writer.WriteLine($"IgnoreReadOnlyFields = {FormatBool(ignoreReadOnlyFields)},");
+
+ if (optionsSpec.IgnoreReadOnlyProperties is bool ignoreReadOnlyProperties)
+ writer.WriteLine($"IgnoreReadOnlyProperties = {FormatBool(ignoreReadOnlyProperties)},");
+
+ if (optionsSpec.IncludeFields is bool includeFields)
+ writer.WriteLine($"IncludeFields = {FormatBool(includeFields)},");
+
+ if (optionsSpec.MaxDepth is int maxDepth)
+ writer.WriteLine($"MaxDepth = {maxDepth},");
+
+ if (optionsSpec.NumberHandling is JsonNumberHandling numberHandling)
+ writer.WriteLine($"NumberHandling = {FormatNumberHandling(numberHandling)},");
+
+ if (optionsSpec.PreferredObjectCreationHandling is JsonObjectCreationHandling preferredObjectCreationHandling)
+ writer.WriteLine($"PreferredObjectCreationHandling = {FormatObjectCreationHandling(preferredObjectCreationHandling)},");
+
+ if (optionsSpec.PropertyNameCaseInsensitive is bool propertyNameCaseInsensitive)
+ writer.WriteLine($"PropertyNameCaseInsensitive = {FormatBool(propertyNameCaseInsensitive)},");
+
+ if (optionsSpec.PropertyNamingPolicy is JsonKnownNamingPolicy propertyNamingPolicy)
+ writer.WriteLine($"PropertyNamingPolicy = {FormatNamingPolicy(propertyNamingPolicy)},");
+
+ if (optionsSpec.ReadCommentHandling is JsonCommentHandling readCommentHandling)
+ writer.WriteLine($"ReadCommentHandling = {FormatCommentHandling(readCommentHandling)},");
+
+ if (optionsSpec.UnknownTypeHandling is JsonUnknownTypeHandling unknownTypeHandling)
+ writer.WriteLine($"UnknownTypeHandling = {FormatUnknownTypeHandling(unknownTypeHandling)},");
+
+ if (optionsSpec.UnmappedMemberHandling is JsonUnmappedMemberHandling unmappedMemberHandling)
+ writer.WriteLine($"UnmappedMemberHandling = {FormatUnmappedMemberHandling(unmappedMemberHandling)},");
+
+ if (optionsSpec.WriteIndented is bool writeIndented)
+ writer.WriteLine($"WriteIndented = {FormatBool(writeIndented)},");
+
+ writer.Indentation--;
+ writer.WriteLine("};");
+
+ static string FormatNamingPolicy(JsonKnownNamingPolicy knownNamingPolicy)
+ {
+ string? policyName = knownNamingPolicy switch
+ {
+ JsonKnownNamingPolicy.CamelCase => nameof(JsonNamingPolicy.CamelCase),
+ JsonKnownNamingPolicy.SnakeCaseLower => nameof(JsonNamingPolicy.SnakeCaseLower),
+ JsonKnownNamingPolicy.SnakeCaseUpper => nameof(JsonNamingPolicy.SnakeCaseUpper),
+ JsonKnownNamingPolicy.KebabCaseLower => nameof(JsonNamingPolicy.KebabCaseLower),
+ JsonKnownNamingPolicy.KebabCaseUpper => nameof(JsonNamingPolicy.KebabCaseUpper),
+ _ => null,
};
- """);
+
+ return policyName != null
+ ? $"{JsonNamingPolicyTypeRef}.{policyName}"
+ : "null";
+ }
}
private static void GenerateConverterHelpers(SourceWriter writer, bool emitGetConverterForNullablePropertyMethod)
return CompleteSourceFileAndReturnText(writer);
}
- private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling)
+ private static string FormatNumberHandling(JsonNumberHandling? numberHandling)
=> numberHandling.HasValue
? SourceGeneratorHelpers.FormatEnumLiteral(JsonNumberHandlingTypeRef, numberHandling.Value)
: "null";
- private static string GetObjectCreationHandlingAsStr(JsonObjectCreationHandling creationHandling)
+ private static string FormatObjectCreationHandling(JsonObjectCreationHandling creationHandling)
=> SourceGeneratorHelpers.FormatEnumLiteral(JsonObjectCreationHandlingTypeRef, creationHandling);
- private static string GetUnmappedMemberHandlingAsStr(JsonUnmappedMemberHandling unmappedMemberHandling)
+ private static string FormatUnmappedMemberHandling(JsonUnmappedMemberHandling unmappedMemberHandling)
=> SourceGeneratorHelpers.FormatEnumLiteral(JsonUnmappedMemberHandlingTypeRef, unmappedMemberHandling);
+ private static string FormatCommentHandling(JsonCommentHandling commentHandling)
+ => SourceGeneratorHelpers.FormatEnumLiteral(JsonCommentHandlingTypeRef, commentHandling);
+
+ private static string FormatUnknownTypeHandling(JsonUnknownTypeHandling commentHandling)
+ => SourceGeneratorHelpers.FormatEnumLiteral(JsonUnknownTypeHandlingTypeRef, commentHandling);
+
+ private static string FormatIgnoreCondition(JsonIgnoreCondition ignoreCondition)
+ => SourceGeneratorHelpers.FormatEnumLiteral(JsonIgnoreConditionTypeRef, ignoreCondition);
+
+ private static string FormatJsonSerializerDefaults(JsonSerializerDefaults defaults)
+ => SourceGeneratorHelpers.FormatEnumLiteral(JsonSerializerDefaultsTypeRef, defaults);
+
private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";
private static string FormatBool(bool value) => value ? "true" : "false";
// Algorithm should be kept in sync with the reflection equivalent in JsonTypeInfo.cs
string memberName = propertySpec.MemberName;
- if (state.AddedProperties.TryAdd(propertySpec.RuntimePropertyName, (propertySpec, state.Properties.Count)))
+ if (state.AddedProperties.TryAdd(propertySpec.EffectiveJsonPropertyName, (propertySpec, state.Properties.Count)))
{
state.Properties.Add(propertySpec);
}
else
{
// The JsonPropertyNameAttribute or naming policy resulted in a collision.
- (PropertyGenerationSpec other, int index) = state.AddedProperties[propertySpec.RuntimePropertyName];
+ (PropertyGenerationSpec other, int index) = state.AddedProperties[propertySpec.EffectiveJsonPropertyName];
if (other.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
{
// Overwrite previously cached property since it has [JsonIgnore].
- state.AddedProperties[propertySpec.RuntimePropertyName] = (propertySpec, index);
+ state.AddedProperties[propertySpec.EffectiveJsonPropertyName] = (propertySpec, index);
state.Properties[index] = propertySpec;
}
else
if (!TryParseJsonSerializerContextAttributes(
contextTypeSymbol,
out List<TypeToGenerate>? rootSerializableTypes,
- out JsonSourceGenerationOptionsAttribute? options))
+ out SourceGenerationOptionsSpec? options))
{
// Context does not specify any source gen attributes.
return null;
return null;
}
- options ??= new JsonSourceGenerationOptionsAttribute();
-
// Enqueue attribute data for spec generation
foreach (TypeToGenerate rootSerializableType in rootSerializableTypes)
{
GeneratedTypes = _generatedTypes.Values.OrderBy(t => t.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(),
Namespace = contextTypeSymbol.ContainingNamespace is { IsGlobalNamespace: false } ns ? ns.ToDisplayString() : null,
ContextClassDeclarations = classDeclarationList.ToImmutableEquatableArray(),
- DefaultIgnoreCondition = options.DefaultIgnoreCondition,
- IgnoreReadOnlyFields = options.IgnoreReadOnlyFields,
- IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties,
- IncludeFields = options.IncludeFields,
- PropertyNamingPolicy = options.PropertyNamingPolicy,
- WriteIndented = options.WriteIndented,
+ GeneratedOptionsSpec = options,
};
// Clear the caches of generated metadata between the processing of context classes.
}
private bool TryParseJsonSerializerContextAttributes(
- ITypeSymbol contextClassSymbol,
+ INamedTypeSymbol contextClassSymbol,
out List<TypeToGenerate>? rootSerializableTypes,
- out JsonSourceGenerationOptionsAttribute? options)
+ out SourceGenerationOptionsSpec? options)
{
Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null);
Debug.Assert(_knownSymbols.JsonSourceGenerationOptionsAttributeType != null);
}
else if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSourceGenerationOptionsAttributeType))
{
- options = ParseJsonSourceGenerationOptionsAttribute(attributeData);
+ options = ParseJsonSourceGenerationOptionsAttribute(contextClassSymbol, attributeData);
}
}
return rootSerializableTypes != null || options != null;
}
- private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOptionsAttribute(AttributeData attributeData)
+ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(INamedTypeSymbol contextType, AttributeData attributeData)
{
- JsonSourceGenerationOptionsAttribute options = new();
+ JsonSourceGenerationMode? generationMode = null;
+ List<TypeRef>? converters = null;
+ JsonSerializerDefaults? defaults = null;
+ bool? allowTrailingCommas = null;
+ int? defaultBufferSize = null;
+ JsonIgnoreCondition? defaultIgnoreCondition = null;
+ JsonKnownNamingPolicy? dictionaryKeyPolicy = null;
+ bool? ignoreReadOnlyFields = null;
+ bool? ignoreReadOnlyProperties = null;
+ bool? includeFields = null;
+ int? maxDepth = null;
+ JsonNumberHandling? numberHandling = null;
+ JsonObjectCreationHandling? preferredObjectCreationHandling = null;
+ bool? propertyNameCaseInsensitive = null;
+ JsonKnownNamingPolicy? propertyNamingPolicy = null;
+ JsonCommentHandling? readCommentHandling = null;
+ JsonUnknownTypeHandling? unknownTypeHandling = null;
+ JsonUnmappedMemberHandling? unmappedMemberHandling = null;
+ bool? writeIndented = null;
+
+ if (attributeData.ConstructorArguments.Length > 0)
+ {
+ Debug.Assert(attributeData.ConstructorArguments.Length == 1 & attributeData.ConstructorArguments[0].Type?.Name is nameof(JsonSerializerDefaults));
+ defaults = (JsonSerializerDefaults)attributeData.ConstructorArguments[0].Value!;
+ }
foreach (KeyValuePair<string, TypedConstant> namedArg in attributeData.NamedArguments)
{
switch (namedArg.Key)
{
+ case nameof(JsonSourceGenerationOptionsAttribute.AllowTrailingCommas):
+ allowTrailingCommas = (bool)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.Converters):
+ converters = new List<TypeRef>();
+ foreach (TypedConstant element in namedArg.Value.Values)
+ {
+ var converterType = (ITypeSymbol?)element.Value;
+ TypeRef? typeRef = GetConverterTypeFromAttribute(contextType, converterType, contextType, attributeData);
+ if (typeRef != null)
+ {
+ converters.Add(typeRef);
+ }
+ }
+
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.DefaultBufferSize):
+ defaultBufferSize = (int)namedArg.Value.Value!;
+ break;
+
case nameof(JsonSourceGenerationOptionsAttribute.DefaultIgnoreCondition):
- options.DefaultIgnoreCondition = (JsonIgnoreCondition)namedArg.Value.Value!;
+ defaultIgnoreCondition = (JsonIgnoreCondition)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.DictionaryKeyPolicy):
+ dictionaryKeyPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!;
break;
case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyFields):
- options.IgnoreReadOnlyFields = (bool)namedArg.Value.Value!;
+ ignoreReadOnlyFields = (bool)namedArg.Value.Value!;
break;
case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyProperties):
- options.IgnoreReadOnlyProperties = (bool)namedArg.Value.Value!;
+ ignoreReadOnlyProperties = (bool)namedArg.Value.Value!;
break;
case nameof(JsonSourceGenerationOptionsAttribute.IncludeFields):
- options.IncludeFields = (bool)namedArg.Value.Value!;
+ includeFields = (bool)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.MaxDepth):
+ maxDepth = (int)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.NumberHandling):
+ numberHandling = (JsonNumberHandling)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling):
+ preferredObjectCreationHandling = (JsonObjectCreationHandling)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.PropertyNameCaseInsensitive):
+ propertyNameCaseInsensitive = (bool)namedArg.Value.Value!;
break;
case nameof(JsonSourceGenerationOptionsAttribute.PropertyNamingPolicy):
- options.PropertyNamingPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!;
+ propertyNamingPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.ReadCommentHandling):
+ readCommentHandling = (JsonCommentHandling)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.UnknownTypeHandling):
+ unknownTypeHandling = (JsonUnknownTypeHandling)namedArg.Value.Value!;
+ break;
+
+ case nameof(JsonSourceGenerationOptionsAttribute.UnmappedMemberHandling):
+ unmappedMemberHandling = (JsonUnmappedMemberHandling)namedArg.Value.Value!;
break;
case nameof(JsonSourceGenerationOptionsAttribute.WriteIndented):
- options.WriteIndented = (bool)namedArg.Value.Value!;
+ writeIndented = (bool)namedArg.Value.Value!;
break;
case nameof(JsonSourceGenerationOptionsAttribute.GenerationMode):
- options.GenerationMode = (JsonSourceGenerationMode)namedArg.Value.Value!;
+ generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!;
break;
default:
}
}
- return options;
+ return new SourceGenerationOptionsSpec
+ {
+ GenerationMode = generationMode,
+ Defaults = defaults,
+ AllowTrailingCommas = allowTrailingCommas,
+ DefaultBufferSize = defaultBufferSize,
+ Converters = converters?.ToImmutableEquatableArray(),
+ DefaultIgnoreCondition = defaultIgnoreCondition,
+ DictionaryKeyPolicy = dictionaryKeyPolicy,
+ IgnoreReadOnlyFields = ignoreReadOnlyFields,
+ IgnoreReadOnlyProperties = ignoreReadOnlyProperties,
+ IncludeFields = includeFields,
+ MaxDepth = maxDepth,
+ NumberHandling = numberHandling,
+ PreferredObjectCreationHandling = preferredObjectCreationHandling,
+ PropertyNameCaseInsensitive = propertyNameCaseInsensitive,
+ PropertyNamingPolicy = propertyNamingPolicy,
+ ReadCommentHandling = readCommentHandling,
+ UnknownTypeHandling = unknownTypeHandling,
+ UnmappedMemberHandling = unmappedMemberHandling,
+ WriteIndented = writeIndented,
+ };
}
private static TypeToGenerate? ParseJsonSerializableAttribute(AttributeData attributeData)
};
}
- private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGenerate, INamedTypeSymbol contextType, Location contextLocation, JsonSourceGenerationOptionsAttribute options)
+ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGenerate, INamedTypeSymbol contextType, Location contextLocation, SourceGenerationOptionsSpec? options)
{
Debug.Assert(IsSymbolAccessibleWithin(typeToGenerate.Type, within: contextType), "should not generate metadata for inaccessible types.");
{
TypeRef = typeRef,
TypeInfoPropertyName = typeInfoPropertyName,
- GenerationMode = typeToGenerate.Mode ?? options.GenerationMode,
+ GenerationMode = typeToGenerate.Mode ?? options?.GenerationMode ?? JsonSourceGenerationMode.Default,
ClassType = classType,
PrimitiveTypeKind = primitiveTypeKind,
IsPolymorphic = isPolymorphic,
}
else if (!foundJsonConverterAttribute && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType))
{
- customConverterType = GetConverterTypeFromAttribute(contextType, typeToGenerate.Type, attributeData);
+ customConverterType = GetConverterTypeFromJsonConverterAttribute(contextType, typeToGenerate.Type, attributeData);
foundJsonConverterAttribute = true;
}
INamedTypeSymbol contextType,
in TypeToGenerate typeToGenerate,
Location typeLocation,
- JsonSourceGenerationOptionsAttribute options,
+ SourceGenerationOptionsSpec? options,
out bool hasExtensionDataProperty)
{
List<PropertyGenerationSpec> properties = new();
ISymbol memberInfo,
ref bool typeHasExtensionDataProperty,
JsonSourceGenerationMode? generationMode,
- JsonSourceGenerationOptionsAttribute options)
+ SourceGenerationOptionsSpec? options)
{
Debug.Assert(memberInfo is IFieldSymbol or IPropertySymbol);
return null;
}
- string clrName = memberInfo.Name;
- string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, options.PropertyNamingPolicy);
- string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName);
+ string effectiveJsonPropertyName = DetermineEffectiveJsonPropertyName(memberInfo.Name, jsonPropertyName, options);
+ string propertyNameFieldName = DeterminePropertyNameFieldName(effectiveJsonPropertyName);
// Enqueue the property type for generation, unless the member is ignored.
TypeRef propertyTypeRef = ignoreCondition != JsonIgnoreCondition.Always
IsPublic = isAccessible,
IsVirtual = memberInfo.IsVirtual(),
JsonPropertyName = jsonPropertyName,
- RuntimePropertyName = runtimePropertyName,
- PropertyNameVarName = propertyNameVarName,
+ EffectiveJsonPropertyName = effectiveJsonPropertyName,
+ PropertyNameFieldName = propertyNameFieldName,
IsReadOnly = isReadOnly,
IsRequired = isRequired,
HasJsonRequiredAttribute = hasJsonRequiredAttribute,
if (converterType is null && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType))
{
- converterType = GetConverterTypeFromAttribute(contextType, memberInfo, attributeData);
+ converterType = GetConverterTypeFromJsonConverterAttribute(contextType, memberInfo, attributeData);
}
else if (attributeType.ContainingAssembly.Name == SystemTextJsonNamespace)
{
return propertyInitializers;
}
- private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, ISymbol declaringSymbol, AttributeData attributeData)
+ private TypeRef? GetConverterTypeFromJsonConverterAttribute(INamedTypeSymbol contextType, ISymbol declaringSymbol, AttributeData attributeData)
{
Debug.Assert(_knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeData.AttributeClass));
- var converterType = (INamedTypeSymbol?)attributeData.ConstructorArguments[0].Value;
+ var converterType = (ITypeSymbol?)attributeData.ConstructorArguments[0].Value;
+ return GetConverterTypeFromAttribute(contextType, converterType, declaringSymbol, attributeData);
+ }
- if (converterType == null ||
- !_knownSymbols.JsonConverterType.IsAssignableFrom(converterType) ||
- !converterType.Constructors.Any(c => c.Parameters.Length == 0 && IsSymbolAccessibleWithin(c, within: contextType)))
+ private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, ITypeSymbol? converterType, ISymbol declaringSymbol, AttributeData attributeData)
+ {
+ if (converterType is not INamedTypeSymbol namedConverterType ||
+ !_knownSymbols.JsonConverterType.IsAssignableFrom(namedConverterType) ||
+ !namedConverterType.Constructors.Any(c => c.Parameters.Length == 0 && IsSymbolAccessibleWithin(c, within: contextType)))
{
ReportDiagnostic(DiagnosticDescriptors.JsonConverterAttributeInvalidType, attributeData.GetDiagnosticLocation(), converterType?.ToDisplayString() ?? "null", declaringSymbol.ToDisplayString());
return null;
return new TypeRef(converterType);
}
- private static string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy)
+ private static string DetermineEffectiveJsonPropertyName(string propertyName, string? jsonPropertyName, SourceGenerationOptionsSpec? options)
{
- string runtimePropName;
-
- if (jsonPropName != null)
+ if (jsonPropertyName != null)
{
- runtimePropName = jsonPropName;
+ return jsonPropertyName;
}
- else
- {
- JsonNamingPolicy? instance = namingPolicy switch
- {
- JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase,
- JsonKnownNamingPolicy.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower,
- JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper,
- JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower,
- JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper,
- _ => null,
- };
- runtimePropName = instance?.ConvertName(clrPropName) ?? clrPropName;
- }
+ JsonNamingPolicy? instance = options?.GetEffectivePropertyNamingPolicy() switch
+ {
+ JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase,
+ JsonKnownNamingPolicy.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower,
+ JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper,
+ JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower,
+ JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper,
+ _ => null,
+ };
- return runtimePropName;
+ return instance?.ConvertName(propertyName) ?? propertyName;
}
private static string? DetermineImmutableCollectionFactoryMethod(string? immutableCollectionFactoryTypeFullName)
return immutableCollectionFactoryTypeFullName is not null ? $"global::{immutableCollectionFactoryTypeFullName}.CreateRange" : null;
}
- private static string DeterminePropNameIdentifier(string runtimePropName)
+ private static string DeterminePropertyNameFieldName(string effectiveJsonPropertyName)
{
const string PropName = "PropName_";
// the rare case there is a C# property in a hex format.
const string EncodedPropName = "EncodedPropName_";
- if (SyntaxFacts.IsValidIdentifier(runtimePropName))
+ if (SyntaxFacts.IsValidIdentifier(effectiveJsonPropertyName))
{
- return PropName + runtimePropName;
+ return PropName + effectiveJsonPropertyName;
}
// Encode the string to a byte[] and then convert to hexadecimal.
// To make the generated code more readable, we could use a different strategy in the future
// such as including the full class name + the CLR property name when there are duplicates,
// but that will create unnecessary JsonEncodedText properties.
- byte[] utf8Json = Encoding.UTF8.GetBytes(runtimePropName);
+ byte[] utf8Json = Encoding.UTF8.GetBytes(effectiveJsonPropertyName);
StringBuilder sb = new StringBuilder(
EncodedPropName,
public required ImmutableEquatableArray<string> ContextClassDeclarations { get; init; }
- public required JsonIgnoreCondition DefaultIgnoreCondition { get; init; }
-
- public required bool IgnoreReadOnlyFields { get; init; }
-
- public required bool IgnoreReadOnlyProperties { get; init; }
-
- public required bool IncludeFields { get; init; }
-
- public required JsonKnownNamingPolicy PropertyNamingPolicy { get; init; }
-
- public required bool WriteIndented { get; init; }
+ public required SourceGenerationOptionsSpec? GeneratedOptionsSpec { get; init; }
}
}
/// specified ahead-of-time via <see cref="JsonSourceGenerationOptionsAttribute"/>.
/// Only used in fast-path serialization logic.
/// </summary>
- public required string RuntimePropertyName { get; init; }
+ public required string EffectiveJsonPropertyName { get; init; }
- public required string PropertyNameVarName { get; init; }
+ /// <summary>
+ /// The field identifier used for storing JsonEncodedText for use by the fast-path serializer.
+ /// </summary>
+ public required string PropertyNameFieldName { get; init; }
/// <summary>
/// Whether the property has a set method.
}
// Discard fields when JsonInclude or IncludeFields aren't enabled.
- if (!IsProperty && !HasJsonInclude && !contextSpec.IncludeFields)
+ if (!IsProperty && !HasJsonInclude && contextSpec.GeneratedOptionsSpec?.IncludeFields != true)
{
return false;
}
{
if (IsProperty)
{
- if (contextSpec.IgnoreReadOnlyProperties)
+ if (contextSpec.GeneratedOptionsSpec?.IgnoreReadOnlyProperties == true)
{
return false;
}
}
- else if (contextSpec.IgnoreReadOnlyFields)
+ else if (contextSpec.GeneratedOptionsSpec?.IgnoreReadOnlyFields == true)
{
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.Text.Json.Serialization;
+
+namespace System.Text.Json.SourceGeneration
+{
+ /// <summary>
+ /// Models compile-time configuration of <see cref="JsonSourceGenerationOptionsAttribute"/>.
+ /// Properties are made nullable to model the presence or absence of a given configuration.
+ /// </summary>
+ public sealed record SourceGenerationOptionsSpec
+ {
+ public required JsonSourceGenerationMode? GenerationMode { get; init; }
+
+ public required JsonSerializerDefaults? Defaults { get; init; }
+
+ public required bool? AllowTrailingCommas { get; init; }
+
+ public required ImmutableEquatableArray<TypeRef>? Converters { get; init; }
+
+ public required int? DefaultBufferSize { get; init; }
+
+ public required JsonIgnoreCondition? DefaultIgnoreCondition { get; init; }
+
+ public required JsonKnownNamingPolicy? DictionaryKeyPolicy { get; init; }
+
+ public required bool? IgnoreReadOnlyFields { get; init; }
+
+ public required bool? IgnoreReadOnlyProperties { get; init; }
+
+ public required bool? IncludeFields { get; init; }
+
+ public required int? MaxDepth { get; init; }
+
+ public required JsonNumberHandling? NumberHandling { get; init; }
+
+ public required JsonObjectCreationHandling? PreferredObjectCreationHandling { get; init; }
+
+ public required bool? PropertyNameCaseInsensitive { get; init; }
+
+ public required JsonKnownNamingPolicy? PropertyNamingPolicy { get; init; }
+
+ public required JsonCommentHandling? ReadCommentHandling { get; init; }
+
+ public required JsonUnknownTypeHandling? UnknownTypeHandling { get; init; }
+
+ public required JsonUnmappedMemberHandling? UnmappedMemberHandling { get; init; }
+
+ public required bool? WriteIndented { get; init; }
+
+ public JsonKnownNamingPolicy? GetEffectivePropertyNamingPolicy()
+ => PropertyNamingPolicy ?? (Defaults is JsonSerializerDefaults.Web ? JsonKnownNamingPolicy.CamelCase : null);
+ }
+}
<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\JsonCommentHandling.cs" Link="Common\System\Text\Json\JsonCommentHandling.cs" />
<Compile Include="..\Common\JsonConstants.cs" Link="Common\System\Text\Json\JsonConstants.cs" />
<Compile Include="..\Common\JsonHelpers.cs" Link="Common\System\Text\Json\JsonHelpers.cs" />
<Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
<Compile Include="..\Common\JsonObjectCreationHandling.cs" Link="Common\System\Text\Json\Serialization\JsonObjectCreationHandling.cs" />
<Compile Include="..\Common\JsonSeparatorNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSeparatorNamingPolicy.cs" />
<Compile Include="..\Common\JsonSerializableAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializableAttribute.cs" />
+ <Compile Include="..\Common\JsonSerializerDefaults.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerDefaults.cs" />
<Compile Include="..\Common\JsonSnakeCaseLowerNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseLowerNamingPolicy.cs" />
<Compile Include="..\Common\JsonSnakeCaseUpperNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseUpperNamingPolicy.cs" />
<Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
<Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
+ <Compile Include="..\Common\JsonUnknownTypeHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
<Compile Include="..\Common\JsonUnmappedMemberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnmappedMemberHandling.cs" />
<Compile Include="..\Common\ThrowHelper.cs" Link="Common\System\Text\Json\ThrowHelper.cs" />
<Compile Include="$(CommonPath)\Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
<Compile Include="Model\ParameterGenerationSpec.cs" />
<Compile Include="Model\PropertyGenerationSpec.cs" />
<Compile Include="Model\PropertyInitializerGenerationSpec.cs" />
+ <Compile Include="Model\SourceGenerationOptionsSpec.cs" />
<Compile Include="Model\TypeGenerationSpec.cs" />
<Compile Include="Model\TypeRef.cs" />
</ItemGroup>
public sealed partial class JsonSourceGenerationOptionsAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonSourceGenerationOptionsAttribute() { }
+ public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefaults defaults) { }
+ public bool AllowTrailingCommas { get { throw null; } set { } }
+ public System.Type[]? Converters { get { throw null; } set { } }
+ public int DefaultBufferSize { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonIgnoreCondition DefaultIgnoreCondition { get { throw null; } set { } }
+ public System.Text.Json.Serialization.JsonKnownNamingPolicy DictionaryKeyPolicy { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonSourceGenerationMode GenerationMode { get { throw null; } set { } }
public bool IgnoreReadOnlyFields { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public bool IncludeFields { get { throw null; } set { } }
+ public int MaxDepth { get { throw null; } set { } }
+ public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get { throw null; } set { } }
+ public System.Text.Json.Serialization.JsonObjectCreationHandling PreferredObjectCreationHandling { get { throw null; } set { } }
+ public bool PropertyNameCaseInsensitive { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonKnownNamingPolicy PropertyNamingPolicy { get { throw null; } set { } }
+ public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
+ public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
+ public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } set { } }
public bool WriteIndented { get { throw null; } set { } }
}
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JsonStringEnumConverter cannot be statically analyzed and requires runtime code generation. Applications should use the generic JsonStringEnumConverter<TEnum> instead.")]
<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\JsonCommentHandling.cs" Link="Common\System\Text\Json\JsonCommentHandling.cs" />
<Compile Include="..\Common\JsonConstants.cs" Link="Common\System\Text\Json\JsonConstants.cs" />
<Compile Include="..\Common\JsonHelpers.cs" Link="Common\System\Text\Json\JsonHelpers.cs" />
<Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
<Compile Include="..\Common\JsonUnmappedMemberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnmappedMemberHandling.cs" />
<Compile Include="..\Common\JsonSeparatorNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSeparatorNamingPolicy.cs" />
<Compile Include="..\Common\JsonSerializableAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializableAttribute.cs" />
+ <Compile Include="..\Common\JsonSerializerDefaults.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerDefaults.cs" />
<Compile Include="..\Common\JsonSnakeCaseLowerNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseLowerNamingPolicy.cs" />
<Compile Include="..\Common\JsonSnakeCaseUpperNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseUpperNamingPolicy.cs" />
<Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
<Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
+ <Compile Include="..\Common\JsonUnknownTypeHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
<Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
<Compile Include="..\Common\ThrowHelper.cs" Link="Common\System\Text\Json\ThrowHelper.cs" />
<Compile Include="System\Text\Json\AppContextSwitchHelper.cs" />
<Compile Include="System\Text\Json\Document\JsonElement.Parse.cs" />
<Compile Include="System\Text\Json\Document\JsonProperty.cs" />
<Compile Include="System\Text\Json\Document\JsonValueKind.cs" />
- <Compile Include="System\Text\Json\JsonCommentHandling.cs" />
<Compile Include="System\Text\Json\JsonConstants.cs" />
<Compile Include="System\Text\Json\JsonEncodedText.cs" />
<Compile Include="System\Text\Json\JsonException.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.String.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Utf8JsonWriter.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonSerializerDefaults.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.Caching.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.Converters.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.cs" />
<Compile Include="System\Text\Json\Serialization\JsonStringEnumConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\FSharpCoreReflectionProxy.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonCollectionInfoValuesOfTCollection.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
JsonSerializerOptions? generatedSerializerOptions = GeneratedSerializerOptions;
- if (ReferenceEquals(options, generatedSerializerOptions))
- {
- // Fast path for the 99% case
- return true;
- }
-
return
generatedSerializerOptions is not null &&
// Guard against unsupported features
options.Converters.Count == 0 &&
- options.Encoder == null &&
+ options.Encoder is null &&
// Disallow custom number handling we'd need to honor when writing.
// AllowReadingFromString and Strict are fine since there's no action to take when writing.
!JsonHelpers.RequiresSpecialNumberHandlingOnWrite(options.NumberHandling) &&
options.IgnoreReadOnlyProperties == generatedSerializerOptions.IgnoreReadOnlyProperties &&
options.IncludeFields == generatedSerializerOptions.IncludeFields &&
options.PropertyNamingPolicy == generatedSerializerOptions.PropertyNamingPolicy &&
- options.DictionaryKeyPolicy == generatedSerializerOptions.DictionaryKeyPolicy &&
- options.WriteIndented == generatedSerializerOptions.WriteIndented;
+ options.DictionaryKeyPolicy is null;
}
/// <summary>
/// <param name="defaults"> The <see cref="JsonSerializerDefaults"/> to reason about.</param>
public JsonSerializerOptions(JsonSerializerDefaults defaults) : this()
{
+ // Should be kept in sync with equivalent overload in JsonSourceGenerationOptionsAttribute
+
if (defaults == JsonSerializerDefaults.Web)
{
_propertyNameCaseInsensitive = true;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Reflection;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Xunit;
}
}
+ public static void AssertOptionsEqual(JsonSerializerOptions expected, JsonSerializerOptions actual)
+ {
+ foreach (PropertyInfo property in typeof(JsonSerializerOptions).GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ Type propertyType = property.PropertyType;
+
+ if (property.Name == nameof(JsonSerializerOptions.IsReadOnly))
+ {
+ continue; // readonly-ness is not a structural property of JsonSerializerOptions.
+ }
+ else if (propertyType == typeof(IList<JsonConverter>))
+ {
+ var expectedConverters = (IList<JsonConverter>)property.GetValue(expected);
+ var actualConverters = (IList<JsonConverter>)property.GetValue(actual);
+ Assert.Equal(expectedConverters.Count, actualConverters.Count);
+ for (int i = 0; i < actualConverters.Count; i++)
+ {
+ Assert.IsType(expectedConverters[i].GetType(), actualConverters[i]);
+ }
+ }
+ else if (propertyType == typeof(IList<IJsonTypeInfoResolver>))
+ {
+ var list1 = (IList<IJsonTypeInfoResolver>)property.GetValue(expected);
+ var list2 = (IList<IJsonTypeInfoResolver>)property.GetValue(actual);
+ Assert.Equal(list1, list2);
+ }
+ else
+ {
+ Assert.Equal(property.GetValue(expected), property.GetValue(actual));
+ }
+ }
+ }
+
/// <summary>
/// Linq Cartesian product
/// </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.
+
+using System.Reflection;
+using System.Text.Json.Serialization;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+ public static partial class JsonSourceGenerationOptionsTests
+ {
+ [Fact]
+ public static void ContextWithGeneralSerializerDefaults_GeneratesExpectedOptions()
+ {
+ JsonSerializerOptions expected = new(JsonSerializerDefaults.General) { TypeInfoResolver = ContextWithGeneralSerializerDefaults.Default };
+ JsonSerializerOptions options = ContextWithGeneralSerializerDefaults.Default.Options;
+
+ JsonTestHelper.AssertOptionsEqual(expected, options);
+ }
+
+ [JsonSourceGenerationOptions(JsonSerializerDefaults.General)]
+ [JsonSerializable(typeof(PersonStruct))]
+ public partial class ContextWithGeneralSerializerDefaults : JsonSerializerContext
+ { }
+
+ [Fact]
+ public static void ContextWithWebSerializerDefaults_GeneratesExpectedOptions()
+ {
+ JsonSerializerOptions expected = new(JsonSerializerDefaults.Web) { TypeInfoResolver = ContextWithWebSerializerDefaults.Default };
+ JsonSerializerOptions options = ContextWithWebSerializerDefaults.Default.Options;
+
+ JsonTestHelper.AssertOptionsEqual(expected, options);
+ }
+
+ [JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
+ [JsonSerializable(typeof(PersonStruct))]
+ public partial class ContextWithWebSerializerDefaults : JsonSerializerContext
+ { }
+
+ [Fact]
+ public static void ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy_GeneratesExpectedOptions()
+ {
+ JsonSerializerOptions expected = new(JsonSerializerDefaults.Web)
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower,
+ TypeInfoResolver = ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy.Default,
+ };
+
+ JsonSerializerOptions options = ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy.Default.Options;
+
+ JsonTestHelper.AssertOptionsEqual(expected, options);
+ }
+
+ [JsonSourceGenerationOptions(JsonSerializerDefaults.Web, PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseLower)]
+ [JsonSerializable(typeof(PersonStruct))]
+ public partial class ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy : JsonSerializerContext
+ { }
+
+ [Fact]
+ public static void ContextWithAllOptionsSet_GeneratesExpectedOptions()
+ {
+ JsonSerializerOptions expected = new(JsonSerializerDefaults.Web)
+ {
+ AllowTrailingCommas = true,
+ Converters = { new JsonStringEnumConverter<BindingFlags>(), new JsonStringEnumConverter<JsonIgnoreCondition>() },
+ DefaultBufferSize = 128,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
+ DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseUpper,
+ IgnoreReadOnlyFields = true,
+ IgnoreReadOnlyProperties = true,
+ IncludeFields = true,
+ MaxDepth = 1024,
+ NumberHandling = JsonNumberHandling.WriteAsString,
+ PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace,
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper,
+ ReadCommentHandling = JsonCommentHandling.Skip,
+ UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
+ UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
+ WriteIndented = true,
+
+ TypeInfoResolver = ContextWithAllOptionsSet.Default,
+ };
+
+ JsonSerializerOptions options = ContextWithAllOptionsSet.Default.Options;
+
+ JsonTestHelper.AssertOptionsEqual(expected, options);
+ }
+
+ [JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
+ AllowTrailingCommas = true,
+ Converters = new[] { typeof(JsonStringEnumConverter<BindingFlags>), typeof(JsonStringEnumConverter<JsonIgnoreCondition>) },
+ DefaultBufferSize = 128,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
+ DictionaryKeyPolicy = JsonKnownNamingPolicy.SnakeCaseUpper,
+ IgnoreReadOnlyFields = true,
+ IgnoreReadOnlyProperties = true,
+ IncludeFields = true,
+ MaxDepth = 1024,
+ NumberHandling = JsonNumberHandling.WriteAsString,
+ PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace,
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseUpper,
+ ReadCommentHandling = JsonCommentHandling.Skip,
+ UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
+ UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
+ WriteIndented = true)]
+ [JsonSerializable(typeof(PersonStruct))]
+ public partial class ContextWithAllOptionsSet : JsonSerializerContext
+ { }
+
+ [Fact]
+ public static void ContextWithInvalidSerializerDefaults_ThrowsArgumentOutOfRangeException()
+ {
+ TypeInitializationException ex = Assert.Throws<TypeInitializationException>(() => ContextWithInvalidSerializerDefaults.Default);
+ ArgumentOutOfRangeException inner = Assert.IsType<ArgumentOutOfRangeException>(ex.InnerException);
+ Assert.Contains("defaults", inner.Message);
+ }
+
+ [JsonSourceGenerationOptions((JsonSerializerDefaults)(-1))]
+ [JsonSerializable(typeof(PersonStruct))]
+ public partial class ContextWithInvalidSerializerDefaults : JsonSerializerContext
+ { }
+ }
+}
<Compile Include="Serialization\CollectionTests.cs" />
<Compile Include="Serialization\ConstructorTests.cs" />
<Compile Include="Serialization\ExtensionDataTests.cs" />
+ <Compile Include="JsonSourceGenerationOptionsTests.cs" />
<Compile Include="Serialization\JsonCreationHandlingTests.cs" />
<Compile Include="Serialization\ReferenceHandlerTests.cs" />
<Compile Include="Serialization\ReferenceHandlerTests.IgnoreCycles.cs" />
var newOptions = new JsonSerializerOptions(options);
Assert.False(newOptions.IsReadOnly);
- VerifyOptionsEqual(options, newOptions);
+ JsonTestHelper.AssertOptionsEqual(options, newOptions);
// No exception is thrown on mutating the new options instance because it is "unlocked".
newOptions.ReferenceHandler = ReferenceHandler.Preserve;
{
JsonSerializerOptions options = GetFullyPopulatedOptionsInstance();
var newOptions = new JsonSerializerOptions(options);
- VerifyOptionsEqual(options, newOptions);
+ JsonTestHelper.AssertOptionsEqual(options, newOptions);
}
[Fact]
{
var options = new JsonSerializerOptions { TypeInfoResolver = JsonSerializerOptions.Default.TypeInfoResolver };
JsonSerializerOptions optionsSingleton = JsonSerializerOptions.Default;
- VerifyOptionsEqual(options, optionsSingleton);
+ JsonTestHelper.AssertOptionsEqual(options, optionsSingleton);
}
[Fact]
var options = new JsonSerializerOptions();
var newOptions = new JsonSerializerOptions(JsonSerializerDefaults.General);
Assert.False(newOptions.IsReadOnly);
- VerifyOptionsEqual(options, newOptions);
+ JsonTestHelper.AssertOptionsEqual(options, newOptions);
}
[Fact]
return options;
}
- private static void VerifyOptionsEqual(JsonSerializerOptions options, JsonSerializerOptions newOptions)
- {
- foreach (PropertyInfo property in typeof(JsonSerializerOptions).GetProperties(BindingFlags.Public | BindingFlags.Instance))
- {
- Type propertyType = property.PropertyType;
-
- if (property.Name == nameof(JsonSerializerOptions.IsReadOnly))
- {
- continue; // readonly-ness is not a structural property of JsonSerializerOptions.
- }
- else if (propertyType == typeof(IList<JsonConverter>))
- {
- var list1 = (IList<JsonConverter>)property.GetValue(options);
- var list2 = (IList<JsonConverter>)property.GetValue(newOptions);
- Assert.Equal(list1, list2);
- }
- else if (propertyType == typeof(IList<IJsonTypeInfoResolver>))
- {
- var list1 = (IList<IJsonTypeInfoResolver>)property.GetValue(options);
- var list2 = (IList<IJsonTypeInfoResolver>)property.GetValue(newOptions);
- Assert.Equal(list1, list2);
- }
- else if (propertyType.IsValueType)
- {
- Assert.Equal(property.GetValue(options), property.GetValue(newOptions));
- }
- else
- {
- Assert.Same(property.GetValue(options), property.GetValue(newOptions));
- }
- }
- }
-
[Fact]
public static void CopyConstructor_IgnoreNullValuesCopied()
{
var options = new JsonSerializerOptions { IgnoreNullValues = true };
var newOptions = new JsonSerializerOptions(options);
- VerifyOptionsEqual(options, newOptions);
+ JsonTestHelper.AssertOptionsEqual(options, newOptions);
}
[Fact]