1

Consider these model classes:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")] [JsonDerivedType(typeof(A), typeDiscriminator: nameof(A))] [JsonDerivedType(typeof(B), typeDiscriminator: nameof(B))] interface IMyType { } class A(DependencyA dependency) : IMyType { public int X { get; set; } = dependency.GetValue(); } class B(DependencyB dependency) : IMyType { public int Y { get; set; } = dependency.GetValue(); } class DependencyA { public int GetValue() => 42; } class DependencyB { public int GetValue() => 123; } 

DI container used is Microsoft.DependencyInjection.

The example:

static void Main(string[] args) { IServiceCollection services = new ServiceCollection() .AddTransient<DependencyA>() .AddTransient<DependencyB>() .AddTransient<A>() .AddTransient<B>(); IServiceProvider provider = services.BuildServiceProvider(); IMyType[] items = [ provider.GetRequiredService<A>(), provider.GetRequiredService<B>() ]; Print(items); string json = JsonSerializer.Serialize(items); Console.WriteLine(json); // This line is invalid but states my intension: IMyType[]? deserialized = JsonSerializer.Deserialize<IMyType[]>(json); // ^^^^^^^^ // System.InvalidOperationException: 'Each parameter in the deserialization constructor // on type 'ConsoleAppExample.A' must bind to an object property or field on // deserialization. Each parameter name must match with a property or field on the object. // Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. // The match can be case-insensitive.' Print(deserialized); } static void Print(IMyType[]? items) { foreach (IMyType instance in items ?? []) { switch (instance) { case A aInstance: Console.WriteLine($"A.X = {aInstance.X}"); break; case B bInstance: Console.WriteLine($"B.Y = {bInstance.Y}"); break; } } } 

Beginning of output (before exception):

A.X = 42 B.Y = 123 [{"$type":"A","X":42},{"$type":"B","Y":123}] 

The serialized JSON output is as expected.

My question: how to make the JsonSerializer aware of ServiceProvider to create the instances using DI instead of calling constructor?

I've tried to create some PolymorphicTypeResolver but failed because I didn't find a place to inject IServiceProvider instance where it can get the derived Type and return the object for deserializer.

OK, here's my attempt:

class PolymorphicTypeResolver(IServiceProvider serviceProvider) : DefaultJsonTypeInfoResolver { static Type[] RegisteredTypes = [typeof(A), typeof(B)]; public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) { var jsonTypeInfo = base.GetTypeInfo(type, options); if (jsonTypeInfo.Type == typeof(IMyType)) { // WRONG LINE because here `type` is always `IMyType` // But where can I get the derived one? jsonTypeInfo.CreateObject = () => serviceProvider.GetRequiredService(type); jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions { TypeDiscriminatorPropertyName = "$type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization, }; foreach (var derived in RegisteredTypes) { jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(derived, derived.Name)); } } return jsonTypeInfo; } } 

A native-AOT-friendly solution is preferred.

1 Answer 1

2

Since you are looking for a solution that is compatible with Native AOT, it would be better to use a JsonTypeInfo modifier that sets the JsonTypeInfo<T>.CreateObject delegate to serviceProvider.GetRequiredService(concreteType) for each concrete type A and B.

First create the following extension methods:

public static partial class JsonExtensions { public static void WithDependencyInjectedCreateObject(JsonTypeInfo typeInfo, IServiceProvider serviceProvider, params Type [] concreteTypes) { foreach (var concreteType in concreteTypes) WithDependencyInjectedCreateObject(typeInfo, serviceProvider, concreteType); } public static void WithDependencyInjectedCreateObject(JsonTypeInfo typeInfo, IServiceProvider serviceProvider, Type concreteType) { if (typeInfo.Type != concreteType) return; typeInfo.CreateObject = () => serviceProvider.GetRequiredService(concreteType); } } 

Then when setting up your JsonSerializerOptions, apply the modifier for your RegisteredTypes array as follows:

// MSFT recommends caching & reusing options for best performance. JsonSerializerOptions defaultOptions = new () { // In Native AOT scenarios, set TypeInfoResolver to your JsonSerializerContext instance TypeInfoResolver = new DefaultJsonTypeInfoResolver(), // Use whatever you want here. WriteIndented = true, }; Type[] RegisteredTypes = [typeof(A), typeof(B)]; // Clone your default options and apply the typeInfo modifier to inject the dependency-injected creation function for each registered type. JsonSerializerOptions options = new (defaultOptions); options.TypeInfoResolver = (options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver()) .WithAddedModifier(t => JsonExtensions.WithDependencyInjectedCreateObject(t, provider, RegisteredTypes)); 

Then if you serialize and deserialize with these options, your types will be round-tripped successfully:

string json = JsonSerializer.Serialize(items, options); IMyType[]? deserialized = JsonSerializer.Deserialize<IMyType[]>(json, options); 

Notes -

Demo .NET 8 fiddle here.

Sign up to request clarification or add additional context in comments.

2 Comments

I was so close, thank you!
@aepot - you're welcome. If you're coming from Newtonsoft you are probably used to inheriting from DefaultContractResolver but with STJ it's almost always better to use a modifier because 1) you can mix and match them, and 2) they can work with source-generated contexts as well as reflection-based contexts.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.