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.