Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark JsonSerializerOptions.TypeInfoResolver as nullable and linker-safe #72044

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults defaults) {
public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
public bool AllowTrailingCommas { get { throw null; } set { } }
public System.Collections.Generic.IList<System.Text.Json.Serialization.JsonConverter> Converters { get { throw null; } }
public static System.Text.Json.JsonSerializerOptions Default { get { throw null; } }
public static System.Text.Json.JsonSerializerOptions Default { [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."), System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] get { throw null; } }
public int DefaultBufferSize { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonIgnoreCondition DefaultIgnoreCondition { get { throw null; } set { } }
public System.Text.Json.JsonNamingPolicy? DictionaryKeyPolicy { get { throw null; } set { } }
Expand All @@ -356,16 +356,13 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
public System.Text.Json.JsonNamingPolicy? PropertyNamingPolicy { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } }
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]
public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver TypeInfoResolver { [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."), System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] get { throw null; } set { } }
public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver? TypeInfoResolver { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
public bool WriteIndented { get { throw null; } set { } }
public void AddContext<TContext>() where TContext : System.Text.Json.Serialization.JsonSerializerContext, new() { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Getting a converter for a type may require reflection which depends on runtime code generation.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Getting a converter for a type may require reflection which depends on unreferenced code.")]
public System.Text.Json.Serialization.JsonConverter GetConverter(System.Type typeToConvert) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Getting a metadata for a type may require reflection which depends on unreferenced code.")]
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Getting a metadata for a type may require reflection which depends on runtime code generation.")]
public System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo(System.Type type) { throw null; }
}
public enum JsonTokenType : byte
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@
<value>Built-in type converters have not been initialized. There is no converter available for type '{0}'. To root all built-in converters, use a 'JsonSerializerOptions'-based method of the 'JsonSerializer'.</value>
</data>
<data name="NoMetadataForType" xml:space="preserve">
<value>Metadata for type '{0}' was not provided to the serializer. The serializer method used does not support reflection-based creation of serialization-related type metadata. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.</value>
<value>Metadata for type '{0}' was not provided by TypeInfoResolver of type '{1}'. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.</value>
</data>
<data name="CollectionIsReadOnly" xml:space="preserve">
<value>Collection is read-only.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio
CreateNodes();
Debug.Assert(_list != null);

options ??= JsonSerializerOptions.Default;
options ??= JsonSerializerOptions.DefaultInstance;

writer.WriteStartArray();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio
}
else
{
options ??= JsonSerializerOptions.Default;
options ??= JsonSerializerOptions.DefaultInstance;

writer.WriteStartObject();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio

if (_converter != null)
{
options ??= JsonSerializerOptions.Default;
options ??= JsonSerializerOptions.DefaultInstance;

if (_converter.IsInternalConverterForNumberType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private static JsonTypeInfo GetTypeInfo(JsonSerializerContext context, Type type
JsonTypeInfo? info = context.GetTypeInfo(type);
if (info is null)
{
ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(type);
ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(type, context);
}

return info;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ public sealed partial class JsonSerializerOptions
///
/// If the <see cref="JsonSerializerOptions"/> instance is locked for modification, the method will return a cached instance for the metadata.
/// </remarks>
[RequiresUnreferencedCode("Getting a metadata for a type may require reflection which depends on unreferenced code.")]
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
[RequiresDynamicCode("Getting a metadata for a type may require reflection which depends on runtime code generation.")]
public JsonTypeInfo GetTypeInfo(Type type)
{
if (type is null)
Expand All @@ -47,8 +45,6 @@ public JsonTypeInfo GetTypeInfo(Type type)
ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(type), type, null, null);
}

_typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance();

JsonTypeInfo? typeInfo;
if (IsLockedInstance)
{
Expand All @@ -62,7 +58,7 @@ public JsonTypeInfo GetTypeInfo(Type type)

if (typeInfo is null)
{
ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type);
ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type, TypeInfoResolver);
}

return typeInfo;
Expand All @@ -82,7 +78,7 @@ internal JsonTypeInfo GetTypeInfoCached(Type type)

if (typeInfo == null)
{
ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type);
ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type, TypeInfoResolver);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ public JsonConverter GetConverter(Type typeToConvert)
ThrowHelper.ThrowArgumentNullException(nameof(typeToConvert));
}

_typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance();
if (_typeInfoResolver is null)
{
// Backward compatibility -- root the default reflection converters
// but do not populate the TypeInfoResolver setting.
DefaultJsonTypeInfoResolver.RootDefaultInstance();
}

return GetConverterFromTypeInfo(typeToConvert);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,19 @@ public sealed partial class JsonSerializerOptions
/// so using fresh default instances every time one is needed can result in redundant recomputation of converters.
/// This property provides a shared instance that can be consumed by any number of components without necessitating any converter recomputation.
/// </remarks>
public static JsonSerializerOptions Default { get; } = CreateDefaultImmutableInstance();
public static JsonSerializerOptions Default
{
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
get
{
JsonSerializerOptions defaultInstance = DefaultInstance;
defaultInstance._typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance();
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
return defaultInstance;
}
}

internal static JsonSerializerOptions DefaultInstance { get; } = new JsonSerializerOptions { IsLockedInstance = true };
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved

// For any new option added, adding it to the options copied in the copy constructor below must be considered.
private IJsonTypeInfoResolver? _typeInfoResolver;
Expand Down Expand Up @@ -162,16 +174,15 @@ public JsonSerializerOptions(JsonSerializerDefaults defaults) : this()
/// Gets or sets a <see cref="JsonTypeInfo"/> contract resolver.
/// </summary>
/// <remarks>
/// A <see langword="null"/> setting is equivalent to using the reflection-based <see cref="DefaultJsonTypeInfoResolver"/>.
/// When used with the reflection-based <see cref="JsonSerializer"/> APIs,
/// a <see langword="null"/> setting be equivalent to and replaced by the reflection-based
/// <see cref="DefaultJsonTypeInfoResolver"/>.
/// </remarks>
[AllowNull]
public IJsonTypeInfoResolver TypeInfoResolver
public IJsonTypeInfoResolver? TypeInfoResolver
{
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
get
{
return _typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance();
return _typeInfoResolver;
}
set
{
Expand All @@ -180,9 +191,6 @@ public IJsonTypeInfoResolver TypeInfoResolver
}
}

// Needed since public property is RequiresUnreferencedCode.
internal IJsonTypeInfoResolver? TypeInfoResolverSafe => _typeInfoResolver;

/// <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.
Expand Down Expand Up @@ -729,11 +737,5 @@ public ConverterList(JsonSerializerOptions options, IList<JsonConverter>? source
protected override bool IsLockedInstance => _options.IsLockedInstance;
protected override void VerifyMutable() => _options.VerifyMutable();
}

private static JsonSerializerOptions CreateDefaultImmutableInstance()
{
var options = new JsonSerializerOptions { IsLockedInstance = true };
return options;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ internal void ValidateCanBeUsedForDeserialization()
{
if (ThrowOnDeserialize)
{
ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolverSafe, Type);
ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolver, Type);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ internal override JsonParameterInfoValues[] GetParameterInfoValues()
JsonParameterInfoValues[] array;
if (CtorParamInitFunc == null || (array = CtorParamInitFunc()) == null)
{
ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeCtorParams(Options.TypeInfoResolverSafe, Type);
ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeCtorParams(Options.TypeInfoResolver, Type);
return null!;
}

Expand Down Expand Up @@ -132,7 +132,7 @@ internal override void LateAddProperties()
return;
}

ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolverSafe, Type);
ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(Options.TypeInfoResolver, Type);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,15 +651,15 @@ public static void ThrowNotSupportedException_BuiltInConvertersNotRooted(Type ty
}

[DoesNotReturn]
public static void ThrowNotSupportedException_NoMetadataForType(Type type)
public static void ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver? resolver)
{
throw new NotSupportedException(SR.Format(SR.NoMetadataForType, type));
throw new NotSupportedException(SR.Format(SR.NoMetadataForType, type, resolver?.GetType().FullName ?? "<null>"));
}

[DoesNotReturn]
public static void ThrowInvalidOperationException_NoMetadataForType(Type type)
public static void ThrowInvalidOperationException_NoMetadataForType(Type type, IJsonTypeInfoResolver? resolver)
{
throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type));
throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type, resolver?.GetType().FullName ?? "<null>"));
}

[DoesNotReturn]
Expand Down
Loading