You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 08:24:55