如何让JSON反序列化适配私有字段与只读列表场景?
问题:对外暴露只读列表时JSON反序列化失效
我希望对外仅暴露只读形式的列表,但在适配JSON反序列化时遇到问题。序列化功能正常,序列化后的JSON示例如下:
{ "StartTime": { "WeightedValues": [ { "Value": "6", "Weight": 1.0 }, { "Value": "7", "Weight": 1.0 } ] } }
使用public List的代码可以正常实现序列化与反序列化:
internal class PoSet<T> { public List<PossibleOutcome<T>> WeightedValues = new(); public PoSet() { } public PoSet(IEnumerable<PossibleOutcome<T>> weightedValues) { if (weightedValues == null) throw new ArgumentNullException(nameof(weightedValues)); WeightedValues.AddRange(weightedValues); } public void Add(PossibleOutcome<T> weightedValue) { if (weightedValue == null) throw new ArgumentNullException(nameof(weightedValue)); WeightedValues.Add(weightedValue); } }
但改成仅暴露只读列表并使用[JsonConstructor]特性的代码后,只能正常序列化,反序列化完全失效,我试过几种JSON属性变体都没用:
internal class PoSet<T> { private List<PossibleOutcome<T>> _weightedValues = new(); public IReadOnlyList<PossibleOutcome<T>> WeightedValues => _weightedValues.AsReadOnly(); [JsonConstructor] public PoSet(List<PossibleOutcome<T>> weightedValues) // 给反序列化器用的构造函数 { if (weightedValues == null) throw new ArgumentNullException(nameof(weightedValues)); _weightedValues.AddRange(weightedValues); } public PoSet() { } public PoSet(IEnumerable<PossibleOutcome<T>> weightedValues) { if (weightedValues == null) throw new ArgumentNullException(nameof(weightedValues)); _weightedValues.AddRange(weightedValues); } public void Add(PossibleOutcome<T> weightedValue) { if (weightedValue == null) throw new ArgumentNullException(nameof(weightedValue)); _weightedValues.Add(weightedValue); } }
附上PossibleOutcome类代码供参考:
// 泛型加权值类(支持任意类型T) internal class PossibleOutcome<T> { public T Value { get; set; } public double Weight { get; set; } public PossibleOutcome() { } // 反序列化用的无参构造函数 public PossibleOutcome(T value, double weight) { if (weight < 0) throw new ArgumentException("权重不能为负数。", nameof(weight)); Value = value; Weight = weight; } }
解决方法
问题核心是JSON序列化器(以常用的Newtonsoft.Json为例)无法自动将JSON中的WeightedValues数组映射到你的只读属性或构造函数参数上——因为只读属性没有setter,且构造函数参数的匹配逻辑需要明确的序列化契约。下面是两种可行的修复方案:
方案1:给只读属性标记[JsonProperty]特性
在WeightedValues属性上添加[JsonProperty],明确告诉序列化器该属性需要参与序列化/反序列化流程,序列化器会自动匹配构造函数的weightedValues参数完成赋值:
internal class PoSet<T> { private List<PossibleOutcome<T>> _weightedValues = new(); [JsonProperty] public IReadOnlyList<PossibleOutcome<T>> WeightedValues => _weightedValues.AsReadOnly(); [JsonConstructor] public PoSet(List<PossibleOutcome<T>> weightedValues) { if (weightedValues == null) throw new ArgumentNullException(nameof(weightedValues)); _weightedValues.AddRange(weightedValues); } // 其他构造函数和Add方法保持不变 }
方案2:给私有字段标记[JsonProperty]特性
直接让序列化器操作私有字段,跳过只读属性的中间层,这种方式更直接,也可以保留对外的只读暴露:
internal class PoSet<T> { [JsonProperty("WeightedValues")] private List<PossibleOutcome<T>> _weightedValues = new(); public IReadOnlyList<PossibleOutcome<T>> WeightedValues => _weightedValues.AsReadOnly(); // 可以保留原有构造函数,也可以去掉[JsonConstructor]——序列化器会直接赋值私有字段 public PoSet() { } public PoSet(IEnumerable<PossibleOutcome<T>> weightedValues) { if (weightedValues == null) throw new ArgumentNullException(nameof(weightedValues)); _weightedValues.AddRange(weightedValues); } // Add方法保持不变 }
针对System.Text.Json的补充(.NET Core 3.0+ 默认序列化器)
如果你使用的是System.Text.Json,需要做以下调整:
- 方案1:给只读属性标记
[JsonPropertyName],并确保序列化器配置JsonSerializerOptions.IncludeFields = true; - 方案2:给私有字段同时标记
[JsonPropertyName("WeightedValues")]和[JsonInclude],同时开启IncludeFields配置:
internal class PoSet<T> { [JsonPropertyName("WeightedValues")] [JsonInclude] private List<PossibleOutcome<T>> _weightedValues = new(); public IReadOnlyList<PossibleOutcome<T>> WeightedValues => _weightedValues.AsReadOnly(); // 构造函数和方法保持不变 }
内容的提问来源于stack exchange,提问作者MrGreggles
相关产品推荐
相关产品推荐

