Skip to content

JsonConverter constructor causes MissingMetadataException upon instantiation #78029

@Sergio0694

Description

@Sergio0694

Description

We're currently working on migrating all our JSON serialization from Newtonsoft.Json to System.Text.Json in the Microsoft Store (see also #77897), and we're hitting some issues with trimming (we're on .NET Native). In particular, this line:

IsInternalConverter = GetType().Assembly == typeof(JsonConverter).Assembly;

This is crashing when trimming is enabled, because the linker will remove support for getting the assembly info from types. We can fix this by adding some .rd.xml directives, but it's error prone and not really a great solution. Eg. we can use:

<Type Name="System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1" Activate="Required Public" />
<Type Name="System.Text.Json.Serialization.Converters.DictionaryOfTKeyTValueConverter`3" Activate="Required Public" />
<Type Name="System.Text.Json.Serialization.Converters.ListOfTConverter`2" Activate="Required Public" />

Etc. for all converters we need. It'd be much better if this was just fixed in System.Text.Json directly. I'm aware that reflection-free mode isn't supported (see #68093), but fixing this would also benefit other scenarios (such as our case) by still allowing the linker to just trim out more metadata and reduce the binary size further.

Note: to clarify, the ask is not to support the reflection-free mode, just to make this path friendlier to trimming.

cc. @eiriktsarpalis @MichalStrehovsky

Reproduction Steps

The repro is pretty much the same as in the linked issue:

string json = """
    {
        "SomeMapping": { "A": "B" },
        "SomeList": ["A", "B"]
    }
    """;

_ = System.Text.Json.JsonSerializer.Deserialize(rawJson, MicrosoftStoreJsonSerializerContext.Default.SomeModel);

public sealed class SomeModel
{
    public Dictionary<string, string> SomeMapping { get; set; }
    public List<string> SomeList { get; set; }
}

[JsonSerializable(typeof(SomeModel))]
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
internal sealed partial class MicrosoftStoreJsonSerializerContext : JsonSerializerContext
{
}

Expected behavior

This should just work fine.

Actual behavior

We're getting a MissingMetadataException:

at System.Reflection.Runtime.TypeInfos.RuntimeNoMetadataNamedTypeInfo.get_Assembly() in f:\\dd\\ndp\\fxcore\\CoreRT\\src\\System.Private.Reflection.Core\\src\\System\\Reflection\\Runtime\\TypeInfos\\RuntimeNoMetadataNamedTypeInfo.cs:line 38 
at System.Reflection.Runtime.TypeInfos.RuntimeConstructedGenericTypeInfo.get_Assembly() in f:\\dd\\ndp\\fxcore\\CoreRT\\src\\System.Private.Reflection.Core\\src\\System\\Reflection\\Runtime\\TypeInfos\\RuntimeConstructedGenericTypeInfo.cs:line 136
at System.Text.Json.Serialization.JsonConverter`1..ctor(Boolean initialize) 
at System.Text.Json.Serialization.JsonConverter`1..ctor()
at System.Text.Json.Serialization.JsonResumableConverter`1..ctor() 
at System.Text.Json.Serialization.JsonObjectConverter`1..ctor()
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1..ctor()
at System.Text.Json.Serialization.Metadata.SourceGenJsonTypeInfo`1.<>c.<GetConverter>b__3_1()
at System.Func`1.Invoke()
at System.Text.Json.Serialization.Converters.JsonMetadataServicesConverter`1.get_Converter() 
at System.Text.Json.Serialization.Converters.JsonMetadataServicesConverter`1.get_ElementType() 
at System.Text.Json.Serialization.Metadata.JsonTypeInfo..ctor(Type type, JsonConverter converter, JsonSerializerOptions options)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1..ctor(JsonConverter converter, JsonSerializerOptions options) 
at System.Text.Json.Serialization.Metadata.SourceGenJsonTypeInfo`1..ctor(JsonSerializerOptions options, JsonObjectInfoValues`1 objectInfo)
at System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo[T](JsonSerializerOptions options, JsonObjectInfoValues`1 objectInfo) 
at SomeProject.MicrosoftStoreJsonSerializerContext.Create_SomeModel(JsonSerializerOptions options)
at SomeProject.MicrosoftStoreJsonSerializerContext.get_SomeModel()  
at SomeProject.<<TryDeserializeSomeModel>g__Foo|77_0>d.MoveNext() 

Regression?

I have a possible idea on how to fix this, by making that path entirely reflection-free. Consider this:

public abstract class JsonConverter<T>
{
    protected JsonConverter()
    {
        IsInternalType = CheckIsInternalType();
    }

    private protected virtual bool CheckIsInternalType() => false;
}

Now, all converter types in System.Text.Json would just override the method accordingly:

public class SomeSealedJsonConverter<T> : JsonConverter<T>
{
    private protected override bool CheckIsInternalType() => true;
}

public class SomeUnsealedJsonConverter<T> : JsonConverter<T>r
{
    private protected override bool CheckIsInternalType() => GetType() == typeof(SomeUnsealedJsonConverter<T>);
}

This makes sure that:

  • External converters directly inheriting from JsonConverter<T> will be marked as external.
  • External converters that inherit from unsealed STJ converters will also be marked as external.

Essentially this should provide a reflection-free way of checking whether a concrete converter type is from the STJ assembly.

Configuration

  • System.Text.Json 7.0
  • .NET Native 6.2.14

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.Text.JsonenhancementProduct code improvement that does NOT require public API changes/additionslinkable-frameworkIssues associated with delivering a linker friendly frameworkpartner-impactThis issue impacts a partner who needs to be kept updated

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions