Created
September 11, 2023 08:06
-
-
Save NN---/7863622be9b488b973a01249427cb31f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Newtonsoft.Json can serialize this successfully while System.Text.Json doesn't do it: | |
```cs | |
var la = new List<A> { new B() { I = 1, J = 2 } }; | |
Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(la)); | |
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(la)); | |
class A | |
{ | |
public int I { get; set; } | |
} | |
class B : A | |
{ | |
public int J { get; set; } | |
} | |
``` | |
``` | |
[{"J":2,"I":1}] | |
[{"I":1}] | |
``` | |
There is a page describing how to work with polymorphic objects: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-7-0 . | |
However, it required writing down all possible types. | |
Newtonsoft.Json writes an object simply as dictionary thus making possible to write any type regardless the hierarachy. | |
I suggest adding an option either by options or by attribute allowing serializing polymorphic type the same way Newtonsoft.Json does. | |
```cs | |
var la = new List<A> { new B() { I = 1, J = 2 } }; | |
// 1. using options | |
Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(la)); | |
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(la, | |
new JsonSerializerOptions { | |
SerializePolymorphicTypes = true // Serialize polymorphic types | |
SerializeAsObject = true // Alternative way by treating types as key-value objects which has the same result | |
})); | |
// 2. using attribute | |
[JsonSerializePolymorphic] // Serialize derived types | |
class A | |
{ | |
public int I { get; set; } | |
} | |
class B : A | |
{ | |
public int J { get; set; } | |
} | |
``` | |
``` | |
[{"J":2,"I":1}] | |
[{"J":2,"I":1}] | |
``` | |
Another option is to use TypeInfoResolver for detecting derived type: | |
```cs | |
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(la, | |
new JsonSerializerOptions { | |
TypeInfoResolver = new PolymorphicTypeResolver() | |
})); | |
public class PolymorphicTypeResolver : DefaultJsonTypeInfoResolver | |
{ | |
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) | |
{ | |
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); | |
if (type.GetCustomAttribute<JsonPolymorphicAttribute>() is { } disc) | |
{ | |
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions | |
{ | |
TypeDiscriminatorPropertyName = disc.TypeDiscriminatorPropertyName, | |
IgnoreUnrecognizedTypeDiscriminators = true, | |
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization, | |
}; | |
foreach (var d in Assembly.GetExecutingAssembly() | |
.GetTypes() | |
.Where(t => t.IsSubclassOf(type))) | |
{ | |
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType( d)); | |
} | |
} | |
return jsonTypeInfo; | |
} | |
} | |
``` | |
Alternatively everything can be defined as `object` which is not so nice making your code dynamically typed. | |
```cs | |
var la = new List<object> { new B() { I = 1, J = 2 } }; | |
``` | |
Or serializing every object manually as key-value: | |
```cs | |
var sla = System.Text.Json.JsonSerializer.Serialize(la, new JsonSerializerOptions | |
{ | |
Converters = { new ObjectConverterFactory() }, | |
}); | |
Console.WriteLine(sla); | |
public class ObjectConverterFactory : JsonConverterFactory | |
{ | |
public override bool CanConvert(Type typeToConvert) | |
{ | |
return !typeToConvert.IsPrimitive && | |
!typeToConvert.IsEnum && | |
typeToConvert.Assembly != typeof(object).Assembly; | |
} | |
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) | |
{ | |
return new ObjectConverter(options); | |
} | |
} | |
public class ObjectConverter : JsonConverter<object> | |
{ | |
private readonly JsonSerializerOptions _options; | |
public ObjectConverter(JsonSerializerOptions options) | |
{ | |
_options = options; | |
} | |
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | |
{ | |
throw new NotImplementedException(); | |
} | |
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) | |
{ | |
Type type = value.GetType(); | |
writer.WriteStartObject(); | |
foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) | |
{ | |
if (pi.GetValue(value) is { } nonNullValue) | |
{ | |
writer.WritePropertyName(pi.Name); | |
writer.WriteRawValue(STJ.JsonSerializer.Serialize(nonNullValue, _options)); | |
} | |
} | |
writer.WriteEndObject(); | |
} | |
} | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment