System.Text.Json反序列化带JsonExtensionData字典时值为JsonElement的问题
问题:Dictionary<string, object>反序列化后值变为JsonElement,如何转为object类型?
序列化Dictionary<string, object>时,反序列化后的值不是预期的object类型,而是JsonElement。编写了单元测试演示该问题,想知道怎么把值反序列化为object类型,是否必须写自定义转换器?
单元测试代码
[Test] public void SerializationTest() { var coll = new PreferenceCollection(); coll.Set("email", "test@test.com"); coll.Set("age", 32); coll.Set("dob", new DateOnly(1991, 2, 14)); var json = JsonSerializer.Serialize(coll); Assert.NotNull(json); var clone = JsonSerializer.Deserialize<PreferenceCollection>(json); Assert.NotNull(clone); Assert.IsNotEmpty(clone!.Values); foreach (var kvp in clone.Values) { Assert.That(kvp.Value is not null); // 测试失败,因为kvp.Value是JsonElement Assert.That(kvp.Value!.Equals(coll.Values[kvp.Key])); } }
PreferenceCollection类代码
using System.Text.Json.Serialization; namespace MyCompany.Preferences; public class PreferenceCollection { public PreferenceCollection() { Values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } [JsonExtensionData] public IDictionary<string, object> Values { get; set; } public object Get(string key) { if (Values.TryGetValue(key, out object value)) { return value; } return null; } public T Get<T>(string key) { if (Values.TryGetValue(key, out var value)) { return (T)value; } return default; } public void Set(string key, object value) { if (value == null) { Remove(key); } else { if (!Values.TryAdd(key, value)) { Values[key] = value; } } } public void Remove(string key) => Values.Remove(key); public void Clear() => Values.Clear(); }
问题原因
你遇到的问题根源在于[JsonExtensionData]特性的设计逻辑:它的作用是捕获JSON中未定义的顶级属性,System.Text.Json默认会将这些属性的值反序列化为JsonElement(用来保留原始JSON结构的类型),而非自动转换为对应的.NET原生类型(比如string、int、DateOnly)。同时,当目标字段类型为object时,框架默认也会用JsonElement来承载任意JSON值。
解决方案
不是必须编写自定义转换器,但自定义转换器是更灵活、可控的方案,下面提供两种可行方法:
方案1:自定义JsonConverter(推荐)
编写针对PreferenceCollection的转换器,手动控制序列化/反序列化流程,确保反序列化时将JSON值转换为对应的.NET类型。
步骤1:实现自定义转换器
using System.Text.Json; using System.Text.Json.Serialization; namespace MyCompany.Preferences; public class PreferenceCollectionConverter : JsonConverter<PreferenceCollection> { public override PreferenceCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var collection = new PreferenceCollection(); if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("JSON格式错误,预期为对象开头"); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) return collection; if (reader.TokenType == JsonTokenType.PropertyName) { var key = reader.GetString(); reader.Read(); // 根据JSON token类型映射到.NET类型 object value = reader.TokenType switch { JsonTokenType.String => reader.GetString(), JsonTokenType.Number when reader.TryGetInt32(out int intVal) => intVal, JsonTokenType.Number when reader.TryGetDouble(out double doubleVal) => doubleVal, JsonTokenType.True => true, JsonTokenType.False => false, JsonTokenType.Null => null, JsonTokenType.StartObject => JsonSerializer.Deserialize<object>(ref reader, options), JsonTokenType.StartArray => JsonSerializer.Deserialize<object[]>(ref reader, options), _ => JsonSerializer.Deserialize<JsonElement>(ref reader, options) }; // 单独处理DateOnly类型(.NET 6+支持) if (value is string str && DateOnly.TryParse(str, out var date)) value = date; collection.Set(key, value); } } throw new JsonException("JSON格式不完整"); } public override void Write(Utf8JsonWriter writer, PreferenceCollection value, JsonSerializerOptions options) { writer.WriteStartObject(); foreach (var kvp in value.Values) { writer.WritePropertyName(kvp.Key); JsonSerializer.Serialize(writer, kvp.Value, options); } writer.WriteEndObject(); } }
步骤2:给PreferenceCollection标记转换器
[JsonConverter(typeof(PreferenceCollectionConverter))] public class PreferenceCollection { // 原有代码保持不变 }
步骤3:添加DateOnly转换器并修改单元测试
因为System.Text.Json默认不支持DateOnly序列化,需要额外添加转换器:
public class DateOnlyJsonConverter : JsonConverter<DateOnly> { private const string DefaultFormat = "yyyy-MM-dd"; public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => DateOnly.ParseExact(reader.GetString(), DefaultFormat, null); public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString(DefaultFormat)); }
修改单元测试,使用配置好的选项:
[Test] public void SerializationTest() { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, Converters = { new DateOnlyJsonConverter() } }; var coll = new PreferenceCollection(); coll.Set("email", "test@test.com"); coll.Set("age", 32); coll.Set("dob", new DateOnly(1991, 2, 14)); var json = JsonSerializer.Serialize(coll, options); Assert.NotNull(json); var clone = JsonSerializer.Deserialize<PreferenceCollection>(json, options); Assert.NotNull(clone); Assert.IsNotEmpty(clone!.Values); foreach (var kvp in clone.Values) { Assert.That(kvp.Value is not null); Assert.That(kvp.Value.Equals(coll.Values[kvp.Key])); } }
方案2:修改扩展数据类型并手动转换
如果不想写自定义转换器,可以将Values的类型改为IDictionary<string, JsonElement>,然后在Get方法中手动转换类型。
修改PreferenceCollection类:
public class PreferenceCollection { public PreferenceCollection() { Values = new Dictionary<string, JsonElement>(StringComparer.OrdinalIgnoreCase); } [JsonExtensionData] public IDictionary<string, JsonElement> Values { get; set; } public object Get(string key) { if (!Values.TryGetValue(key, out var element)) return null; return element.ValueKind switch { JsonValueKind.String => element.GetString(), JsonValueKind.Number when element.TryGetInt32(out int intVal) => intVal, JsonValueKind.String when DateOnly.TryParse(element.GetString(), out var date) => date, // 按需添加其他类型的转换逻辑 _ => element }; } public T Get<T>(string key) { if (Values.TryGetValue(key, out var element)) return element.Deserialize<T>(); return default; } public void Set(string key, object value) { if (value == null) { Remove(key); return; } var jsonElement = JsonSerializer.SerializeToElement(value); if (!Values.TryAdd(key, jsonElement)) Values[key] = jsonElement; } // Remove、Clear方法保持不变 }
这种方法无需自定义转换器,但需要调整Values类型和Get/Set方法的实现,适合类型简单的场景。
内容的提问来源于stack exchange,提问作者Vic F
相关产品推荐
相关产品推荐

