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

The JsonSerializerOptions.TypeInfoResolver property should be nullable #71960

Closed
Tracked by #63686
eiriktsarpalis opened this issue Jul 11, 2022 · 4 comments · Fixed by #72044
Closed
Tracked by #63686

The JsonSerializerOptions.TypeInfoResolver property should be nullable #71960

eiriktsarpalis opened this issue Jul 11, 2022 · 4 comments · Fixed by #72044
Assignees
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions
Milestone

Comments

@eiriktsarpalis
Copy link
Member

The current iteration of JsonSerializerOptions.TypeInfoResolver

public IJsonTypeInfoResolver TypeInfoResolver
{
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
get
{
return _typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance();
}
set
{
VerifyMutable();
_typeInfoResolver = value;
}
}

is implemented as non-nullable and marked as RequiredUnreferencedCode since by default it will return the reflection-based contract resolver. This has been informed by the existing semantics of the JsonSerializer methods accepting JsonSerializerOptions as an argument: unless a contract resolver is explicitly set the method will revert to using reflection-based serialization.

I believe however that this approach is problematic, for a number of reasons:

  • Because the property is marked RUC, it can trigger linker warnings in otherwise innocuous operations, such as copying resolver configuration to a new options instance.
  • The introduction of contract customization has effectively decoupled reflection-based serialization from JsonSerializerOptions. While we cannot change the semantics of existing JsonSerializer methods it is conceivable that future serialization APIs can make use of it in a linker-safe manner. Assuming we ship the TypeInfoResolver property in the current manifestation, we forever couple JsonSerializerOptions to the reflection-based serialization and trimmability concerns.

I propose we make the following changes to the API:

public partial class JsonSerializerOptions
{
-       [RequiresUnreferenceCode]
-       [RequiresDynamicCode]
-       public IJsonTypeInfoResolver TypeInfoResolver { get; set; }
+       public IJsonTypeInfoResolver? TypeInfoResolver { get; set; }

+       [RequiresUnreferenceCode]
+       [RequiresDynamicCode]
        public static JsonSerializerOptions Default { get; } // default instance comes populated with reflection resolver
}
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jul 11, 2022
@ghost
Copy link

ghost commented Jul 11, 2022

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

The current iteration of JsonSerializerOptions.TypeInfoResolver

public IJsonTypeInfoResolver TypeInfoResolver
{
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
get
{
return _typeInfoResolver ??= DefaultJsonTypeInfoResolver.RootDefaultInstance();
}
set
{
VerifyMutable();
_typeInfoResolver = value;
}
}

is implemented as non-nullable and marked as RequiredUnreferencedCode since by default it will return the reflection-based contract resolver. This has been informed by the existing semantics of the JsonSerializer methods accepting JsonSerializerOptions as an argument: unless a contract resolver is explicitly set the method will revert to using reflection-based serialization.

I believe however that this approach is problematic, for a number of reasons:

  • Because the property is marked RUC, it can trigger linker warnings in otherwise innocuous operations, such as copying resolver configuration to a new options instance.
  • The introduction of contract customization has effectively decoupled reflection-based serialization from JsonSerializerOptions. While we cannot change the semantics of existing JsonSerializer methods it is conceivable that future serialization APIs can make use of it in a linker-safe manner. Assuming we ship the TypeInfoResolver property in the current manifestation, we forever couple JsonSerializerOptions to the reflection-based serialization and trimmability concerns.

I propose we make the following changes to the API:

public partial class JsonSerializerOptions
{
-       [RequiresUnreferenceCode]
-       [RequiresDynamicCode]
-       public IJsonTypeInfoResolver TypeInfoResolver { get; set; }
+       public IJsonTypeInfoResolver? TypeInfoResolver { get; set; }

+       [RequiresUnreferenceCode]
+       [RequiresDynamicCode]
        public static JsonSerializerOptions Default { get; } // default instance comes populated with reflection resolver
}
Author: eiriktsarpalis
Assignees: -
Labels:

area-System.Text.Json

Milestone: -

@eiriktsarpalis eiriktsarpalis added this to the 7.0.0 milestone Jul 11, 2022
@eiriktsarpalis eiriktsarpalis added enhancement Product code improvement that does NOT require public API changes/additions and removed untriaged New issue has not been triaged by the area owner labels Jul 11, 2022
@eiriktsarpalis eiriktsarpalis self-assigned this Jul 11, 2022
@krwq
Copy link
Member

krwq commented Jul 12, 2022

IMO following behavior which is proposed here feels like designing APIs around linker. In both cases null gets translated tot he same instance the only difference is that in this proposal it happens in some seemingly magic moment in time (we as devs know that moment but I doubt trying to explain that to anyone will make sense why we would've picked that behavior)

// proposed:
options.TypeInfoResolver = null;
// options.TypeInfoResolver == null
JsonSerializer(..., options);
// options.TypeInfoResolver.GetType()  == typeof(DefaultJsonTypeInfoResolver)

current behavior:

// current behavior:
options.TypeInfoResolver = null;
// options.TypeInfoResolver.GetType()  == typeof(DefaultJsonTypeInfoResolver) 
JsonSerializer(..., options);
// options.TypeInfoResolver.GetType()  == typeof(DefaultJsonTypeInfoResolver) 

it just feels more consistent the way it is right now

@eiriktsarpalis
Copy link
Member Author

eiriktsarpalis commented Jul 12, 2022

cc @jkotas @eerhardt who might have opinions on the matter.

I don't think "designing APIs around the linker" is the right way to look at this. It is about designing a type that works for scenaria beyond the narrow scope of reflection-based serialization. If we do ship JsonSerializerOptions with the current semantics/annotation, the type will be forever coupled to reflection-based serialization: in effect it will be impossible to ship serialization APIs accepting JsonSerializerOptions parameters in the future that do not need to be marked "RequiresUnreferenceCode". I think this is a problem, particularly given the fact that we expect source generators to be the future of serialization in .NET.

For the record, I don't believe reflection-based JsonSerializer methods populating null TypeInfoResolvers to be an important issue. This is in line with System.Text.Json semantics since the APIs got shipped originally (these methods also lock the options values so a degree of mutation is not entirely unexpected). We need to document this, of course, but I doubt this might be considered a source of confusion.

@jkotas
Copy link
Member

jkotas commented Jul 12, 2022

@eiriktsarpalis 's proposal makes sense to me.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jul 12, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jul 13, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Aug 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants