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

.NET 8中System.Text.Json反序列化接口字典始终返回null

问题描述

我正尝试将PlanningData类进行JSON序列化与反序列化并存储到数据库中。序列化过程看似正常,但在反序列化Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption>时始终返回null,尽管反序列化时在Visual Studio的JSON查看器中可以正确查看该字典的键和值。

我使用的是.NET 8,尝试了多态反序列化相关方案,但未能解决问题,反序列化仍返回null。


相关代码

数据库序列化配置

modelBuilder.Entity<SomeEntity>()
    .Property(c => c.PlanningData)
    .HasColumnName("PlanningData")
    .HasConversion(
        v => JsonHelper.Serialize(v),
        v => JsonHelper.Deserialize<PlanningData>(v)
    );

核心类定义

PlanningData

public class PlanningData
{
    public required ProfitLossData ProfitLossData { get; init; }
}

ProfitLossData

public class ProfitLossData
{
    // 此属性反序列化始终返回null
    public Dictionary<string, List<ProfitLossDataLineItem>> ProfitLossDataLineItems { get; private set;}
}

ProfitLossDataLineItem & DataLineItem

public class ProfitLossDataLineItem : DataLineItem
{
}

public class DataLineItem
{
    [System.Text.Json.Serialization.JsonConverter(typeof(PlanningCalculatedResultConverter))]
    public Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption> AllPlanningCellValueObjects { get; set; } = new Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption>
                                        {
                                            { PlanningType.A, new Class1ThatImplementsInterface() },
                                            { PlanningType.B, new Class2ThatImplementsInterface() },
                                            { PlanningType.C, new Class3ThatImplementsInterface() },
                                            { PlanningType.D, new Class4ThatImplementsInterface() },
                                            { PlanningType.E, new Class5ThatImplementsInterface()},                        
    };
}

JsonHelper工具类

public static class JsonHelper
{
    static readonly JsonSerializerOptions _options = new()
    {
        Converters = { new PlanningCalculatedResultConverter(), new NullableObjectArrayConverter()  }
    };

    public static string Serialize<T>(T value)
    {
        return System.Text.Json.JsonSerializer.Serialize(value);
    }

    public static T Deserialize<T>(string json)
    {
        // 此处从未调用我的PlanningCalculatedResultConverter
        var deserializedValue = System.Text.Json.JsonSerializer.Deserialize<T>(json, _options) ?? throw new InvalidOperationException("Deserialization returned null.");
        return deserializedValue;
    }
}

自定义转换器PlanningCalculatedResultConverter

public class PlanningCalculatedResultConverter : JsonConverter<Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption>>
{
    public override Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // 将JSON反序列化为JsonDocument
        using (JsonDocument doc = JsonDocument.ParseValue(ref reader))
        {
            var dictionary = new Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption>();

            // 遍历JSON文档中的每个属性
            foreach (var property in doc.RootElement.EnumerateObject())
            {
                // 从属性名称解析PlanningType
                if (Enum.TryParse<PlanningType>(property.Name, out var planningType))
                {
                    // 将值反序列化为合适的类型
                    var value = DeserializePlanningCalculatedResult(property.Value, planningType, options);

                    // 将反序列化后的值添加到字典中
                    dictionary.Add(planningType, value);
                }
                else
                {
                    throw new JsonException($"Unsupported PlanningType: {property.Name}");
                }
            }

            return dictionary;
        }
    }

    private IPlanningCalculatedResultAndRequiredInputDataForSingleOption DeserializePlanningCalculatedResult(JsonElement element, PlanningType planningType, JsonSerializerOptions options)
    {
        switch (planningType)
        {
            case PlanningType.A:
                return JsonSerializer.Deserialize<Class1ThatImplementsInterface>(element.GetRawText(), options);
            case PlanningType.B:
                return JsonSerializer.Deserialize<Class2ThatImplementsInterface>(element.GetRawText(), options);
            case PlanningType.C:
                return JsonSerializer.Deserialize<Class3ThatImplementsInterface>(element.GetRawText(), options);
            case PlanningType.D:
                return JsonSerializer.Deserialize<Class4ThatImplementsInterface>(element.GetRawText(), options);
            case PlanningType.E:
                return JsonSerializer.Deserialize<Class5ThatImplementsInterfaceatedResult>(element.GetRawText(), options);
            default:
                throw new JsonException($"Unsupported PlanningType: {planningType}");
        }
    }

    public override void Write(Utf8JsonWriter writer, Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption> value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        foreach (var kvp in value)
        {
            writer.WritePropertyName(kvp.Key.ToString());

            JsonSerializer.Serialize(writer, kvp.Value, kvp.Value.GetType(), options);
        }
        writer.WriteEndObject();
    }
}

接口定义

public interface IPlanningCalculatedResultAndRequiredInputDataForSingleOption
{
    public bool IsOptionInDefaultState => InputParameters.Values.All(x => x.IsParameterInDefaultState);
    public double?[] CalculatedOptionResult { get; set; }

    public PlanningType PlanningType { get; } 
    public Dictionary<InputParameter, ISingleInputParameter> InputParameters { get; }
}

public interface ISingleInputParameter
{
    public bool IsParameterInDefaultState => InputValues.Length == 0;

    public double[] InputValues { get; set; }

    public string InputParameterName { get; set; }

    public bool IsPercentageData { get; set; }
}

解决方案

核心问题定位

  1. 序列化未使用自定义配置:JsonHelper.Serialize方法直接调用默认序列化,未传入包含自定义转换器的_options,导致生成的JSON结构不符合转换器预期,反序列化时无法触发转换器逻辑。
  2. 私有setter导致属性无法赋值:ProfitLossData.ProfitLossDataLineItems只有私有setter,System.Text.Json默认无法反序列化此类属性。
  3. 转换器存在笔误:DeserializePlanningCalculatedResult方法中Class5ThatImplementsInterfaceatedResult是错误类型名称,与初始化时的Class5ThatImplementsInterface不匹配。

修复步骤

1. 修正JsonHelper.Serialize方法

确保序列化和反序列化使用相同的配置:

public static string Serialize<T>(T value)
{
    return System.Text.Json.JsonSerializer.Serialize(value, _options);
}

2. 处理私有setter问题

选择以下两种方式之一:

  • 将setter改为公共:
    public Dictionary<string, List<ProfitLossDataLineItem>> ProfitLossDataLineItems { get; set; }
    
  • 保留私有setter,添加无参数构造函数并初始化属性:
    public class ProfitLossData
    {
        public Dictionary<string, List<ProfitLossDataLineItem>> ProfitLossDataLineItems { get; private set; }
    
        public ProfitLossData()
        {
            ProfitLossDataLineItems = new Dictionary<string, List<ProfitLossDataLineItem>>();
        }
    }
    

3. 修正转换器中的类型笔误

将Class5ThatImplementsInterfaceatedResult改为Class5ThatImplementsInterface:

case PlanningType.E:
    return JsonSerializer.Deserialize<Class5ThatImplementsInterface>(element.GetRawText(), options);

4. 优化枚举解析逻辑

添加忽略大小写的枚举解析,避免大小写不匹配问题:

if (Enum.TryParse<PlanningType>(property.Name, ignoreCase: true, out var planningType))

5. 增强序列化配置

在_options中添加必要配置,提升兼容性:

static readonly JsonSerializerOptions _options = new()
{
    Converters = { new PlanningCalculatedResultConverter(), new NullableObjectArrayConverter() },
    WriteIndented = true, // 方便调试查看JSON结构
    PropertyNameCaseInsensitive = true, // 忽略属性名称大小写
    IncludeFields = true // 如果有字段需要序列化/反序列化可启用
};

内容的提问来源于stack exchange,提问作者Murmelmann

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 21:25:52