c# - 使用 Newtonsoft.JSON 自定义转换器读取具有不同输入的 json

我正在使用 NewtonSoft.Json 以 json 格式读取/写入我们的数据。一个(非常简化的)例子是:

{
  "$type": "MyNamespace.LandingEvent, MyAssembly",
  "TimeOfLanding": "2021-04-11T15:00:00.000Z",
  "AirportName": "KLAX",
  "AirportRunway": "25L"
}

使用模仿属性的 C# DTO 类。请注意,我们使用 TypeNameHandling

我们想将 C# 类更改为更复杂的设置:

class Airport
{
    public string Name { get; set; }
    public string Runway { get; set; }
}

class LandingEvent
{
    public DateTime TimeOfLanding { get; set; }
    public Airport Airport { get; set; }
}

这将导致新数据将写入 JSON 中:

{
  "$type": "MyNamespace.LandingEvent, MyAssembly",
  "TimeOfLanding": "2021-04-11T15:00:00.000Z",
  "Airport": {
    "Name": "KLAX",
    "Runway": "25L"
  }
}

但我们仍然需要能够读取旧的 JSON 数据并解析为新的类结构。这就是我目前正在努力解决的问题。

我知道要走的路可能是专门的 JsonConverter。在这方面我有几个问题:

  1. 如何读取 $type 属性并实例化正确的类型? (我重写的 CanConvert() 方法被提供了一个基类的名称(由于实际上下文比这个例子更复杂)。
  2. 如果属性 AirportName 存在,我只想进行自定义读取。如果不是这种情况,我该如何回退到默认反序列化?

编辑:需要进行一些说明。如果我创建自定义 JsonConverter,则 CanConvert 将接收类型 EventBase,但 $type 实际上可以包含"MyNamespace.LandingEvent, MyAssembly" "MyNamespace.TakeoffEvent, MyAssembly"。因此,我可能需要根据这个值自己实例化返回的对象。不过,我不确定如何。

最佳答案

您可以使用自定义 JsonConverter 在处理多态事件类型和不同的 JSON 格式方面执行双重任务。下面是一个例子。它通过将数据加载到 JObject 中来工作,它可以在其中读取 $type 属性并实例化正确的事件类型。从那里,它将尝试从 JSON 填充事件对象。如果 Airport 反序列化失败,它将尝试读取遗留机场属性并从中填充一个新的 Airport 实例。

class EventConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(BaseEvent).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);

        string type = (string)obj["$type"];
        BaseEvent baseEvent;
        if (type.Contains(nameof(TakeoffEvent)))
        {
            baseEvent = new TakeoffEvent();
        }
        else
        {
            baseEvent = new LandingEvent();
        }
        serializer.Populate(obj.CreateReader(), baseEvent);

        if (baseEvent.Airport == null)
        {
            baseEvent.Airport = new Airport
            {
                Name = (string)obj["AirportName"],
                Runway = (string)obj["AirportRunway"]
            };
        }
        return baseEvent;
    }

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

注意:这假设你的类结构实际上是这样的:

class Airport
{
    public string Name { get; set; }
    public string Runway { get; set; }
}

class BaseEvent
{
    public Airport Airport { get; set; }
}

class TakeoffEvent : BaseEvent
{
    public DateTime TimeOfTakeoff { get; set; }
}

class LandingEvent : BaseEvent
{
    public DateTime TimeOfLanding { get; set; }
}

要使用转换器,请将其添加到JsonSerializerSettings 中的Converters 集合,并将设置传递给DeserializeObject():

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    Converters = new List<JsonConverter> { new EventConverter() }
};

var baseEvent = JsonConvert.DeserializeObject<BaseEvent>(json, settings);

这是一个工作演示:https://dotnetfiddle.net/jSaq4T

另请参阅:Adding backward compatibility support for an older JSON structure

https://stackoverflow.com/questions/67044973/

相关文章:

python - VSCode 终端不显示所有行

r - 如何重命名 R 中 DT 数据表的过滤器中的占位符?

java - 如何给java.net.http.HttpClient GET请求添加参数?

reactjs - 使用vite时如何识别 typescript 中的env变量?

uml - 将关联类添加到组合关系

javascript - 在 Laravel 应用程序中延迟加载 vue 组件 - 不加载

c# - (非网络)主机上下文中范围服务的含义

reactjs - React Portal 中 nextJS 应用程序的转发样式

modbus - Minimalmodbus:在同一个串口上读取多个从机

python - ValueError : Cannot set tensor: Dimension