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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 19:16:05