C# 使用json.net反序列化无类型信息的多态json类

C# 使用json.net反序列化无类型信息的多态json类,c#,serialization,json.net,imgur,C#,Serialization,Json.net,Imgur,此调用返回一个列表,其中包含以JSON表示的画廊图像和画廊相册类 我看不出如何使用Json.NET自动反序列化这些类,因为没有$type属性告诉反序列化程序要表示哪个类。有一个称为“IsAlbum”的属性可以用来区分两者 这个问题似乎显示了一种方法,但它看起来有点像黑客 如何反序列化这些类?(使用C#,Json.NET) 样本数据: 画廊图片 { "id": "OUHDm", "title": "My most recent drawing. Spent over 100 hou

此调用返回一个列表,其中包含以JSON表示的画廊图像画廊相册

我看不出如何使用Json.NET自动反序列化这些类,因为没有$type属性告诉反序列化程序要表示哪个类。有一个称为“IsAlbum”的属性可以用来区分两者

这个问题似乎显示了一种方法,但它看起来有点像黑客

如何反序列化这些类?(使用C#,Json.NET)

样本数据:

画廊图片

{
    "id": "OUHDm",
    "title": "My most recent drawing. Spent over 100 hours.",
        ...
    "is_album": false
}
画廊相册

{
    "id": "lDRB2",
    "title": "Imgur Office",
    ...
    "is_album": true,
    "images_count": 3,
    "images": [
        {
            "id": "24nLu",
            ...
            "link": "http://i.imgur.com/24nLu.jpg"
        },
        {
            "id": "Ziz25",
            ...
            "link": "http://i.imgur.com/Ziz25.jpg"
        },
        {
            "id": "9tzW6",
            ...
            "link": "http://i.imgur.com/9tzW6.jpg"
        }
    ]
}
}

通过创建一个定制的
JsonConverter
来处理对象实例化,您可以非常轻松地完成这项工作。假设您的类定义如下:

public abstract class GalleryItem
{
    public string id { get; set; }
    public string title { get; set; }
    public string link { get; set; }
    public bool is_album { get; set; }
}

public class GalleryImage : GalleryItem
{
    // ...
}

public class GalleryAlbum : GalleryItem
{
    public int images_count { get; set; }
    public List<GalleryImage> images { get; set; }
}
public class GalleryItemConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(GalleryItem).IsAssignableFrom(objectType);
    }

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

        // Using a nullable bool here in case "is_album" is not present on an item
        bool? isAlbum = (bool?)jo["is_album"];

        GalleryItem item;
        if (isAlbum.GetValueOrDefault())
        {
            item = new GalleryAlbum();
        }
        else
        {
            item = new GalleryImage();
        }

        serializer.Populate(jo.CreateReader(), item);

        return item;
    }

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

    public override void WriteJson(JsonWriter writer, 
        object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
下面是一个示例程序,显示了转换器的工作情况:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
            {
                ""id"": ""OUHDm"",
                ""title"": ""My most recent drawing. Spent over 100 hours."",
                ""link"": ""http://i.imgur.com/OUHDm.jpg"",
                ""is_album"": false
            },
            {
                ""id"": ""lDRB2"",
                ""title"": ""Imgur Office"",
                ""link"": ""http://alanbox.imgur.com/a/lDRB2"",
                ""is_album"": true,
                ""images_count"": 3,
                ""images"": [
                    {
                        ""id"": ""24nLu"",
                        ""link"": ""http://i.imgur.com/24nLu.jpg""
                    },
                    {
                        ""id"": ""Ziz25"",
                        ""link"": ""http://i.imgur.com/Ziz25.jpg""
                    },
                    {
                        ""id"": ""9tzW6"",
                        ""link"": ""http://i.imgur.com/9tzW6.jpg""
                    }
                ]
            }
        ]";

        List<GalleryItem> items = 
            JsonConvert.DeserializeObject<List<GalleryItem>>(json, 
                new GalleryItemConverter());

        foreach (GalleryItem item in items)
        {
            Console.WriteLine("id: " + item.id);
            Console.WriteLine("title: " + item.title);
            Console.WriteLine("link: " + item.link);
            if (item.is_album)
            {
                GalleryAlbum album = (GalleryAlbum)item;
                Console.WriteLine("album images (" + album.images_count + "):");
                foreach (GalleryImage image in album.images)
                {
                    Console.WriteLine("    id: " + image.id);
                    Console.WriteLine("    link: " + image.link);
                }
            }
            Console.WriteLine();
        }
    }
}

Fiddle:

我发布这篇文章只是为了澄清一些困惑。如果您使用的是预定义格式,并且需要对其进行反序列化,我发现这是最有效的方法,并演示了机制,以便其他人可以根据需要对其进行调整

public class BaseClassConverter : JsonConverter
    {
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var j = JObject.Load(reader);
            var retval = BaseClass.From(j, serializer);
            return retval;
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }

        public override bool CanConvert(Type objectType)
        {
            // important - do not cause subclasses to go through this converter
            return objectType == typeof(BaseClass);
        }
    }

    // important to not use attribute otherwise you'll infinite loop
    public abstract class BaseClass
    {
        internal static Type[] Types = new Type[] {
            typeof(Subclass1),
            typeof(Subclass2),
            typeof(Subclass3)
        };

        internal static Dictionary<string, Type> TypesByName = Types.ToDictionary(t => t.Name.Split('.').Last());

        // type property based off of class name
        [JsonProperty(PropertyName = "type", Required = Required.Always)]
        public string JsonObjectType { get { return this.GetType().Name.Split('.').Last(); } set { } }

        // convenience method to deserialize a JObject
        public static new BaseClass From(JObject obj, JsonSerializer serializer)
        {
            // this is our object type property
            var str = (string)obj["type"];

            // we map using a dictionary, but you can do whatever you want
            var type = TypesByName[str];

            // important to pass serializer (and its settings) along
            return obj.ToObject(type, serializer) as BaseClass;
        }


        // convenience method for deserialization
        public static BaseClass Deserialize(JsonReader reader)
        {
            JsonSerializer ser = new JsonSerializer();
            // important to add converter here
            ser.Converters.Add(new BaseClassConverter());

            return ser.Deserialize<BaseClass>(reader);
        }
    }
公共类BaseClassConverter:JsonConverter
{
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
var j=作业对象加载(读卡器);
var retval=BaseClass.From(j,序列化程序);
返回返回;
}
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
{
serializer.Serialize(writer,value);
}
公共覆盖布尔CanConvert(类型objectType)
{
//重要信息-不要使子类通过此转换器
返回objectType==typeof(基类);
}
}
//重要的是不要使用属性,否则将导致无限循环
公共抽象类基类
{
内部静态类型[]类型=新类型[]{
类型(第1子类),
类型(第2子类),
类型(第3子类)
};
内部静态字典TypesByName=Types.ToDictionary(t=>t.Name.Split('.').Last());
//基于类名的类型属性
[JsonProperty(PropertyName=“type”,Required=Required.Always)]
公共字符串JsonObjectType{get{返回此.GetType().Name.Split('.').Last();}set{}
//反序列化作业对象的简便方法
来自(JObject obj、JsonSerializer序列化程序)的公共静态新基类
{
//这是我们的对象类型属性
var str=(字符串)obj[“type”];
//我们使用字典绘制地图,但你可以做任何你想做的事
var type=TypesByName[str];
//传递序列化程序(及其设置)很重要
返回obj.ToObject(类型,序列化器)作为基类;
}
//反序列化的简便方法
公共静态基类反序列化(JsonReader)
{
JsonSerializer ser=新的JsonSerializer();
//此处添加转换器很重要
添加(新的BaseClassConverter());
返回序列反序列化(读取器);
}
}

以下实现应允许您反序列化,而无需更改类的设计方式,也无需使用$type以外的字段来决定反序列化为什么

public class GalleryImageConverter : JsonConverter
{   
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(GalleryImage) || objectType == typeof(GalleryAlbum));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            if (!CanConvert(objectType))
                throw new InvalidDataException("Invalid type of object");
            JObject jo = JObject.Load(reader);
            // following is to avoid use of magic strings
            var isAlbumPropertyName = ((MemberExpression)((Expression<Func<GalleryImage, bool>>)(s => s.is_album)).Body).Member.Name;
            JToken jt;
            if (!jo.TryGetValue(isAlbumPropertyName, StringComparison.InvariantCultureIgnoreCase, out jt))
            {
                return jo.ToObject<GalleryImage>();
            }
            var propValue = jt.Value<bool>();
            if(propValue) {
                resultType = typeof(GalleryAlbum);
            }
            else{
                resultType = typeof(GalleryImage);
            }
            var resultObject = Convert.ChangeType(Activator.CreateInstance(resultType), resultType);
            var objectProperties=resultType.GetProperties();
            foreach (var objectProperty in objectProperties)
            {
                var propType = objectProperty.PropertyType;
                var propName = objectProperty.Name;
                var token = jo.GetValue(propName, StringComparison.InvariantCultureIgnoreCase);
                if (token != null)
                {
                    objectProperty.SetValue(resultObject,token.ToObject(propType)?? objectProperty.GetValue(resultObject));
                }
            }
            return resultObject;
        }
        catch (Exception ex)
        {
            throw;
        }
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
公共类GalleryImageConverter:JsonConverter
{   
公共覆盖布尔CanConvert(类型objectType)
{
return(objectType==typeof(GalleryImage)| | objectType==typeof(GalleryAlbum));
}
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
尝试
{
如果(!CanConvert(对象类型))
抛出新的InvalidDataException(“对象类型无效”);
JObject jo=JObject.Load(读卡器);
//以下是避免使用魔术字符串
var isAlbumPropertyName=((MemberExpression)((Expression)(s=>s.is_相册)).Body.Member.Name;
jtokenjt;
if(!jo.TryGetValue(IsalBumpPropertyName,StringComparison.InvariantCultureIgnoreCase,out jt))
{
返回jo.ToObject();
}
var propValue=jt.Value();
if(propValue){
结果类型=类型(GalleryAlbum);
}
否则{
结果类型=类型(GalleryImage);
}
var resultObject=Convert.ChangeType(Activator.CreateInstance(resultType),resultType);
var objectProperties=resultType.GetProperties();
foreach(objectProperties中的var objectProperty)
{
var propType=objectProperty.PropertyType;
var propName=objectProperty.Name;
var token=jo.GetValue(propName、StringComparison.InvariantCultureIgnoreCase);
if(令牌!=null)
{
objectProperty.SetValue(resultObject,token.ToObject(propType)??objectProperty.GetValue(resultObject));
}
}
返回结果对象;
}
捕获(例外情况除外)
{
投掷;
}
}
公共覆盖布尔可写
{
获取{return false;}
}
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
{
抛出新的NotImplementedException();
}
}
仅使用与Json.NET一起工作的属性

    [JsonConverter(typeof(JsonSubtypes), "is_album")]
    [JsonSubtypes.KnownSubType(typeof(GalleryAlbum), true)]
    [JsonSubtypes.KnownSubType(typeof(GalleryImage), false)]
    public abstract class GalleryItem
    {
        public string id { get; set; }
        public string title { get; set; }
        public string link { get; set; }
        public bool is_album { get; set; }
    }

    public class GalleryImage : GalleryItem
    {
        // ...
    }

    public class GalleryAlbum : GalleryItem
    {
        public int images_count { get; set; }
        public List<GalleryImage> images { get; set; }
    }
[JsonConverter(typeof(JsonSubtypes),“is_相册”)]
[JsonSubtypes.KnownSubType(typeof(GalleryAlbum),true)]
[JsonSubtypes.KnownSubType(typeof(GalleryImage),false)]
公共抽象类GalleryItem
{
    [JsonConverter(typeof(JsonSubtypes), "is_album")]
    [JsonSubtypes.KnownSubType(typeof(GalleryAlbum), true)]
    [JsonSubtypes.KnownSubType(typeof(GalleryImage), false)]
    public abstract class GalleryItem
    {
        public string id { get; set; }
        public string title { get; set; }
        public string link { get; set; }
        public bool is_album { get; set; }
    }

    public class GalleryImage : GalleryItem
    {
        // ...
    }

    public class GalleryAlbum : GalleryItem
    {
        public int images_count { get; set; }
        public List<GalleryImage> images { get; set; }
    }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    ..... YOU Code For Determine Real Type of Json Record .......

    // 1. Correct ContractResolver for you derived type
    var contract = serializer.ContractResolver.ResolveContract(DeterminedType);
    if (converter != null && !typeDeserializer.Type.IsAbstract && converter.GetType() == GetType())
    {
        contract.Converter = null; // Clean Wrong Converter grabbed by DefaultContractResolver from you base class for derived class
    }

    // Deserialize in general way           
    var jTokenReader = new JTokenReader(jObject);
    var result = serializer.Deserialize(jTokenReader, DeterminedType);

    return (result);
}