C# 通过自定义属性(json.net)从序列化中排除属性

C# 通过自定义属性(json.net)从序列化中排除属性,c#,serialization,json.net,custom-attributes,C#,Serialization,Json.net,Custom Attributes,我需要能够控制如何/是否序列化类上的某些属性。最简单的情况是[ScriptIgnore]。但是,我只希望这些属性在我正在处理的一个特定的序列化情况下得到尊重-如果应用程序中的其他下游模块也希望序列化这些对象,那么这些属性都不应该妨碍 因此,我的想法是在属性上使用自定义属性MyAttribute,并使用知道查找该属性的钩子初始化JsonSerializer的特定实例 乍一看,JSON.NET中没有任何可用的钩子点会为当前属性提供PropertyInfo来进行这样的检查-只有属性的值。我错过什么了吗

我需要能够控制如何/是否序列化类上的某些属性。最简单的情况是
[ScriptIgnore]
。但是,我只希望这些属性在我正在处理的一个特定的序列化情况下得到尊重-如果应用程序中的其他下游模块也希望序列化这些对象,那么这些属性都不应该妨碍

因此,我的想法是在属性上使用自定义属性
MyAttribute
,并使用知道查找该属性的钩子初始化JsonSerializer的特定实例


乍一看,JSON.NET中没有任何可用的钩子点会为当前属性提供
PropertyInfo
来进行这样的检查-只有属性的值。我错过什么了吗?还是一种更好的方法?

您有几个选择。我建议您在阅读下面的内容之前先阅读Json.Net文档

本文介绍了两种方法:

  • 创建一个方法,根据Json.Net将遵循的命名约定返回
    bool
    值,以确定是否序列化属性
  • 创建忽略属性的自定义协定解析程序 在这两者中,我倾向于后者。完全跳过属性——只使用它们忽略所有序列化形式的属性。相反,创建一个忽略相关属性的自定义协定解析程序,并且仅在希望忽略该属性时使用协定解析程序,从而使该类的其他用户可以自由序列化该属性,也可以不随意序列化

    编辑为了避免链接腐烂,我发布了文章中有问题的代码

    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 =>
             {
                // replace this logic with your own, probably just  
                // return false;
                Employee e = (Employee)instance;
                return e.Manager != e;
             };
          }
    
          return property;
       }
    }
    
    下面是一个基于以下内容的通用可重用“忽略属性”解析器:


    这里是一个基于drzaus优秀的序列化器契约的方法,它使用lambda表达式。只需将其添加到同一个类中。毕竟,谁不喜欢编译器为它们进行检查呢

    public IgnorableSerializerContractResolver Ignore<TModel>(Expression<Func<TModel, object>> selector)
    {
        MemberExpression body = selector.Body as MemberExpression;
    
        if (body == null)
        {
            UnaryExpression ubody = (UnaryExpression)selector.Body;
            body = ubody.Operand as MemberExpression;
    
            if (body == null)
            {
                throw new ArgumentException("Could not get property name", "selector");
            }
        }
    
        string propertyName = body.Member.Name;
        this.Ignore(typeof (TModel), propertyName);
        return this;
    }
    
    公共IgnorableSerializerContractResolver忽略(表达式选择器)
    {
    MemberExpression body=选择器。body作为MemberExpression;
    if(body==null)
    {
    UnaryExpression ubody=(UnaryExpression)selector.Body;
    body=ubody.操作数作为MemberExpression;
    if(body==null)
    {
    抛出新ArgumentException(“无法获取属性名”、“选择器”);
    }
    }
    字符串propertyName=body.Member.Name;
    this.Ignore(typeof(TModel),propertyName);
    归还这个;
    }
    
    现在,您可以轻松流畅地忽略属性:

    contract.Ignore<Node>(node => node.NextNode)
        .Ignore<Node>(node => node.AvailableNodes);
    
    contract.Ignore(node=>node.NextNode)
    .Ignore(node=>node.AvailableNodes);
    
    我不想将属性名设置为字符串,以防它们更改会破坏我的其他代码

    我在需要序列化的对象上有几个“视图模式”,因此我最终在契约解析器中执行了类似的操作(由构造函数参数提供的视图模式):

    我的对象看起来像这样:

    public interface IStatement
    {
        [UnregisteredCustomer]
        string PolicyNumber { get; set; }
    
        string PlanCode { get; set; }
    
        PlanStatus PlanStatus { get; set; }
    
        [UnregisteredCustomer]
        decimal TotalAmount { get; }
    
        [UnregisteredCustomer]
        ICollection<IBalance> Balances { get; }
    
        void SetBalances(IBalance[] balances);
    }
    
    公共接口IStatement
    {
    [未注册客户]
    字符串PolicyNumber{get;set;}
    字符串平面码{get;set;}
    PlanStatus PlanStatus{get;set;}
    [未注册客户]
    十进制总数{get;}
    [未注册客户]
    ICollection余额{get;}
    无效结余(IBalance[]结余);
    }
    

    这样做的缺点是解析器中有一些反射,但我认为有更多可维护的代码是值得的。

    使用
    JsonIgnore
    属性

    例如,要排除
    Id

    public class Person {
        [JsonIgnore]
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    

    我把drzaus和Steve Rukuts的答案结合起来,得到了很好的结果。但是,当我为属性设置不同名称或大写的JsonPropertyAttribute时,我会遇到一个问题。例如:

    [JsonProperty("username")]
    public string Username { get; set; }
    
    将UnderlineName包括在内可解决此问题:

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
    
        if (this.IsIgnored(property.DeclaringType, property.PropertyName)
            || this.IsIgnored(property.DeclaringType, property.UnderlyingName)
            || this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName)
            || this.IsIgnored(property.DeclaringType.BaseType, property.UnderlyingName))
        {
            property.ShouldSerialize = instance => { return false; };
        }
    
        return property;
    }
    

    如果您愿意使用F#(或者只是使用未针对C#进行优化的API),则该库允许您以简单且强类型的方式控制在序列化时是否包含给定属性(并确定在反序列化时是否包含属性),此外,还允许您单独控制/确定可为空性的排除。(完全披露:我是该库的作者。)

    我知道这已经得到了回答,但我发现在分析EF模型时,您需要比较基本类型。如果(this.IsIgnored(property.DeclaringType.BaseType,property.PropertyName))@user576838,这是一个很好的观点。我认为这取决于您的风格(代码优先vs数据库优先)——我认为EF在数据库优先的情况下会生成“随机”代理类,但在代码优先的情况下可能不会?不过包括支票可能更安全@user576838-在启动bucket时的Ignore方法中添加了对EFI的额外检查,如果(!Ignores.ContainsKey(type))忽略[type]=new HashSet(),则按如下方式声明HashSet以忽略propertyname的大小写更改此
    接口有一个
    null
    基类型,它使
    包含的
    崩溃。我们也应该检查一下
    | |(property.DeclaringType.BaseType!=null&&this.IsIgnored(property.DeclaringType.BaseType,property.PropertyName))
    事实上,我喜欢
    成员表达式
    技巧——它的工作原理类似于反射,但感觉不那么笨拙。我要到处用这个。希望它仍在运行…;)它的速度肯定比你的版本慢,但我觉得编译器为你检查它的代价是值得的。除非你把这个放在O(n ^ 2)循环的中间,否则我怀疑它会影响什么。初步阅读告诉我,无论如何,它比反射要快得多。所以我在重复使用这个
    表达式
    技巧时,遇到了嵌套属性的绊脚石
    public class Person {
        [JsonIgnore]
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
    [JsonProperty("username")]
    public string Username { get; set; }
    
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
    
        if (this.IsIgnored(property.DeclaringType, property.PropertyName)
            || this.IsIgnored(property.DeclaringType, property.UnderlyingName)
            || this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName)
            || this.IsIgnored(property.DeclaringType.BaseType, property.UnderlyingName))
        {
            property.ShouldSerialize = instance => { return false; };
        }
    
        return property;
    }