使用json.net反序列化没有类型信息的多态json类

Pet*_*ale 66 c# serialization json.net imgur

Imgur api调用返回一个列表,其中包含json中表示的Gallery ImageGallery Album类.

我不知道如何使用Json.NET自动反序列化这些,因为没有$ type属性告诉反序列化器要表示哪个类.有一个名为"IsAlbum"的属性可用于区分两者.

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

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

样本数据:

图库图片

{
    "id": "OUHDm",
    "title": "My most recent drawing. Spent over 100 hours.",
        ...
    "is_album": false
}
Run Code Online (Sandbox Code Playgroud)

画廊专辑

{
    "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"
        }
    ]
}
}
Run Code Online (Sandbox Code Playgroud)

}

Bri*_*ers 98

您可以通过创建自定义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; }
}
Run Code Online (Sandbox Code Playgroud)

你会像这样创建转换器:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个显示转换器运行的示例程序:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是上述程序的输出:

id: OUHDm
title: My most recent drawing. Spent over 100 hours.
link: http://i.imgur.com/OUHDm.jpg

id: lDRB2
title: Imgur Office
link: http://alanbox.imgur.com/a/lDRB2
album images (3):
    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
Run Code Online (Sandbox Code Playgroud)

  • 如果多态对象是递归的,即如果专辑可能包含其他专辑,则这不起作用.在转换器中,应该使用Serializer.Populate()而不是item.ToObject().请参阅http://stackoverflow.com/questions/29124126/polymorphic-json-deserialization-failing-using-json-net (19认同)
  • 对于任何尝试这种方法并发现它导致无限循环(并最终导致堆栈溢出)的人,您可能希望使用"Populate"方法而不是"ToObject".请参阅/sf/ask/1778294171/和/sf/ask/2038688851/的答案.失败的使用-json-net.我在这里有一个关于Gist中两种方法的例子:https://gist.github.com/chrisoldwood/b604d69543a5fe5896a94409058c7a95. (4认同)
  • 我已修复转换器以使用“JsonSerializer.Populate()”而不是“JObject.ToObject()”,正如 Ivan 和 Chris 所建议的那样。这将避免递归循环的问题,并允许转换器成功地与属性一起使用。 (2认同)

man*_*c66 25

只需使用与Json.NET 一起使用的JsonSubTypes属性即可

    [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; }
    }
Run Code Online (Sandbox Code Playgroud)

  • 这应该是最好的答案.我花了大量时间研究这个问题的解决方案,检查来自数十位作者的自定义JsonConverter类.你的nuget包用三行代码替换了所有的努力.干得好先生.做得好. (7认同)