【问题标题】:Deserialize json on different classes在不同的类上反序列化 json
【发布时间】:2021-05-07 12:28:01
【问题描述】:

我有一个包含不同类型块的 json。有简单的块,比如text,反序列化没有问题,所以我不会掩盖它们。问题在于三种块:medialistquiz

{
  "blocks": [
    {
      "type": "text",
      "data": {
           "text": "my awesome text",
           "text_truncated": "<<<same>>>"
        },
      "cover": false,
      "anchor": ""
    },
    {
      "type": "media",
      "data": {
        "items": [
          {
            "title": "title1",
            "author": "author1",
            "image": {
              "type": "image",
              "data": {
                "uuid": "eb19f678-3c9f-58f0-90c2-33bcb8237b17",
                "width": 1024,
                "height": 756,
                "size": 448952,
                "type": "jpg",
                "color": "d3c58f",
                "hash": "",
                "external_service": []
              }
            }
          },
          {
            "title": "title2",
            "author": "author2",
            "image": {
              "type": "image",
              "data": {
                "uuid": "9274038e-1e9b-5cab-9db5-4936ce88a5c9",
                "width": 750,
                "height": 563,
                "size": 164261,
                "type": "jpg",
                "color": "b7a58d",
                "hash": "",
                "external_service": []
              }
            }
          }
        ],
        "with_background": false,
        "with_border": false
      },
      "cover": false,
      "anchor": ""
    },
    {
      "type": "list",
      "data": {
        "items": [
          "foo",
          "bar"
        ],
        "type": "UL"
      },
      "cover": false,
      "anchor": ""
    },
    {
      "type": "quiz",
      "data": {
        "uid": "00bde249ff735f481620328765695",
        "hash": "29d6bf8fec36eee3",
        "tmp_hash": "",
        "title": "When?",
        "items": {
          "a16203287650": "Ashita",
          "a16203287651": "Kinou",
          "a16203287742": "Ima"
        },
        "is_public": false,
        "date_created": 1620328765
      },
      "cover": false,
      "anchor": ""
    }
  ]
}

这些块的items 属性各不相同。

对于序列化/反序列化,我使用的是System.Text.Json,内置于net5.0。虽然我可以将此 json 的 items 反序列化为 JsonElement 并进一步使用它,但我想在调用 JsonSerializer.Deserialize(...); 时自动将它们反序列化为精确的类,例如IEnumerable&lt;MediaBlockData&gt;IEnumerable&lt;string&gt; 等等,这取决于 type(我的意思是 json 的属性)是什么。

在寻找答案时,我在 mircosoft 网站上找到了一篇文章,但我并不完全了解如何为我的案例实施它:

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0

更新 1

我的块实现:

    public class Block
    {
        [JsonPropertyName("type")]
        public string Type { get; set; }

        [JsonPropertyName("data")]
        public JsonElement Data { get; set; }

        public IBlockData ParsedData { get; set; }

        [JsonPropertyName("cover")]
        public bool Cover { get; set; }

        [JsonPropertyName("anchor")]
        public string Anchor { get; set; }

        private Type GetBlockDataType() => Type switch
        {
            "audio" => typeof(AudioBlockData),
            "code" => typeof(CodeBlockData),
            "delimiter" => typeof(DelimiterBlockData),
            "header" => typeof(HeaderBlockData),
            "image" => typeof(ImageBlockData),
            "incut" => typeof(IncutBlockData),
            "instagram" => typeof(InstagramBlockData),
            "link" => typeof(LinkBlockData),
            "list" => typeof(ListBlockData),
            "media" => typeof(MediaBlockData),
            "number" => typeof(NumberBlockData),
            "person" => typeof(PersonBlockData),
            "quiz" => typeof(QuizBlockData),
            "quote" => typeof(QuoteBlockData),
            "spotify" => typeof(SpotifyBlockData),
            "telegram" => typeof(TelegramBlockData),
            "text" => typeof(TextBlockData),
            "tiktok" => typeof(TikTokBlockData),
            "tweet" => typeof(TweetBlockData),
            "universalbox" => typeof(UniversalBoxBlockData),
            "video" => typeof(VideoBlockData),
            "yamusic" => typeof(YaMusicBlockData),
            _ => typeof(object)
        };

        public IBlockData GetBlockData()
        {
            var blockType = GetBlockDataType();

            return (IBlockData)JsonSerializer.Deserialize(Data.ToString(), blockType);
        }
    }
    
    public class ListBlockData : IBlockData
    {
        [JsonPropertyName("items")]
        public IEnumerable<string> Items { get; set; }

        [JsonPropertyName("type")]
        public string Type { get; set; }
    }
    
    public class QuizBlockData : IBlockData
    {
        [JsonPropertyName("uid")]
        public string Uid { get; set; }

        [JsonPropertyName("hash")]
        public string Hash { get; set; }

        [JsonPropertyName("tmp_hash")]
        public string TempHash { get; set; }

        [JsonPropertyName("title")]
        public string Title { get; set; }

        [JsonPropertyName("items")]
        public JsonElement Items { get; set; }

        [JsonPropertyName("is_public")]
        public bool IsPublic { get; set; }

        [JsonPropertyName("date_created")]
        public long DateCreated { get; set; }
    }
    
    public class MediaBlockData : IBlockData
    {
        [JsonPropertyName("items")]
        public IEnumerable<MediaItem> Items { get; set; }

        [JsonPropertyName("with_background")]
        public bool WithBackground { get; set; }

        [JsonPropertyName("with_border")]
        public bool WithBorder { get; set; }
    }

    public class MediaItem : Block
    {
        [JsonPropertyName("title")]
        public string Title { get; set; }

        [JsonPropertyName("author")]
        public string Author { get; set; }

        [JsonPropertyName("image")]
        public Block Image { get; set; }
    }

更新 2

这是包含 json 字符串的最小可重现代码示例:

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ConsoleApp1
{
    public interface IBlockData
    {

    }

    public class TextBlockData : IBlockData
    {
        [JsonPropertyName("text")]
        public string Text { get; set; }

        [JsonPropertyName("text_truncated")]
        public string TextTruncated { get; set; }
    }

    public class MediaItem : Block
    {
        [JsonPropertyName("title")]
        public string Title { get; set; }

        [JsonPropertyName("author")]
        public string Author { get; set; }

        [JsonPropertyName("image")]
        public Block Image { get; set; }
    }

    public class QuizBlockData : IBlockData
    {
        [JsonPropertyName("uid")]
        public string Uid { get; set; }

        [JsonPropertyName("hash")]
        public string Hash { get; set; }

        [JsonPropertyName("tmp_hash")]
        public string TempHash { get; set; }

        [JsonPropertyName("title")]
        public string Title { get; set; }

        [JsonPropertyName("items")]
        public JsonElement Items { get; set; } // TODO: normal class instead of JsonElement

        [JsonPropertyName("is_public")]
        public bool IsPublic { get; set; }

        [JsonPropertyName("date_created")]
        public long DateCreated { get; set; }
    }

    public class MediaBlockData : IBlockData
    {
        [JsonPropertyName("items")]
        public IEnumerable<MediaItem> Items { get; set; }

        [JsonPropertyName("with_background")]
        public bool WithBackground { get; set; }

        [JsonPropertyName("with_border")]
        public bool WithBorder { get; set; }
    }

    public class ListBlockData : IBlockData
    {
        [JsonPropertyName("items")]
        public IEnumerable<string> Items { get; set; }

        [JsonPropertyName("type")]
        public string Type { get; set; }
    }

    public class ImageBlockData : IBlockData
    {
        [JsonPropertyName("uuid")]
        public Guid Uuid { get; set; }

        [JsonPropertyName("width")]
        public int Width { get; set; }

        [JsonPropertyName("height")]
        public int Height { get; set; }

        [JsonPropertyName("size")]
        public long Size { get; set; }

        [JsonPropertyName("type")]
        public string Type { get; set; }

        [JsonPropertyName("color")]
        public string Color { get; set; }

        [JsonPropertyName("hash")]
        public string Hash { get; set; }

        [JsonPropertyName("external_service")]
        public IEnumerable<ExternalService> ExternalService { get; set; }
    }

    public class ExternalService
    {
        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }
    }

    public class Block
    {
        [JsonPropertyName("type")]
        public string Type { get; set; }

        [JsonPropertyName("data")]
        public JsonElement Data { get; set; }

        public IBlockData ParsedData { get; set; }

        [JsonPropertyName("cover")]
        public bool Cover { get; set; }

        [JsonPropertyName("anchor")]
        public string Anchor { get; set; }

        private Type GetBlockDataType() => Type switch
        {
            "image" => typeof(ImageBlockData),
            "list" => typeof(ListBlockData),
            "media" => typeof(MediaBlockData),
            "quiz" => typeof(QuizBlockData),
            "text" => typeof(TextBlockData),
            _ => typeof(object)
        };

        public IBlockData GetBlockData()
        {
            var blockType = GetBlockDataType();

            return (IBlockData)JsonSerializer.Deserialize(Data.ToString(), blockType);
        }
    }

    public class Root
    {
        [JsonPropertyName("blocks")]
        public IEnumerable<Block> Blocks { get; set; }
    }

    internal static class Program
    {
        private static void Main()
        {
            var json = "{\"blocks\":[{\"type\":\"text\",\"data\":{\"text\":\"my awesome text\",\"text_truncated\":\"<<<same>>>\"},\"cover\":false,\"anchor\":\"\"},{\"type\":\"media\",\"data\":{\"items\":[{\"title\":\"title1\",\"author\":\"author1\",\"image\":{\"type\":\"image\",\"data\":{\"uuid\":\"eb19f678-3c9f-58f0-90c2-33bcb8237b17\",\"width\":1024,\"height\":756,\"size\":448952,\"type\":\"jpg\",\"color\":\"d3c58f\",\"hash\":\"\",\"external_service\":[]}}},{\"title\":\"title2\",\"author\":\"author2\",\"image\":{\"type\":\"image\",\"data\":{\"uuid\":\"9274038e-1e9b-5cab-9db5-4936ce88a5c9\",\"width\":750,\"height\":563,\"size\":164261,\"type\":\"jpg\",\"color\":\"b7a58d\",\"hash\":\"\",\"external_service\":[]}}}],\"with_background\":false,\"with_border\":false},\"cover\":false,\"anchor\":\"\"},{\"type\":\"list\",\"data\":{\"items\":[\"foo\",\"bar\"],\"type\":\"UL\"},\"cover\":false,\"anchor\":\"\"},{\"type\":\"quiz\",\"data\":{\"uid\":\"00bde249ff735f481620328765695\",\"hash\":\"29d6bf8fec36eee3\",\"tmp_hash\":\"\",\"title\":\"When?\",\"items\":{\"a16203287650\":\"Ashita\",\"a16203287651\":\"Kinou\",\"a16203287742\":\"Ima\"},\"is_public\":false,\"date_created\":1620328765},\"cover\":false,\"anchor\":\"\"}]}";

            var root = JsonSerializer.Deserialize<Root>(json);

            // Example on how it can be deserialized afterwards
            foreach (Block block in root.Blocks)
            {
                block.ParsedData = block.GetBlockData();
            }
        }
    }
}

更新 3

感谢@dbc 和这篇文章,我已经能够正确反序列化块(希望这段代码没有大问题):

Deserialize JSON array which has mixed values System.Text.JSON

更新代码:

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ConsoleApp1
{
    public class BlockData
    {

    }

    public class TextBlockData : BlockData
    {
        [JsonPropertyName("text")]
        public string Text { get; set; }

        [JsonPropertyName("text_truncated")]
        public string TextTruncated { get; set; }
    }

    public class MediaItemBlock : Block
    {
        [JsonPropertyName("title")]
        public string Title { get; set; }

        [JsonPropertyName("author")]
        public string Author { get; set; }

        [JsonPropertyName("image")]
        public Block Image { get; set; }
    }

    public class QuizBlockData : BlockData
    {
        [JsonPropertyName("uid")]
        public string Uid { get; set; }

        [JsonPropertyName("hash")]
        public string Hash { get; set; }

        [JsonPropertyName("tmp_hash")]
        public string TempHash { get; set; }

        [JsonPropertyName("title")]
        public string Title { get; set; }

        [JsonPropertyName("items")]
        public Dictionary<string, string> Items { get; set; }

        [JsonPropertyName("is_public")]
        public bool IsPublic { get; set; }

        [JsonPropertyName("date_created")]
        public long DateCreated { get; set; }
    }

    public class MediaBlockData : BlockData
    {
        [JsonPropertyName("items")]
        public IEnumerable<MediaItemBlock> Items { get; set; }

        [JsonPropertyName("with_background")]
        public bool WithBackground { get; set; }

        [JsonPropertyName("with_border")]
        public bool WithBorder { get; set; }
    }

    public class ListBlockData : BlockData
    {
        [JsonPropertyName("items")]
        public IEnumerable<string> Items { get; set; }

        [JsonPropertyName("type")]
        public string Type { get; set; }
    }

    public class ImageBlockData : BlockData
    {
        [JsonPropertyName("uuid")]
        public Guid Uuid { get; set; }

        [JsonPropertyName("width")]
        public int Width { get; set; }

        [JsonPropertyName("height")]
        public int Height { get; set; }

        [JsonPropertyName("size")]
        public long Size { get; set; }

        [JsonPropertyName("type")]
        public string Type { get; set; }

        [JsonPropertyName("color")]
        public string Color { get; set; }

        [JsonPropertyName("hash")]
        public string Hash { get; set; }

        [JsonPropertyName("external_service")]
        public IEnumerable<ExternalService> ExternalService { get; set; }
    }

    public class ExternalService
    {
        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }
    }

    public class Block
    {
        [JsonPropertyName("type")]
        public string Type { get; set; }

        [JsonPropertyName("data")]
        public BlockData Data { get; set; }

        [JsonPropertyName("cover")]
        public bool Cover { get; set; }

        [JsonPropertyName("anchor")]
        public string Anchor { get; set; }

        public static Type GetBlockDataType(string type) => type switch
        {
            "image" => typeof(ImageBlockData),
            "list" => typeof(ListBlockData),
            "media" => typeof(MediaBlockData),
            "quiz" => typeof(QuizBlockData),
            "text" => typeof(TextBlockData),
            _ => typeof(object)
        };
    }

    public class Root
    {
        [JsonPropertyName("blocks")]
        public IEnumerable<Block> Blocks { get; set; }
    }

    internal static class Program
    {
        private static void Main()
        {
            var json = "{\"blocks\":[{\"type\":\"text\",\"data\":{\"text\":\"my awesome text\",\"text_truncated\":\"<<<same>>>\"},\"cover\":false,\"anchor\":\"\"},{\"type\":\"media\",\"data\":{\"items\":[{\"title\":\"title1\",\"author\":\"author1\",\"image\":{\"type\":\"image\",\"data\":{\"uuid\":\"eb19f678-3c9f-58f0-90c2-33bcb8237b17\",\"width\":1024,\"height\":756,\"size\":448952,\"type\":\"jpg\",\"color\":\"d3c58f\",\"hash\":\"\",\"external_service\":[]}}},{\"title\":\"title2\",\"author\":\"author2\",\"image\":{\"type\":\"image\",\"data\":{\"uuid\":\"9274038e-1e9b-5cab-9db5-4936ce88a5c9\",\"width\":750,\"height\":563,\"size\":164261,\"type\":\"jpg\",\"color\":\"b7a58d\",\"hash\":\"\",\"external_service\":[]}}}],\"with_background\":false,\"with_border\":false},\"cover\":false,\"anchor\":\"\"},{\"type\":\"list\",\"data\":{\"items\":[\"foo\",\"bar\"],\"type\":\"UL\"},\"cover\":false,\"anchor\":\"\"},{\"type\":\"quiz\",\"data\":{\"uid\":\"00bde249ff735f481620328765695\",\"hash\":\"29d6bf8fec36eee3\",\"tmp_hash\":\"\",\"title\":\"When?\",\"items\":{\"a16203287650\":\"Ashita\",\"a16203287651\":\"Kinou\",\"a16203287742\":\"Ima\"},\"is_public\":false,\"date_created\":1620328765},\"cover\":false,\"anchor\":\"\"}]}";

            var options = new JsonSerializerOptions
            {
                Converters = { new BlockConverter() }
            };

            var root = JsonSerializer.Deserialize<Root>(json, options);
            var ser = JsonSerializer.Serialize(root, options);
        }
    }

    public class BlockConverter : JsonConverter<Block>
    {
        public override Block Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            using JsonDocument doc = JsonDocument.ParseValue(ref reader);

            // Get type for convert
            string type = doc.RootElement.GetProperty("type").GetString();

            // Create new block with default deserializer
            Block block = JsonSerializer.Deserialize<Block>(doc.RootElement.GetRawText());

            // Warning: recursive for types with blocks inside
            block.Data = (BlockData)JsonSerializer.Deserialize(doc.RootElement.GetProperty("data").GetRawText(),
                                                               Block.GetBlockDataType(type), options);

            return block;
        }

        public override void Write(Utf8JsonWriter writer, Block value, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    }
}

现在我正在尝试将其正确序列化回字符串,但尚未发生。

【问题讨论】:

  • 能否在代码中也加入你正在使用的类结构和代码?您不需要为此使用自定义转换器,因为 .Net5 库支持对象反序列化。
  • @blockingHD 确定!我已经用代码 sn-p 更新了问题。
  • 看起来你有一个多态类型层次结构。 System.Text.Json 不支持开箱即用此类层次结构的序列化。您将需要编写一个自定义转换器来做到这一点。为此,请参阅Is polymorphic deserialization possible in System.Text.Json?。这是否回答了您的问题,还是您需要其他帮助?
  • @dbc 谢谢你的回答!我在 microsoft guilde to json converters for System.Text.Json 上看到了类似的示例,但老实说,我不太了解如何为我的案例实现它。
  • 那么你能把你的代码分解成minimal reproducible example吗?到目前为止您共享的代码甚至无法编译,请参阅dotnetfiddle.net/8UT9r4。如果没有完整的可编译示例,我们所能做的就是将您指向Is polymorphic deserialization possible in System.Text.Json?

标签: c# json serialization deserialization system.text.json


【解决方案1】:

这是一个不使用转换器反序列化此 json 的版本。

var media = new List<MediaBlock>();
var texts = new List<TextBlock>();

using var doc = JsonDocument.Parse(json);
foreach (var block in doc.RootElement.GetProperty("blocks").EnumerateArray())
{
    switch (block.GetProperty("type").GetString())
    {
        case "text": texts.Add(Deserialise<TextBlock>(block.GetProperty("data"))); break;
        case "media": media.Add(Deserialise<MediaBlock>(block.GetProperty("data"))); break;
        // ... 
    }
}

var composite = new 
{
    Texts = texts,// compsite.Texts[0].Text is 'my awesome text'
    Media = media // compsite.Media.Items[0].Author is 'author1'
};

// This is OK, but if you need speed, please have a look at
// https://stackoverflow.com/questions/58138793/system-text-json-jsonelement-toobject-workaround
static T Deserialise<T>(JsonElement e) => JsonSerializer.Deserialize<T>(e.GetRawText(), options: new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

我使用的辅助类有:

class MediaBlock
{
    public List<MediaItem> Items { get; set; }
}

class MediaItem
{
    public string Author { get; set; }
    public string Title { get; set; }
    // ...
}

class TextBlock
{
    public string Text { get; set; }
}

【讨论】:

  • 有趣的方法,感谢您提供更多信息!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 2021-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多