.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; } }
解决方案
核心问题定位
- 序列化未使用自定义配置:
JsonHelper.Serialize方法直接调用默认序列化,未传入包含自定义转换器的_options,导致生成的JSON结构不符合转换器预期,反序列化时无法触发转换器逻辑。 - 私有setter导致属性无法赋值:
ProfitLossData.ProfitLossDataLineItems只有私有setter,System.Text.Json默认无法反序列化此类属性。 - 转换器存在笔误:
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
相关产品推荐
相关产品推荐

