使用Json.NET使属性反序列化但不序列化

181

我们有一些配置文件,这些文件是使用Json.net序列化C#对象生成的。

我们想要将序列化类的一个属性从简单的枚举属性迁移到类属性。

一种简单的方法是,在类上保留旧的枚举属性,并安排Json.net在加载配置时读取此属性,但在下次序列化对象时不保存它。我们将单独处理从旧枚举到新类的生成过程。

是否有一种简单的方法(例如使用属性)来标记C#对象的属性,以便Json.net仅在反序列化时关注它而在序列化时忽略它?


一个自定义转换器怎么样:你可以把它用作属性,通过重写ReadJson和WriteJson来实现不同的行为,对吧?例如(不完全是你需要的,但是...)http://weblogs.asp.net/thangchung/archive/2010/08/26/customizing-the-converter-for-json-net.aspx - Raphaël Althaus
OnDeserialized attribute 可以成为您的解决方法。 - Estefany Velez
使用[JsonIgnore]属性不应该可以实现吗??http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx - Juri
你能详细说明如何仅在一个方向上使用它吗?就像问题的最后一段所述。 - Will Dean
可以将[JsonIgnore]与装饰有[JsonProperty]属性的次要私有setter结合使用。还有其他几种简单的解决方案。我已经添加了详细的写作说明。 - Brian Rogers
经过1年,我下面的答案只有一个投票,但是如果从属性中删除get;,JSON序列化器将无法读取它,因此它将不再导出旧值。 - simo.3792
13个回答

177

实际上有几种相当简单的方法可以用来达到您想要的结果。

例如,假设您当前定义类如下:

class Config
{
    public Fizz ObsoleteSetting { get; set; }
    public Bang ReplacementSetting { get; set; }
}

enum Fizz { Alpha, Beta, Gamma }

class Bang
{
    public string Value { get; set; }
}

而您想要做的是:

string json = @"{ ""ObsoleteSetting"" : ""Gamma"" }";

// deserialize
Config config = JsonConvert.DeserializeObject<Config>(json);

// migrate
config.ReplacementSetting = 
    new Bang { Value = config.ObsoleteSetting.ToString() };

// serialize
json = JsonConvert.SerializeObject(config);
Console.WriteLine(json);

要获得这个:

{"ReplacementSetting":{"Value":"Gamma"}}

方法一:添加 ShouldSerialize 方法

Json.NET 可以通过在类中查找相应的 ShouldSerialize 方法来有条件地序列化属性。

为了使用该功能,在你的类中添加一个布尔型的 ShouldSerializeBlah() 方法,其中 Blah 被替换为你不想序列化的属性的名称。将此方法的实现始终返回 false

class Config
{
    public Fizz ObsoleteSetting { get; set; }

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

注意:如果你喜欢这种方法,但不想通过引入 ShouldSerialize 方法来混淆类的公共接口,你可以使用一个 IContractResolver 来以编程方式实现相同的效果。请参阅文档中的条件属性序列化

方法二:使用JObjects操作JSON

不要使用JsonConvert.SerializeObject进行序列化,将配置对象加载到JObject中,然后在写出之前从JSON中删除不需要的属性。只需添加几行额外的代码即可。

JObject jo = JObject.FromObject(config);

// remove the "ObsoleteSetting" JProperty from its parent
jo["ObsoleteSetting"].Parent.Remove();

json = jo.ToString();

方法三:巧妙地(滥用)属性

  1. 在你不想序列化的属性上应用 [JsonIgnore] 属性。
  2. 向类中添加一个同原来属性相同类型的私有属性设置器,并将该属性设置器的实现设置为原始属性。
  3. 在备用属性设置器上应用 [JsonProperty] 属性,将其赋予与原始属性相同的 JSON 名称。

这是修订后的 Config 类:

class Config
{
    [JsonIgnore]
    public Fizz ObsoleteSetting { get; set; }

    [JsonProperty("ObsoleteSetting")]
    private Fizz ObsoleteSettingAlternateSetter
    {
        // get is intentionally omitted here
        set { ObsoleteSetting = value; }
    }

    public Bang ReplacementSetting { get; set; }
}

9
在我们的项目中(使用了一个内部集成特定的超集,其中不应序列化任何超类属性),我们通过将 get 属性设置为 internal 来解决了这个问题。拥有公共的 setter 可以让 Web Api 设置属性,但却阻止了它对其进行序列化。 - Daniel Saidi
7
从C# 6.0开始,除了使用“魔术字符串”之外,您还可以使用nameof关键字来配合使用JsonPropertyAttribute。这使得重构变得更加容易和可靠 - 如果您错过了任何重命名的情况,编译器也会提出警告。以@Brian的示例为例,使用方式如下:[JsonProperty(nameof(ObsoleteSetting))] - Geoff James
6
在JsonProperty声明中使用nameof()是个不好的主意,尤其是在这种遗留方案中。JSON代表与另一个接口的外部(并希望是永久的)契约,如果重构后更改JSON属性的名称,将会破坏与所有现有JSON文件和生成以这种格式的JSON组件的兼容性。实际上,最好对每个序列化属性都使用完整名称放置JsonProperty(…),以确保它们在以后更名时不会更改。 - Ammo Goettsch
1
在这种情况下,[JsonProperty(nameof(ObsoleteSetting))] 基本上是一个无操作。使用 JsonProperty 的主要原因是确保 JSON 属性名称固定,而 C# 属性名称可以更改。这使您能够进行重构而不会破坏 API 合同。 - Florian Winter

74

对于任何情况,如果将只进行反序列化的属性标记为internal是可以接受的,那么有一个非常简单的解决方案,完全不依赖于属性。只需将该属性标记为internal get,但public set:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

这将导致使用默认设置/解析器等进行正确反序列化,但该属性将从序列化输出中剥离。


简单而巧妙的解决方案。 - hbulens
请注意,该属性也将被验证模块忽略。因此,您不能再将其标记为[Required]以进行反序列化,因为这取决于公共的“get”方法。 - Martin Hansen
4
这在internalprivate中都不起作用,它总是被序列化。 - Paul
这对我没有起作用。反序列化时出现了属性未找到的错误。 - Drew Sumido
2
我不禁想知道最后两位评论者是否将内部访问修饰符放在了setter而不是getter上。当我使用Json.NET时,这正如所宣传的那样完美地运作。但也许System.Text.Json的工作方式有所不同。 - carlin.scott
运行得非常完美,而且是迄今为止最简单的建议。 - JDTLH9

49

我喜欢在这里坚持使用属性,以下是我在需要反序列化属性但不需要序列化它或反之时使用的方法。

步骤1 - 创建自定义属性

public class JsonIgnoreSerializationAttribute : Attribute { }

步骤 2 - 创建自定义合约解析器

class JsonPropertiesResolver : DefaultContractResolver
{
    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        //Return properties that do NOT have the JsonIgnoreSerializationAttribute
        return objectType.GetProperties()
                         .Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute)))
                         .ToList<MemberInfo>();
    }
}

步骤三 - 在不需要序列化但需要反序列化的情况下添加属性

    [JsonIgnoreSerialization]
    public string Prop1 { get; set; } //Will be skipped when serialized

    [JsonIgnoreSerialization]
    public string Prop2 { get; set; } //Also will be skipped when serialized

    public string Prop3 { get; set; } //Will not be skipped when serialized

步骤四 - 使用它

var sweet = JsonConvert.SerializeObject(myObj, new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() });

希望这可以帮助到您!另外值得注意的是,当反序列化发生时,它也将忽略属性,在我进行反序列化时,我只是按照通常的方式使用转换器。

JsonConvert.DeserializeObject<MyType>(myString);

1
谢谢您提供这个有用的实现。是否有一种方法可以扩展GetSerializableMembers的基本实现,而不是完全覆盖它? - Alain
4
没关系,我意识到它很简单,只需这么写:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList(); - Alain
1
不确定为什么这不是最受欢迎的答案。它很干净,遵循了Newtonsoft的模式,并且易于操作。唯一需要补充的是,您可以使用JsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }进行全局设置。 - Matt M
2
没关系,这实际上并没有做到提问者要求的功能。这基本上是重新创建JsonIgnore。它不会跳过序列化属性,而是在反序列化期间设置属性,因为GetSerializableMembers方法无法知道它是读取还是写入,所以你同时排除了属性的读取和写入。这个解决方案行不通。 - Matt M
这正是我在搜索时脑海中想到的解决方案。 - JJS
@MattM - 这里的关键是在反序列化时不要添加此自定义序列化程序 :) - Bartosz

11

使用setter属性:

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { _ignoreOnSerializing = value; } }

[JsonIgnore]
private string _ignoreOnSerializing;

[JsonIgnore]
public string IgnoreOnSerializing
{
    get { return this._ignoreOnSerializing; }
    set { this._ignoreOnSerializing = value; }
}

希望这能帮到您。


1
请注意,JsonProperty应该有一个大写的IgnoreOnSerializing,等于属性。我建议使用nameof(IgnoreOnSerializing)来避免魔术字符串,在重命名时更加方便。谢谢。 - Bendik August Nesbø

5

在我花费相当长的时间搜索如何将类属性标记为可反序列化但不可序列化后,我发现根本没有这样的事情。所以我想到了一个解决方案,结合两种不同的库或序列化技术 (System.Runtime.Serialization.Json & Newtonsoft.Json),并且我的解决方案如下:

  • flag all your class and sub-classes as "DataContract".
  • flag all the properties of your class and sub-classes as "DataMember".
  • flag all the properties of your class and sub-classes as "JsonProperty" except those you want them not to be serialized.
  • now flag the properties the you do NOT want it to be serialized as "JsonIgnore".
  • then Serialize using "Newtonsoft.Json.JsonConvert.SerializeObject" and De-Serialize using "System.Runtime.Serialization.Json.DataContractJsonSerializer".

    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Runtime.Serialization.Json;
    using System.Text;
    
    namespace LUM_Win.model
    {
        [DataContract]
        public class User
        {
            public User() { }
            public User(String JSONObject)
            {
                MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(JSONObject));
                DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(User));
    
                User user = (User)dataContractJsonSerializer.ReadObject(stream);
                this.ID = user.ID;
                this.Country = user.Country;
                this.FirstName = user.FirstName;
                this.LastName = user.LastName;
                this.Nickname = user.Nickname;
                this.PhoneNumber = user.PhoneNumber;
                this.DisplayPicture = user.DisplayPicture;
                this.IsRegistred = user.IsRegistred;
                this.IsConfirmed = user.IsConfirmed;
                this.VerificationCode = user.VerificationCode;
                this.Meetings = user.Meetings;
            }
    
            [DataMember(Name = "_id")]
            [JsonProperty(PropertyName = "_id")]
            public String ID { get; set; }
    
            [DataMember(Name = "country")]
            [JsonProperty(PropertyName = "country")]
            public String Country { get; set; }
    
            [DataMember(Name = "firstname")]
            [JsonProperty(PropertyName = "firstname")]
            public String FirstName { get; set; }
    
            [DataMember(Name = "lastname")]
            [JsonProperty(PropertyName = "lastname")]
            public String LastName { get; set; }
    
            [DataMember(Name = "nickname")]
            [JsonProperty(PropertyName = "nickname")]
            public String Nickname { get; set; }
    
            [DataMember(Name = "number")]
            [JsonProperty(PropertyName = "number")]
            public String PhoneNumber { get; set; }
    
            [DataMember(Name = "thumbnail")]
            [JsonProperty(PropertyName = "thumbnail")]
            public String DisplayPicture { get; set; }
    
            [DataMember(Name = "registered")]
            [JsonProperty(PropertyName = "registered")]
            public bool IsRegistred { get; set; }
    
            [DataMember(Name = "confirmed")]
            [JsonProperty(PropertyName = "confirmed")]
            public bool IsConfirmed { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "verification_code")]
            public String VerificationCode { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "meeting_ids")]
            public List<Meeting> Meetings { get; set; }
    
            public String toJSONString()
            {
                return JsonConvert.SerializeObject(this, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            }
        }
    }
    
希望您能受益...

1
干得好,Ahmed Abulazm。谢谢你,这确实为我省了很多工作。 :) - Sike12
使用两个序列化框架不是我会积极反对的事情。 - JJS

3

根据应用程序中的位置以及是否只是一个属性,您可以采用一种 手动 的方式,将属性值设置为 null,然后在模型上指定如果该值为 null,则忽略该属性:

[JsonProperty(NullValueHandling = NullValue.Ignore)]
public string MyProperty { get; set; }

如果您在开发ASP.NET Core Web应用程序,并且希望全局设置所有模型中的所有属性,可以在Startup.cs文件中进行如下设置:

public void ConfigureServices(IServiceCollection services) {
    // other configuration here
    services.AddMvc()
        .AddJsonOptions(options => options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore);
}

2

参考@ThoHo的解决方案,只需要使用setter方法,不需要额外的标签。

对于我而言,我之前有一个单一的引用Id,我想要加载并添加到新的引用Id集合中。通过将引用Id的定义更改为仅包含一个setter方法,该方法将值添加到新的集合中。如果属性没有get;方法,Json无法写回该值。

// Old property that I want to read from Json, but never write again. No getter.
public Guid RefId { set { RefIds.Add(value); } }

// New property that will be in use from now on. Both setter and getter.
public ICollection<Guid> RefIds { get; set; }

现在,这个类已经向后兼容到之前的版本,并且只保存新版本的RefIds


2
承接Tho Ho的回答,这个方法也可以用于字段。
[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { IgnoreOnSerializing = value; } }

[JsonIgnore]
public string IgnoreOnSerializing;

0
Jraco11的回答非常简洁明了。如果你想在序列化和反序列化时都使用相同的IContractResolver,那么可以使用以下方法:
public class JsonPropertiesResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (member.IsDefined(typeof(JsonIgnoreSerializationAttribute)))
        {
            property.ShouldSerialize = instance => false;
        }

        return property;
    }
}

0
有没有一种简单的方法来标记(例如使用属性)C#对象的属性,以便在序列化时Json.net将仅忽略它,但在反序列化时注意到它?
截至本文撰写时,我发现最简单的方法是在您的IContractResolver中包含此逻辑。
以下是从上面链接复制的示例代码,供后人参考:
public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }

    public bool ShouldSerializeManager()
    {
        // don't serialize the Manager property if an employee is their own manager
        return (Manager != this);
    }
}

public class ShouldSerializeContractResolver : DefaultContractResolver
{
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
        {
            property.ShouldSerialize =
                instance =>
                {
                    Employee e = (Employee)instance;
                    return e.Manager != e;
                };
        }

        return property;
    }
}

所有的答案都很好,但这种方法似乎是最干净的方式。我实际上是通过查找SkipSerialize和SkipDeserialize属性上的标记来实现这一点,因此您可以标记任何您控制的类。好问题!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接