.NET Core JSON 轉 Dictionary string, object 地雷
0 | 5,880 |
發現從 .NET 6 開始支援 System.Text.Json DOM 巡覽及編修,小小興奮了一下,打算逐步用 System.Text.Json 取代 Json.NET,不料隨即踩到雷。
有段用 JSON 傳送 Dictionary<string, object> 的程式,原本靠 JsonConvert.DeserializeObject<Dictionary<string, object>>() 將 JSON 轉回 Dictionary<string, object>。開開心心改成 JsonSerializer.Deserializ <Dictionary<string, object>>(),結果在跑測試時爆炸!
用以下程式重現問題:
using System.Text.Json;
var jsonOpt = new JsonSerializerOptions {
WriteIndented = true
};
var dict = new Dictionary<string, object>() {
["i"] = 255,
["s"] = "String",
["d"] = DateTime.Today,
["a"] = new int[] { 1, 2 },
["o"] = new { Prop = 123 }
};
var json = JsonSerializer.Serialize(dict, jsonOpt);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("== JSON ==");
Console.WriteLine(json);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("== Json.NET ==");
var dJsonNet = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
foreach (var kv in dJsonNet!) {
Console.WriteLine($"{kv.Value.GetType().Name} {kv.Key} = {kv.Value}");
}
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("== System.Text.Json ==");
var dSysTextJson = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
foreach (var kv in dSysTextJson!) {
Console.WriteLine($"{kv.Value.GetType().Name} {kv.Key} = {kv.Value}");
}
Console.ResetColor();
問題點為 System.Text.Json 在反序列化 object 時不像 Json.NET 會試著轉型成 int、string、DateTime 等型別(我另外加測了 int[] 及物件,Json.NET 會轉成 JArray 及 JObject),而是一律視為 JsonElement,雖然 JsonElement 提供了 GetByte()、GetGuid()、GetInt32()、GetDateTime()... 等方法,不愁取不出值,但得逐 Key 分別處理,應用上不如 Dictionary<string, object> 便利。
爬文查到有位微軟 MVP 分享自製 DictionaryStringObjectJsonConverter 的解法,透過 JsonConverterAttribute 套用在類別屬性上,原則上可以克服問題。但我心中更理想的解法是未來 System.Text.Json 能內建選項,提供與 Json.NET 類似的轉換邏輯,在此之前,我先試寫一個簡易版擴充函式 ToStringObjectDictionary() 頂著用,順便練手感。
using System.Text.Json.Nodes;
namespace System.Text.Json
{
public static class JsonDictStringObjExtensions
{
public static Dictionary<string, object> ToStringObjectDictionary(this JsonObject jsonObject)
{
var dict = new Dictionary<string, object>();
foreach (var prop in jsonObject)
{
object value;
if (prop.Value == null) value = null!;
else if (prop.Value is JsonArray) value = prop.Value.AsArray();
else if (prop.Value is JsonObject) value = prop.Value.AsObject();
else
{
var v = prop.Value.AsValue();
var t = prop.Value.ToJsonString();
if (t.StartsWith('"')) {
if (v.TryGetValue<DateTime>(out var d)) value = d;
else if (v.TryGetValue<Guid>(out var g)) value = g;
else value = v.GetValue<string>();
}
else value = v.GetValue<decimal>();
}
dict.Add(prop.Key, value);
}
return dict;
}
}
}
修改測試程式,再多測試浮點數、Guid 及 null:
using System.Text.Json;
using System.Text.Json.Nodes;
var jsonOpt = new JsonSerializerOptions {
WriteIndented = true
};
var dict = new Dictionary<string, object>() {
["i"] = 255,
["f"] = 3.1416,
["s"] = "String",
["d"] = DateTime.Today,
["a"] = new int[] { 1, 2 },
["o"] = new { Prop = 123 },
["g"] = Guid.NewGuid(),
["n"] = null!
};
var json = JsonSerializer.Serialize(dict, jsonOpt);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("== JSON ==");
Console.WriteLine(json);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("== Json.NET ==");
var dJsonNet = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
foreach (var kv in dJsonNet!) {
Console.WriteLine($"{kv.Value?.GetType().Name} {kv.Key} = {kv.Value ?? "null"}");
}
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("== System.Text.Json ==");
var dSysTextJson = JsonSerializer.Deserialize<JsonObject>(json)!.ToStringObjectDictionary();
foreach (var kv in dSysTextJson!) {
Console.WriteLine($"{kv.Value?.GetType().Name} {kv.Key} = {kv.Value ?? "null"}");
}
Console.ResetColor();
測試成功!
When deserializing Dictionary<string, object> with System.Text.Json, object value will be JsonElement, not as convient as Json.NET. This article reveal this issue and provide some workaround.
Comments
Be the first to post a comment