How do I avoid exposing a type discriminator when serializing a class that has opted into polymorphic deserialization?
I'm using System.Text.Json's JsonDerivedType
attributes to do polymorphic deserialization. So I need to specify typeDiscriminator
on the attribute, or else deserialization won't read incoming $type
fields on the JSON. But when I serialize those same classes, I don't want System.Text.Json to automatically add $type
(exposing implmentation details, etc.).
Example
[JsonDerivedType(typeof(Derived1), typeDiscriminator: "Derived1")]
[JsonDerivedType(typeof(Derived2), typeDiscriminator: "Derived2")]
public record BaseType(int Id);
public record Derived1(int Id, string Name) : BaseType(Id);
public record Derived2(int Id, bool IsActive) : BaseType(Id);
When serializing:
var values = new List<BaseType>
{
new Derived1(123, "Foo"),
new Derived2(456, true)
};
JsonSerializer.Serialize(values);
Actual output:
[
{ "$type": "Derived1", "Id": 123, "Name": "Foo" },
{ "$type": "Derived2", "Id": 456, "IsActive": true }
]
How do I avoid $type
being written?
Desired output:
[
{ "Id": 123, "Name": "Foo" },
{ "Id": 456, "IsActive": true }
]
Again, I know I could exclude typeDiscriminator
from the attribute, but then deserialization would not work.
Links
CodePudding user response:
One solution is to write separate overrides of DefaultJsonTypeInfoResolver
for serialization and deserialization, and remove the [JsonDerivedType]
attributes.
Similar to Newtonsoft, System.Text.Json also supports polymorphic deserialization via the serializer's options as an alternative to attributes. In Newtonsoft, you would use different values of JsonSerializerSettings.TypeNameHandling
to affect whether $type
is written. System.Text.Json requires that you opt each type into this behavior explicitly, but the idea is the same.
Sample classes:
// Don't add [JsonDerivedType]
public record BaseType(int Id);
public record Derived1(int Id, string Name) : BaseType(Id);
public record Derived2(int Id, bool IsActive) : BaseType(Id);
Resolver for deserializing with a discriminator:
// Example taken from dotnet/runtime#63747 "Configuring Polymorphism via the Contract model"
public class DeserializeResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
if (jsonTypeInfo.Type == typeof(BaseType))
{
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions()
{
DerivedTypes =
{
// Pass in the type discriminator for this resolver.
new JsonDerivedType(typeof(Derived1), typeDiscriminator: "Derived1"),
new JsonDerivedType(typeof(Derived2), typeDiscriminator: "Derived2"),
}
};
}
return jsonTypeInfo;
}
}
Resolver for serializing without a discriminator:
public class SerializeResolver : DefaultJsonTypeInfoResolver
{
...
// Exact same as above resolver except:
DerivedTypes =
{
// Don't pass in the typeDiscriminator parameter.
new JsonDerivedType(typeof(Derived1)),
new JsonDerivedType(typeof(Derived2)),
}
...
}
Then, use different instances of JsonSerializerOptions
serialization and deserialization:
initial
:
[
{ "$type": "Derived1", "Name": "Foo", "Id": 123 },
{ "$type": "Derived2", "IsActive": true, "Id": 456 }
]
string initial;
var deserializeOptions = new JsonSerializerOptions
{
TypeInfoResolver = new DeserializeResolver(),
};
var serializeOptions = new JsonSerializerOptions
{
TypeInfoResolver = new SerializeResolver(),
};
var values = JsonSerializer.Deserialize<List<BaseType>>(initial, deserializeOptions);
// At this point, the types have been successfully deserialized via their $type.
Assert.NotNull(values);
Assert.IsAssignableFrom<Derived1>(values[0]);
Assert.IsAssignableFrom<Derived2>(values[1]);
string result = JsonSerializer.Serialize(values, serializeOptions);
result
:
[
{ "Name": "Foo", "Id": 123 },
{ "IsActive": true, "Id": 456 }
]
CodePudding user response:
I see the following options:
- Don't use polymorphic list, e.g. have a list of
Derived1
and another list ofDerived2
- Change
$type
to something that you like[JsonPolymorphic(CustomTypeDiscriminatorPropertyName = "Kind")]
- Use a different name:
[JsonDerivedType(typeof(Derived1), typeDiscriminator: "A")]
, or a number[JsonDerivedType(typeof(Derived1), typeDiscriminator: 1)]
, - Don't use
JsonDerivedType
and write your own deserialiser
Source: #63747: Developers can use System.Text.Json to serialize type hierarchies securely