【问题标题】:Tagging private setters with JsonIgnoreAttribute使用 JsonIgnoreAttribute 标记私有 setter
【发布时间】:2014-03-20 22:44:45
【问题描述】:

我注意到,默认情况下,JSON.NET 只会(反)序列化对象的公共属性。这很好。但是,当属性被标记为 [JsonPropertyAttribute] 时,JSON.NET 也将访问 private getter 和 setter。这很糟糕。

我想要解决这个问题的方法是用 [JsonIgnoreAttribute] 标记私有 getter/setter。

例如:

public class JsonObject
{
    [JsonProperty(PropertyName = "read_write_property")]
    public object ReadOnlyProperty
    {
        get;
        [JsonIgnore] private set;
    }
}

很遗憾,这不是有效的 C# 代码。那么什么代码可以达到同样的效果呢?

我知道一些可行的想法:

  1. 移除 [JsonPropertyAttribute]
  2. 完全移除 setter 并引入支持字段

只有这两个选项吗?

编辑

我为我的只读属性添加了支持字段。不确定,但我想我在 Json.Net 中发现了一个错误。当仅存在 getter 时,即使指定的 name 属性与 JSON 字符串匹配,序列化程序也会将该属性视为不存在。这特别烦人,因为我也在使用[JsonExtensionData] 机制。所以反序列化的值最终会进入我的扩展数据字典。下面是演示问题的代码:

违规班级

using System.ComponentModel;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public class JsonObject
{
    private readonly BindingDirection bindingDirection;

    public JsonObject()
    {
        this.bindingDirection = BindingDirection.OneWay;
    }

    [JsonProperty(PropertyName = "binding_type"), JsonConverter(typeof(StringEnumConverter))]
    public BindingDirection BindingDirection
    {
        get
        {
            return this.bindingDirection;
        }
    }

    [JsonExtensionData]
    public IDictionary<string, object> ExtensionData { get; set; }
}

演示

using Newtonsoft.Json;

class Program
{
    static void Main(string[] args)
    {
        var obj = new JsonObject();

        var serialized = JsonConvert.SerializeObject(obj);

        var deserialized = JsonConvert.DeserializeObject<JsonObject>(serialized);

        Console.WriteLine("*** Extension data ***\n");

        foreach (var kvp in deserialized.ExtensionData)
        {
            Console.WriteLine("{0} == {1}", kvp.Key, kvp.Value);
        }

        Console.ReadLine();
    }
}

输出

*** 扩展数据***

binding_type == OneWay

【问题讨论】:

  • 您实际上想要达到什么结果?如果您有一个带有私有 setter 的公共字段,您是说您希望该字段序列化但不反序列化?
  • @BrianRogers,是的。我只希望将该属性序列化为 JSON。该属性是从构造函数初始化的(示例中未显示),因此我不必从 JSON 字符串中检索它。
  • 但最重要的是,我只是希望 JSON.NET 不再违反面向对象编程最基本的原则之一!

标签: c# json.net


【解决方案1】:

Json.Net 在使用[JsonProperty] 属性标记时访问私有成员的事实实际上是设计使然。存在此功能是为了使 Json.Net 能够用于序列化/反序列化对象的内部状态(例如持久化),而无需为该状态公开公共接口。
来自documentation

JsonPropertyAttribute

JsonPropertyAttribute 有很多用途:

  • 默认情况下,JSON 属性将与 .NET 属性同名。此属性允许自定义名称。

  • 表示当成员序列化设置为选择加入时,应序列化属性。

  • 在序列化和反序列化中包含非公共属性。

  • 自定义属性值的类型名称、引用、null 和默认值处理。

  • 自定义属性的集合项JsonConverter,类型名处理和引用处理。

不幸的是,[JsonProperty] 属性的参数显然没有提供一种方法来选择不包括非公共成员,同时仍然允许您使用其他功能,例如更改属性名称。因此,您必须在这种情况下使用解决方法。

您已经提到了几种可能性,我将添加第三种可能性:

  1. 删除[JsonPropertyAttribute]
  2. 完全移除 setter 并引入支持字段
  3. 为您的班级实现自定义 JsonConverter

其中,第二个是最简洁和最容易实现的,同时仍能实现您正在寻找的结果:公共属性将使用自定义的属性名称进行序列化,而私有支持字段在反序列化期间不会受到影响。

第一个选项,删除属性,不会完全得到你想要的。虽然它会阻止该字段被反序列化,但您必须接受在序列化期间写入 JSON 的原始属性名称。

第三个选项,编写转换器,让您可以完全控制类的序列化和反序列化方式。您可以更改属性名称、省略属性、包含甚至不在类中的附加信息,等等。它们并不难写;但是,如果您真正需要的只是一个简单的支持字段来解决您的问题,那么转换器在这里可能是矫枉过正的。也就是说,如果您真的对此选项感兴趣,我很乐意提供一个简单的示例。请告诉我。

编辑

关于您的扩展数据问题 - 您是对的,这看起来确实像一个错误。当我最初尝试重现该问题时,我不能,因为我的 JsonObject 版本不包含默认构造函数。我假设您的构造函数有一个参数来接受只读属性的初始值。一旦我删除了参数,它就开始出现异常。我不确定为什么构造函数会对 JsonExtensionData 的解释方式产生影响。但是,这确实提出了一个奇怪的解决方法:尝试将以下内容添加到您的课程中,看看是否可以解决问题。

    [JsonConstructor]
    private JsonObject(string dummy) : this()
    {
    }

【讨论】:

  • 这将是整个班级的转换器,还是只是那个单一的财产?我想知道添加具有CanWrite == false 的转换器是否会强制序列化程序忽略该属性。
  • 不确定,但我想我发现了一个错误。当仅存在 getter 时,即使指定的 name 属性与 JSON 字符串匹配,序列化程序也会将该属性视为不存在。这特别烦人,因为我也在使用[JsonExtensionData] 机制。所以反序列化的值最终会进入我的扩展数据字典。
  • 关于转换器,我说的是为整个JsonObject 类创建一个转换器。我尝试了您的想法,即仅为一个属性制作一个基本上为空的转换器并使用CanRead == false(因此它会序列化但不会反序列化该属性),但这不起作用:当 Json.Net 看到CanRead == false 这意味着“不要在这部分使用转换器;而是使用默认行为”。那么我们回到最初的问题。
  • 关于您的第二条评论,我无法重现您描述的问题。你能发布一个示例来演示这个问题吗?
  • 谢谢,我现在看到了。我已经用一种可能会有所帮助的解决方法更新了我的答案,尽管它非常难看。
猜你喜欢
  • 2014-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-27
  • 2015-10-03
  • 2018-08-06
  • 1970-01-01
  • 2018-06-03
相关资源
最近更新 更多