Excluir propriedade da serialização via atributo personalizado (json.net)

Eu preciso ser capaz de controlar como / se determinadas propriedades em uma class são serializadas. O caso mais simples é [ScriptIgnore] . No entanto, só quero que esses atributos sejam honrados para essa situação de serialização específica em que estou trabalhando – se outros módulos downstream no aplicativo também quiserem serializar esses objects, nenhum desses atributos deve atrapalhar.

Então, meu pensamento é usar um atributo personalizado MyAttribute nas propriedades e inicializar a instância específica do JsonSerializer com um gancho que saiba procurar por esse atributo.

À primeira vista, não vejo nenhum dos pontos de gancho disponíveis no JSON.NET fornecerá o PropertyInfo para a propriedade atual para fazer essa inspeção – apenas o valor da propriedade. Estou esquecendo de algo? Ou uma maneira melhor de abordar isso?

Você tem poucas opções. Eu recomendo que você leia o artigo de documentação do Json.Net sobre o assunto antes de ler abaixo.

O artigo apresenta dois methods:

  1. Crie um método que retorne um valor de bool base em uma convenção de nomenclatura que o Json.Net seguirá para determinar se deve ou não serializar a propriedade.
  2. Crie um resolvedor de contrato personalizado que ignore a propriedade.

Dos dois, sou a favor do último. Ignore todos os atributos – use-os apenas para ignorar propriedades em todas as formas de serialização. Em vez disso, crie um resolvedor de contrato personalizado que ignore a propriedade em questão e use apenas o resolvedor de contrato quando desejar ignorar a propriedade, deixando outros usuários da class livres para serializar a propriedade ou não por conta própria.

Editar Para evitar a perda de links, estou postando o código em questão no artigo

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

Aqui está um resolvedor genérico reutilizável “ignore property” baseado na resposta aceita :

 ///  /// Special JsonConvert resolver that allows you to ignore properties. See https://stackoverflow.com/a/13588192/1037948 ///  public class IgnorableSerializerContractResolver : DefaultContractResolver { protected readonly Dictionary> Ignores; public IgnorableSerializerContractResolver() { this.Ignores = new Dictionary>(); } ///  /// Explicitly ignore the given property(s) for the given type ///  ///  /// one or more properties to ignore. Leave empty to ignore the type entirely. public void Ignore(Type type, params string[] propertyName) { // start bucket if DNE if (!this.Ignores.ContainsKey(type)) this.Ignores[type] = new HashSet(); foreach (var prop in propertyName) { this.Ignores[type].Add(prop); } } ///  /// Is the given property for the given type ignored? ///  ///  ///  ///  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); } ///  /// The decision logic goes here ///  ///  ///  ///  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; } } 

E uso:

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

Use o atributo JsonIgnore .

Por exemplo, para excluir o Id :

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

Aqui está um método baseado no excelente contrato de serializador do drzaus que usa expressões lambda. Basta adicioná-lo à mesma class. Afinal, quem não prefere o compilador para fazer a verificação para eles?

 public IgnorableSerializerContractResolver Ignore(Expression> 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; } 

Agora você pode ignorar propriedades com facilidade e fluência:

 contract.Ignore(node => node.NextNode) .Ignore(node => node.AvailableNodes); 

Eu não me importo de definir os nomes das propriedades como strings, no caso de eles mudarem, quebraria meu outro código.

Eu tinha vários “modos de visualização” nos objects que eu precisava para serializar, então acabei fazendo algo parecido com isso no resolvedor de contrato (modo de visualização fornecido pelo argumento do construtor):

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

Onde meus objects se parecem com isso:

 public interface IStatement { [UnregisteredCustomer] string PolicyNumber { get; set; } string PlanCode { get; set; } PlanStatus PlanStatus { get; set; } [UnregisteredCustomer] decimal TotalAmount { get; } [UnregisteredCustomer] ICollection Balances { get; } void SetBalances(IBalance[] balances); } 

A desvantagem disso seria o reflexo do resolvedor, mas acho que vale a pena ter um código mais sustentável.

Eu tive bons resultados com a combinação das respostas de drzaus e Steve Rukuts. No entanto, enfrento um problema quando defino JsonPropertyAttribute com um nome ou limites diferentes para a propriedade. Por exemplo:

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

Include UnderlyingName em consideração resolve o problema:

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