反序列化一个 Json 字段有两种类型,一种类型是递归的

Deserialize a Json with a field being of 2 types and one type is recursive

有一个打字稿部分,我从中得到 json 结果,需要在 c# 中解析结果以获取使用它的对象,但因为我们在这里:

  1. 多类型字段
  2. 递归

越来越难以理解反序列化器的外观, 我知道我需要对多类型字段使用 JsonConverter,但是如何处理多类型字段的递归?

这是制作 json 的打字稿代码:

export interface FilterDescriptor {

  field ? : string | Function;

  operator: string | Function;

  value ? : any;

  ignoreCase ? : boolean;
}

export interface CompositeFilterDescriptor {
  logic: 'or' | 'and';

  filters: Array < FilterDescriptor | CompositeFilterDescriptor > ;
}

export declare
const isCompositeFilterDescriptor: (source: FilterDescriptor | CompositeFilterDescriptor) => source is CompositeFilterDescriptor;

json 的示例: 递归

{
"logic": "and",
"filters": [
  {
    "field": "type.group",
    "logic": "and",
    "filters": [
      {
        "field": "type.group",
        "operator": "neq",
        "value": 2
      },
      {
        "field": "type.group",
        "operator": "neq",
        "value": 5
      }
    ]
  }
]}

没有递归:

{
"logic": "and",
"filters": [
  {
    "field": "type.group",
    "operator": "eq",
    "value": 2
  }
]}

这个 json 是通过使用 Kendo Ui for Angular 来自 Telerik "CompositeFilterDescriptor"

谢谢。

问题是 C# 没有 'discriminated union' 结构的等价物(至少,我认为它是这样叫的 ;-))

可用于多态性的唯一构造是接口和继承。

在这种情况下,您需要两种类型(FilterDescriptorCompositeFilterDescriptor),并且您需要多态性,因为 CompositeFilterDescriptor 有一个数组可以包含其中任何一种。在 C# 中,您需要一个接口或基础 class 来表达:

interface IFilterDescriptor { }

class FilterDescriptor : IFilterDescriptor
{
    // ... (fields of FilterDescriptor)
}

class CompositeFilterDescriptor : IFilterDescriptor
{
    public string @logic { get; set; }
    public IFilterDescriptor[] filters { get; set; }
}

此外,您不能使用 Json.NET 默认反序列化,因为这需要多态反序列化的类型信息。但是,您可以创建一个自定义转换器,您可以在其中检查某些字段的存在以决定如何反序列化对象:

public class FilterDescriptorConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) => typeof(IFilterDescriptor).IsAssignableFrom(objectType);

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

        // if the 'logic' field is present, it's a CompositeFilter
        var item = o["logic"] != null 
            ? (IFilterDescriptor)new CompositeFilterDescriptor() 
            : (IFilterDescriptor)new FilterDescriptor();
        serializer.Populate(o.CreateReader(), item);
        return item;
    }

    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

你可以这样使用它:

var result = JsonConvert.DeserializeObject<IFilterDescriptor>(json, 
          new FilterDescriptorConverter());