【问题标题】:How does JSON.NET set a get-only IEnumerable<T> property?JSON.NET 如何设置仅获取的 IEnumerable<T> 属性?
【发布时间】:2019-07-25 08:33:38
【问题描述】:

在以下示例中,foodeserializedFoo 变量都具有完全填充的 Values 属性。为 [JsonConstructor] 属性赋予了一个私有的无操作构造函数,以避免来自公共构造函数的构造函数值映射。

尽管如此,如果 JSON.NET 在构造期间将其支持字段分配给一个空列表,它似乎能够设置 get-only 属性的值。

我在 JSON.NET 文档中找不到任何提及此行为的内容。谁能解释这种行为?

void Main()
{
    var foo = new Foo(new [] { "One", "Two", "Three" });
    var json = JsonConvert.SerializeObject(foo);
    var deserializedFoo = JsonConvert.DeserializeObject<Foo>(json);
}

class Foo
{
    private readonly List<string> _values;

    public Foo(IEnumerable<string> values)
    {
        _values = values.ToList();
    }

    [JsonConstructor]
    private Foo()
    {
        _values = new List<string>();
    }

    public IEnumerable<string> Values => _values;
}

【问题讨论】:

  • 我猜它是通过反射魔法完成的......
  • 我想你可以在 here 的某个地方找到它,虽然我不太清楚如何/在哪里 :)
  • 您期望在这里发生什么?

标签: c# json.net


【解决方案1】:

我不确定这是在哪里记录的(如果有的话),但是在对象的反序列化过程中,如果已经分配了引用类型成员,Json.NET 将重新使用现有的成员实例而不是分配新的成员实例一。然后它将使用成员值的实际、具体 类型(而不是声明的类型)将 JSON 填充到成员中。在您的情况下,Values 返回的对象的实际具体类型是 List&lt;string&gt; - Json.NET 能够填充。

如果我将Values 更改为实际返回只读枚举,如下所示:

public IEnumerable<string> Values => _values.AsReadOnly();

然后Values 没有填充——演示小提琴#1 here

另一方面,如果我将 Values 的声明类型更改为 object,如下所示:

public object Values => _values;

填充。演示小提琴 #2 here.

作为最后的演示,如果我修改您的 Foo 模型,使其具有一些额外的预分配只读属性 Bar,该属性仅声明为 object,如下所示:

class Foo
{
    private readonly List<string> _values;
    private readonly Bar _bar = new Bar();

    public Foo(IEnumerable<string> values)
    {
        _values = values.ToList();
    }

    [JsonConstructor]
    private Foo()
    {
        _values = new List<string>();
    }

    public IEnumerable<string> Values { get { return _values; } }

    public object Bar { get { return _bar; } }
}

public class Bar
{
    public string Property1 { get; set; }
}

那么该属性所引用的实际对象就会被成功填充:

var json = @"{""Values"":[""One"",""Two"",""Three""],""Bar"":{""Property1"":""Test""}}";
var deserializedFoo = JsonConvert.DeserializeObject<Foo>(json);
Assert.IsTrue(deserializedFoo.Values.Count() == 3 && ((Bar)deserializedFoo.Bar).Property1 == "Test"); // No Assert

演示小提琴#3 here.

如果您不想要这种行为,您有什么选择?

首先,您可以将您的集合包装在一个只读包装器中,如上所示。一般来说,这不是一个坏主意,因为它可以防止 Foo 类型的使用者简单地将 Values 转换为 List&lt;T&gt; 并对其进行修改。

其次,您可以使用 ObjectCreationHandling.Replace 进行反序列化。启用此设置后,Json.NET 将始终为预分配的读/写属性分配新值——并跳过预分配的只读属性。演示小提琴 #4 here。请注意,这可以防止填充引用任何 .Net 引用类型的只读成员,而不仅仅是集合。

第三,您可以通过Pavlo Lissov 采用从this answerSerialize Property, but Do Not Deserialize Property in Json.Net 的方法,并创建一个自定义合同解析器,将JsonProperty.ShouldDeserialize 设置为一个谓词,该谓词对于不应该的属性返回false反序列化。

【讨论】:

  • 真正全面的答案。谢谢。
猜你喜欢
  • 2020-04-20
  • 2012-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-02
  • 1970-01-01
  • 2019-12-24
相关资源
最近更新 更多