使用 .NET Core System.Text.Json 序列化/反序列化类层次结构 [英] Serialize/Deserialize a class hierarchy with .NET Core System.Text.Json
问题描述
我有一个简单的类层次结构,我想使用 System.Text.Json 对其进行序列化.
I have a simple class hierarchy that I want to serialize using System.Text.Json.
有3个班级.基础是Shape
.继承的是Box
和Circle
.
There are 3 classes. The base is Shape
. Inherited ones are Box
and Circle
.
我计划在我的前端应用程序中使用这些类作为标记联合,因此我刚刚引入了鉴别器属性 Tag
.
I have a plan to use these classes as a tagged union on my frontend app so I just introduced a discriminator property Tag
.
我写了一个类型转换器,支持这个层次结构的序列化/反序列化.
I wrote a type convertor that supports serialization/deserialization of this hierarchy.
我想了解的是 - 这是否是实现此类功能的最佳方法.确实,序列化的输出结果非常难看(我在下面的示例中添加了注释).我不确定它是否以最好的方式完成,反正它只是在工作.
What I'm trying to understand - is this a best approach to implement such functionality or not. Indeed the output result of serialization is quite ugly (I put a comment in an example below). I'm not sure it's done the best way anyway it's just working.
这是我如何实现序列化/反序列化的示例:
Here's the example how I implemented serialization/deserialization:
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Serialization.Theory
{
public abstract class Shape
{
public abstract String Tag { get; }
}
public class Box : Shape
{
public override String Tag { get; } = nameof(Box);
public Single Width { get; set; }
public Single Height { get; set; }
public override String ToString()
{
return $"{Tag}: Width={Width}, Height={Height}";
}
}
public class Circle : Shape
{
public override String Tag { get; } = nameof(Circle);
public Single Radius { get; set; }
public override String ToString()
{
return $"{Tag}: Radius={Radius}";
}
}
public class ShapeConverter : JsonConverter<Shape>
{
public override Boolean CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(Circle) || typeToConvert == typeof(Shape);
}
public override Shape Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var raw = reader.GetString();
var doc = JsonDocument.Parse(raw);
var prop = doc.RootElement.EnumerateObject().Where(x => x.Name == "Tag").First();
var value = prop.Value.GetString();
switch (value)
{
case nameof(Circle):
return JsonSerializer.Deserialize<Circle>(raw);
case nameof(Box):
return JsonSerializer.Deserialize<Box>(raw);
default:
throw new NotSupportedException();
}
}
public override void Write(Utf8JsonWriter writer, Shape value, JsonSerializerOptions options)
{
if (value is Circle circle)
{
writer.WriteStringValue(JsonSerializer.SerializeToUtf8Bytes(circle));
}
else if (value is Box box)
{
writer.WriteStringValue(JsonSerializer.SerializeToUtf8Bytes(box));
}
}
}
class Program
{
static void Main(string[] args)
{
// Keep in base class references like it's a property on another object.
Shape origin1 = new Box { Width = 10, Height = 20 };
Shape origin2 = new Circle { Radius = 30 };
var settings = new JsonSerializerOptions();
settings.Converters.Add(new ShapeConverter());
var raw1 = JsonSerializer.Serialize(origin1, settings);
var raw2 = JsonSerializer.Serialize(origin2, settings);
Console.WriteLine(raw1); // "{\u0022Tag\u0022:\u0022Box\u0022,\u0022Width\u0022:10,\u0022Height\u0022:20}"
Console.WriteLine(raw2); // "{\u0022Tag\u0022:\u0022Circle\u0022,\u0022Radius\u0022:30}"
var restored1 = JsonSerializer.Deserialize<Shape>(raw1, settings);
var restored2 = JsonSerializer.Deserialize<Shape>(raw2, settings);
Console.WriteLine(restored1); // Box: Width=10, Height=20
Console.WriteLine(restored2); // Circle: Radius=30
}
}
}
推荐答案
请试试我写的这个库作为 System.Text.Json 的扩展来提供多态:https://github.com/dahomey-technologies/Dahomey.Json
Please try this library I wrote as an extension to System.Text.Json to offer polymorphism: https://github.com/dahomey-technologies/Dahomey.Json
public abstract class Shape
{
}
[JsonDiscriminator(nameof(Box))]
public class Box : Shape
{
public float Width { get; set; }
public float Height { get; set; }
public override string ToString()
{
return $"Box: Width={Width}, Height={Height}";
}
}
[JsonDiscriminator(nameof(Circle))]
public class Circle : Shape
{
public float Radius { get; set; }
public override string ToString()
{
return $"Circle: Radius={Radius}";
}
}
继承类必须手动注册到鉴别器约定注册表,以便让框架知道鉴别器值和类型之间的映射:
Inherited classes must be manually registered to the discriminator convention registry in order to let the framework know about the mapping between a discriminator value and a type:
JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
DiscriminatorConventionRegistry registry = options.GetDiscriminatorConventionRegistry();
registry.RegisterConvention(new AttributeBasedDiscriminatorConvention<string>(options, "Tag"));
registry.RegisterType<Box>();
registry.RegisterType<Circle>();
Shape origin1 = new Box { Width = 10, Height = 20 };
Shape origin2 = new Circle { Radius = 30 };
string json1 = JsonSerializer.Serialize(origin1, options);
string json2 = JsonSerializer.Serialize(origin2, options);
Console.WriteLine(json1); // {"Tag":"Box","Width":10,"Height":20}
Console.WriteLine(json2); // {"Tag":"Circle","Radius":30}
var restored1 = JsonSerializer.Deserialize<Shape>(json1, options);
var restored2 = JsonSerializer.Deserialize<Shape>(json2, options);
Console.WriteLine(restored1); // Box: Width=10, Height=20
Console.WriteLine(restored2); // Circle: Radius=30
这篇关于使用 .NET Core System.Text.Json 序列化/反序列化类层次结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!