I have a json with different kind of blocks in it. There are simple block, like text
, there's no problems in deserializing them, so I won't cover them up. The problems are with three kind of blocks: media
, list
and quiz
:
{
"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": ""
}
]
}
These blocks's items
property is different from one to another.
For serializing/deserializing I'm using System.Text.Json
, built in net5.0
. While I can deserialize this json's items
into JsonElement
and work with it further, I'd like to deserialize them to exact classes automatically, when I call JsonSerializer.Deserialize(...);
e.g. IEnumerable<MediaBlockData>
, IEnumerable<string>
and so on, depending on what type
(I mean json's property) it is.
While looking for answer I found out an article on mircosoft website, but I'm not wholly understand on how to implement it for my case:
Update 1
My blocks implementation:
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; }
}
Update 2
Here's minimal reproducable code sample with json string in it:
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();
}
}
}
}
Update 3
I've been able to deserialize blocks correctly (hope there are no major issues with this code), thanks to @dbc and this post:
Deserialize JSON array which has mixed values System.Text.JSON
Updated code:
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();
}
}
}
Now I'm trying to serialize it back to string correctly, not yet happening.