How to deserialize a JSON array of elements with different types and varying number of elements?

倖福魔咒の 提交于 2021-01-29 09:50:37

问题


I am dealing with a JSON array, where each element is of a different type, indicated by a type attribute. There can be more than one element of the same type, and the number of elements is not known beforehand. That is:

[
  {
    'abc': '0',
    'type': 'a'
  },
  {
    'cde': '10',
    'type: 'b'
  },
  {
    'abc': '20'
    'type': 'a'
  }
]

I need to deserialize such an array into a List<A> and List<B>.

I looked into Json.NET documentation but I am not sure what would be a good strategy or feature to use for this task. Any pointers would be appreciated.


回答1:


Assuming your types are all known before hand you can deserialize all elements to JObject and use linq to separate the initial array into multiple lists.

Instead of using List<JObject>, you could declare an abstract base type as dbc suggests then implement a custom JsonConverter.

In either case if you want separate lists of each sub type you will need to iterate over your initial array converting the super type to sub type.

Define your types:

class A
{
    public int abc { get; set; }
}

class B
{
    public int cde { get; set; }
}

Then deserialize your base array, and use linq to split into two separate lists.

string json = @"[
    {
    'abc': '0',
    'type': 'a'
    },
    {
    'cde': '10',
    'type': 'b'
    },
    {
    'abc': '20',
    'type': 'a'
    }
]";

List<JObject> objs = JsonConvert.DeserializeObject<List<JObject>>(json);

List<A> objectsA = objs.Where(d => d["type"].ToString() == "a").Select(d => d.ToObject<A>()).ToList();
List<B> objectsB = objs.Where(d => d["type"].ToString() == "b").Select(d => d.ToObject<B>()).ToList();



回答2:


Re-elaborating answers given here and here, and using a base class as stated by dbc, you can obtain the required result.

First, define the types:

class BaseClass
{
    [JsonProperty("type")]
    public string EntityType
    { get; set; }
}

class A : BaseClass
{
    public int abc { get; set; }
}

class B : BaseClass
{
    public int cde { get; set; }
}

Then, define the custom creation converter:

class BaseClassConverter : JsonCreationConverter<BaseClass>
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // Implement this if you need to serialize the object too
        throw new NotImplementedException();
    }

    protected override BaseClass Create(Type objectType, JObject jObject)
    {
        if (jObject["type"].Value<string>() == "a")
        {
            return new A();
        }
        else
        {
            return new B();
        }
    }
}

public abstract class JsonCreationConverter<T> : JsonConverter
{
    /// <summary>
    /// Create an instance of objectType, based properties in the JSON object
    /// </summary>
    /// <param name="objectType">type of object expected</param>
    /// <param name="jObject">
    /// contents of JSON object that will be deserialized
    /// </param>
    /// <returns></returns>
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

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

        // Create target object based on JObject
        T target = Create(objectType, jObject);

        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }
}

Finally, you deserialize your json and obtain the wanted lists:

string json = @"[
        {
        'abc': '0',
        'type': 'a'
        },
        {
        'cde': '10',
        'type': 'b'
        },
        {
        'abc': '20',
        'type': 'a'
        }
    ]";


    List<BaseClass> objects = JsonConvert.DeserializeObject<List<BaseClass>>(json, new BaseClassConverter());
    List<A> aObjects = objects.Where(t => t.GetType() == typeof(A)).Select(o => (A)o).ToList();
    List<B> bObjects = objects.Where(t => t.GetType() == typeof(B)).Select(o => (B)o).ToList();

If and only if the type attribute is the fully qualified name of your type, you can use this in the custom creation converter:

protected override BaseClass Create(Type objectType, JObject jObject)
{
     string type = jObject["type"].Value<string>();
     return (BaseClass)Activator.CreateInstance(Type.GetType(type));
}


来源:https://stackoverflow.com/questions/54027333/how-to-deserialize-a-json-array-of-elements-with-different-types-and-varying-num

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!