反序列化嵌套接口

时间:2018-07-03 18:55:21

标签: c# json.net

可以在此处查看复制:Gist

在尝试反序列化接口集合时收到错误,尤其是在接口还包含相同接口集合的情况下。如果删除嵌套的集合,则可以反序列化。我以为实现JsonConverter可以解决此问题,但我似乎误会了。

请注意,我无法控制序列化。

我收到的错误是:

  

无法创建类型为JsonRepro.IMember的实例。类型是接口或抽象类,无法实例化。路径“ members [0] .id”,第17行,位置25。

最小复制如下:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonRepro
{
    public interface IMember
    {
        int ID { get; set; }
        string Type { get; set; }
    }

    public class A : IMember
    {
        public int ID { get; set; }
        public string Type { get; set; }
        public List<IMember> Members { get; set; }
    }

    public class B : IMember
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Type { get; set; }
    }

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

        public override object ReadJson(JsonReader reader,
            Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject item = JObject.Load(reader);
            var typeValue = item["type"].Value<string>();

            switch (typeValue)
            {
                case "A":
                    return item.ToObject<A>();
                case "B":
                    return item.ToObject<B>();
            }

            throw new NotSupportedException();
        }

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

        public override bool CanWrite => false;
    }


    class Program
    {
        private const string json = @"
            [
                {
                'id': 0,
                'type': 'A'
                },
                {
                'id': 1,
                'type': 'B',
                'name': 'One'
                },
                {
                'id': 2,
                'type': 'A',
                'members': [
                    {
                    'id': 3,
                    'type': 'A'
                    }
                ]
                }
            ]
        ";

        static void Main(string[] args)
        {
            var settings = new JsonSerializerSettings() { Converters = { new MyConverter() } };
            var result = JsonConvert.DeserializeObject<List<IMember>>(json, settings);
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

如果同时控制序列化和反序列化,请尝试在序列化程序设置中同时使用TypeNameHandling.All和反序列化。

当您具有接口之类的抽象类型时,它不知道要使用哪种具体类型。外部具体类型可以在Deserialize类型参数中指定,但是内部类型不能指定。使用TypeNameHandling可以反序列化为抽象类型,但实际上它将反序列化为$type中指定的具体类型。

另一种方法是将反序列化为JObject,然后对每个反序列化ToObject<T>,但随后必须对每个内部对象进行反序列化。

您还可以使用自定义JsonConverter,但这更加复杂。

另一种替代方法是,只要可以避免,就不要使用接口来存储数据。如果只是一堆没有方法的属性,则可能不需要接口。


更新

如何对您的自定义JsonConverter进行修改:

case "A":
    return new A
    {
        ID = item["id"]?.Value<int>() ?? 0,
        Type = typeValue,
        Members = ((JArray)item["members"])?.ToObject<List<IMember>>(serializer)
    };

虽然不漂亮,但是可以。您需要在ToObject上将序列化程序与自定义转换器一起传递,但是如果执行item.ToObject<A>(serializer)则会导致堆栈溢出,因此只需要在members属性上进行。