63

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

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

乍一看,我没有看到 JSON.NET 中的任何可用挂钩点将为PropertyInfo当前属性提供进行此类检查的方法 - 只有属性的值。我错过了什么吗?或者更好的方法来解决这个问题?

4

7 回答 7

74

这是一个基于公认答案的通用可重用“忽略属性”解析器:

/// <summary>
/// Special JsonConvert resolver that allows you to ignore properties.  See https://stackoverflow.com/a/13588192/1037948
/// </summary>
public class IgnorableSerializerContractResolver : DefaultContractResolver {
    protected readonly Dictionary<Type, HashSet<string>> Ignores;

    public IgnorableSerializerContractResolver() {
        this.Ignores = new Dictionary<Type, HashSet<string>>();
    }

    /// <summary>
    /// Explicitly ignore the given property(s) for the given type
    /// </summary>
    /// <param name="type"></param>
    /// <param name="propertyName">one or more properties to ignore.  Leave empty to ignore the type entirely.</param>
    public void Ignore(Type type, params string[] propertyName) {
        // start bucket if DNE
        if (!this.Ignores.ContainsKey(type)) this.Ignores[type] = new HashSet<string>();

        foreach (var prop in propertyName) {
            this.Ignores[type].Add(prop);
        }
    }

    /// <summary>
    /// Is the given property for the given type ignored?
    /// </summary>
    /// <param name="type"></param>
    /// <param name="propertyName"></param>
    /// <returns></returns>
    public bool IsIgnored(Type type, string propertyName) {
        if (!this.Ignores.ContainsKey(type)) return false;

        // if no properties provided, ignore the type entirely
        if (this.Ignores[type].Count == 0) return true;

        return this.Ignores[type].Contains(propertyName);
    }

    /// <summary>
    /// The decision logic goes here
    /// </summary>
    /// <param name="member"></param>
    /// <param name="memberSerialization"></param>
    /// <returns></returns>
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (this.IsIgnored(property.DeclaringType, property.PropertyName)
        // need to check basetype as well for EF -- @per comment by user576838
        || this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName)) {
            property.ShouldSerialize = instance => { return false; };
        }

        return property;
    }
}

和用法:

var jsonResolver = new IgnorableSerializerContractResolver();
// ignore single property
jsonResolver.Ignore(typeof(Company), "WebSites");
// ignore single datatype
jsonResolver.Ignore(typeof(System.Data.Objects.DataClasses.EntityObject));
var jsonSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = jsonResolver };
于 2013-01-24T20:28:17.197 回答
71

使用JsonIgnore属性。

例如,要排除Id

public class Person {
    [JsonIgnore]
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
于 2014-10-10T22:46:07.373 回答
50

你有几个选择。我建议您在阅读以下内容之前阅读有关该主题的 Json.Net 文档文章。

文章介绍了两种方法:

  1. 创建一个bool根据 Json.Net 将遵循的命名约定返回值的方法,以确定是否序列化该属性。
  2. 创建一个忽略该属性的自定义合同解析器。

两者之中,我赞成后者。完全跳过属性——仅使用它们来忽略所有形式的序列化中的属性。相反,创建一个忽略相关属性的自定义合约解析器,并且仅在您想要忽略该属性时使用合约解析器,让该类的其他用户可以自由地序列化该属性,也可以不随心所欲。

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

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;
   }
}
于 2012-11-27T16:04:26.710 回答
30

这是一种基于 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;
}

您现在可以轻松流畅地忽略属性:

contract.Ignore<Node>(node => node.NextNode)
    .Ignore<Node>(node => node.AvailableNodes);
于 2013-05-20T10:18:49.363 回答
3

我不在乎将属性名称设置为字符串,以防他们改变它会破坏我的其他代码。

我在需要序列化的对象上有几个“视图模式”,所以我最终在合同解析器中做了这样的事情(构造函数参数提供的视图模式):

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
    JsonProperty property = base.CreateProperty(member, memberSerialization);
    if (viewMode == ViewModeEnum.UnregisteredCustomer && member.GetCustomAttributes(typeof(UnregisteredCustomerAttribute), true).Length == 0)
    {
        property.ShouldSerialize = instance => { return false; };
    }

    return property;
}

我的对象看起来像这样:

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

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

于 2014-03-07T14:27:56.507 回答
1

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

[JsonProperty("username")]
public string Username { get; set; }

将 UnderlyingName 纳入考虑可解决问题:

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;
}
于 2017-11-29T18:01:08.670 回答
0

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

于 2018-11-04T17:25:22.653 回答