使属性反序列化但不使用json.net序列化


122

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

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

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

是否有任何简单的方法来标记(例如,带有属性)C#对象的属性,以便Json.net仅在序列化时将其忽略,而在反序列化时将其保留?


那么自定义转换器呢:您可以将其用作属性的属性,以不同的方式覆盖ReadJson和WriteJson,不是吗?示例(不正是你需要的,但是......)weblogs.asp.net/thangchung/archive/2010/08/26/...
拉斐尔奥尔索斯


使用`[JsonIgnore]'属性不可能吗?james.newtonking.com/archive/2009/10/23/…–
Juri

根据q的最后一段,您是否能够扩展仅在一个方向上使用它的方式?
迪安

可以将[JsonIgnore]与装饰有[JsonProperty]属性的辅助私有设置器结合使用。还有一些其他简单的解决方案。我添加了详细的文章。
Brian Rogers

Answers:


132

实际上,可以使用几种相当简单的方法来获得所需的结果。

例如,假设您当前定义了这样的类:

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"}}

方法1:添加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方法弄混类的公共接口,则可以使用an IContractResolver来以编程方式执行相同的操作。请参阅文档中的条件属性序列化

方法2:使用JObjects处理JSON

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

JObject jo = JObject.FromObject(config);

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

json = jo.ToString();

方法3:巧妙(滥用)属性使用

  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; }
}

7
我们通过将get-properties设置为internal来解决它(在该项目中,它使用了基础模型的内部特定于集成的超级集,在该超级集中,不应该对任何超类属性进行序列化)。有了公共设置程序,Web Api可以设置属性,但是阻止其序列化它们。
Daniel Saidi

7
JsonPropertyAttribute从C#6.0开始,与结合使用,可以使用nameof关键字而不是“魔术字符串”。这使得重构一个很多更容易和防呆-加,如果你错过任何重命名事件,编译器会警告你,反正。使用@Brian的示例,用法如下:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James

1
在JsonProperty声明中使用nameof()是个坏主意,尤其是在这种旧方案中。JSON代表与另一个接口的外部(并且希望是永恒的)合同,并且您绝对不想在重构时更改JSON属性的名称。您将破坏所有以这种格式生成JSON的现有JSON文件和组件的兼容性。实际上,最好将JsonProperty(…)和全名放在每个序列化的属性上,以确保在以后重命名该属性时它们不会更改。
Ammo Goettsch

36

对于只将反序列化属性标记为内部的任何情况,都有一种非常简单的解决方案,它完全不依赖于属性。只需将属性标记为内部获取,但将其设为公共集即可:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

使用默认设置/解析器/等可以导致正确的反序列化,但是该属性已从序列化输出中删除。


简单而巧妙的解决方案。
hbulens

适当注意,验证模块也将忽略该属性。(因此,您不能再将其标记为反序列化的[Required],因为它依赖于公共get方法)。
马丁·汉森

2
internal都不适用private。它总是序列化的。
保罗

这对我不起作用。反序列化时出现属性未找到错误。
Drew Sumido

31

我喜欢在这个属性上坚持使用属性,这是我需要反序列化属性但不序列化属性时使用的方法,反之亦然。

步骤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>();
    }
}

步骤3-在不需要序列化但反序列化的地方添加属性

    [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

第4步-使用它

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

希望这可以帮助!还值得注意的是,当反序列化发生时,这也会忽略属性,当我进行反序列化时,我只是以常规方式使用转换器。

JsonConvert.DeserializeObject<MyType>(myString);

感谢您的帮助。有没有办法扩展GetSerializableMembers而不是完全覆盖基础实现?
阿兰(Alain)

4
没关系,只是意识到这很简单:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
阿兰

不知道为什么这不是最受好评的答案。它很干净,遵循newtonsoft的模式并且易于执行。我唯一要补充的是,您可以使用JsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M

1
没关系,这实际上并没有执行问询者所要求的。这基本上是在重新创建JsonIgnore。它不会跳过要序列化的属性,而是在反序列化期间设置属性,因为GetSerializableMembers方法无法知道它是读还是写,因此您要排除这两个属性。此解决方案不起作用。
Matt M

7

使用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)重命名时避免使用魔术字符串。
迪克AugustNesbø17年

5

在花了很长时间研究了如何将一个类属性标记为可反序列化和不可序列化之后,我发现根本没有这样做的事情。所以我想出了一个结合了两种不同的库或序列化技术(System.Runtime.Serialization.Json和Newtonsoft.Json)的解决方案,它对我的​​工作如下:

  • 将您所有的类和子类标记为“ DataContract”。
  • 将您的类和子类的所有属性标记为“ DataMember”。
  • 将您的类和子类的所有属性标记为“ JsonProperty”,但不希望它们不被序列化的属性除外。
  • 现在标记您不希望将其序列化为“ JsonIgnore”的属性。
  • 然后使用“ Newtonsoft.Json.JsonConvert.SerializeObject”进行序列化,然后使用“ 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
Bravo Ahmed Abulazm。谢谢,这确实使我免于大量工作。:)
Sike12年

2

参照@ThoHo的解决方案,实际上只需要使用setter,而无需其他标签。

对我来说,我以前只有一个参考ID,我想加载并添加到新的参考ID集合中。通过将引用ID的定义更改为仅包含setter方法,该方法将值添加到新集合中。如果Property没有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; }

现在,该类与以前的版本向后兼容,并且仅保存新版本的RefId


1

要根据胡浩的答案,这也可以用于领域。

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

[JsonIgnore]
public string IgnoreOnSerializing;

1

根据发生在应用程序中的位置以及它仅仅是一个属性,您可以通过一种手动方式将属性值设置为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);
}


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的属性上查找属性来实现的,因此您可以标记任何您控制的类。好问题!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.