-
Notifications
You must be signed in to change notification settings - Fork 5k
System.Text.Json: Allow derived types to specify base type for polymorphic serialization #111295
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
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis |
Fundamentally the reason why this scenario is not supported is that we cannot reliably implement it in the context of the source generator/trimmed apps/Native AOT. Searching the type hierarchy at runtime for potential derived types requires reflection that only works in full CoreCLR. This is why we recommend using contract customization and custom attributes to get support for this scenario.
Why is it unreasonable for your project? |
Simply just because of the unique position the project is in. Legacy project where this style has been used since well before I joined, making it a tough sell on making changes (whether it be source generators or something else) on that base class.
Do you mind sharing a resource for this? This recommendation you're describing sounds like it may be able to work like I'm wanting.
And just out of curiosity, why wouldn't something like I'm suggesting work in those scenarios? I'm not too knowledgeable about how the compiler handle base/derived classes, but at least from where I'm at, if specifying every derived class from the base would work, I'd think specifying the base from why derived class could work too. Maybe some order of operations thing I'm just missing, so I'm just curious with this point. |
Here's how you can implement an attribute like the one you describe: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class JsonBaseTypeAttribute(Type baseType, string typeDiscriminator) : Attribute
{
public Type BaseType { get; } = baseType;
public string TypeDiscriminator { get; } = typeDiscriminator;
}
public static class JsonBaseTypeExtensions
{
public static JsonSerializerOptions WithBaseTypeAttribute(this JsonSerializerOptions options)
{
IJsonTypeInfoResolver resolver = options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver();
options.TypeInfoResolver = resolver.WithAddedModifier(static typeInfo =>
{
if (typeInfo.Type.IsValueType || typeInfo.Type.IsSealed)
{
return;
}
var derivedTypes = System.AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => typeInfo.Type.IsAssignableFrom(t));
foreach (Type derivedType in derivedTypes)
{
if (derivedType.GetCustomAttribute<JsonBaseTypeAttribute>() is { } attr &&
attr.BaseType == typeInfo.Type)
{
typeInfo.PolymorphismOptions ??= new();
typeInfo.PolymorphismOptions.DerivedTypes.Add(new(derivedType, attr.TypeDiscriminator));
}
}
});
return options;
}
} You can then use this in your application as follows: var options = new JsonSerializerOptions().WithBaseTypeAttribute();
Base value = new Derived(1, 2);
JsonSerializer.Serialize(value, options); // {"$type":"derived","y":2,"x":1}
Ιf you look at the implementation above you will see that it needs to query the full AppDomain for all possible types deriving from your base. This creates a number of problems in trimmed and AOT applications because the derived type might have been trimmed or because the source generator is not able to see and generate a contract for the derived type at compile time. This is why attributes must be specified at the base type if you're using the built-in attributes. |
That snippet looks to work perfectly, thanks! That explanation also makes sense. |
One small nit: |
Uh oh!
There was an error while loading. Please reload this page.
Starting with .NET 7, according to this article, the only ways to do polymorphic de/serialization is to use
JsonDerivedType
and/orJsonPolymorphic
on the base type or to create a customJsonTypeInfoResolver
. While this should be perfectly reasonable for many projects, it's very unreasonable for a project I work on.I would like to see an attribute similar to
JsonDerivedType
but kinda working in reverse, possibly calledJsonBaseType
, that could be used like so:My use case, and why this would be much more useful for me, is at my job we handle, and need slightly custom method logic, for hundreds of PDF forms. So every time a new form is processed and added to our system, it's going to have its own class generated with its metadata and an employee will add the custom logic as needed. Here is a very simplified example of how this looks in practice:
With the scale of this pattern, and how our form classes are being generated, it would be much easier to just add an attribute to every new derived class referencing its base than needing to add hundreds of attributes to the base class or having hundreds of entries in a custom JsonTypeInfoResolver. An option for this to be handled in a way similar to Newtonsoft.JSON would be a very painless solution, but I believe that's been shot down before.
The text was updated successfully, but these errors were encountered: