Exclure la propriété de la sérialisation via l'attribut personnalisé (json.net)

je dois être capable de contrôler comment/si certaines propriétés sur une classe sont sérialisées. Le cas le plus simple est [ScriptIgnore] . Cependant, je veux seulement que ces attributs soient honorés pour cette situation spécifique de sérialisation sur laquelle je travaille - si d'autres modules en aval de l'application veulent aussi sérialiser ces objets, aucun de ces attributs ne devrait se mettre en travers.

Donc, ma pensée est d'utiliser un attribut personnalisé MyAttribute sur les propriétés, et initialise L'instance spécifique de JsonSerializer avec un crochet qui sait chercher cet attribut.

à première vue, Je ne vois aucun des points de crochet disponibles dans JSON.NET fournira le PropertyInfo pour le bien courant pour faire une telle inspection - seulement la valeur du bien. Ai-je raté quelque chose? Ou une meilleure façon d'aborder cette question?

50
demandé sur Rex M 2012-11-27 19:55:21

6 réponses

vous avez quelques options. Je vous recommande de lire la Json.Net documentation article sur le sujet avant de lire ci-dessous.

l'article présente deux méthodes:

  1. créez une méthode qui retourne une valeur bool basée sur une convention de nommage qui Json.Net suivra pour déterminer s'il faut ou non sérialiser la propriété.
  2. créer un résolveur de contrat personnalisé qui ne tient pas compte de la propriété.

des deux, je préfère celle-ci. Sauter tous les attributs -- ne les utiliser que pour ignorer les propriétés dans toutes les formes de sérialisation. Au lieu de cela, créez un résolveur de contrat personnalisé qui ignore la propriété en question, et n'utilisez le résolveur de contrat que lorsque vous voulez ignorer la propriété, laissant les autres utilisateurs de la classe libres de sérialiser la propriété ou pas à leur propre caprice.

Modifier Pour éviter link rot, je poste le code en question de l'article

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;
   }
}
39
répondu Randolpho 2015-02-20 16:41:49

Voici un générique réutilisable "ignore" résolution basée sur les accepté de répondre :

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

et usage:

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 };
63
répondu drzaus 2017-05-23 10:31:31

utilisez l'attribut JsonIgnore .

par exemple, pour exclure Id :

public class Person {
    [JsonIgnore]
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
51
répondu ramonesteban78 2016-01-11 16:14:15

Voici une méthode basée sur l'excellent contrat serializer de drzaus qui utilise des expressions lambda. Il suffit de l'ajouter à la même classe. Après tout, qui ne préfère pas le compilateur d'effectuer la vérification pour eux?

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

vous pouvez maintenant ignorer les propriétés facilement et couramment:

contract.Ignore<Node>(node => node.NextNode)
    .Ignore<Node>(node => node.AvailableNodes);
24
répondu Steve Rukuts 2013-05-20 18:39:48

Je ne me soucie pas de définir les noms de propriété comme des chaînes, au cas où ils changeraient il briserait mon autre code.

j'avais plusieurs "modes de vue" sur les objets que j'avais besoin de sérialiser, donc j'ai fini par faire quelque chose comme ça dans le résolveur de contrat (mode de vue fourni par l'argument constructeur):

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

où mes objets ressemblent à ceci:

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

l'inconvénient de ceci serait le peu de réflexion dans le résolveur, mais je pense qu'il vaut la peine d'avoir plus de code maintenable.

3
répondu frattaro 2014-03-07 19:36:53

j'ai eu de bons résultats avec la combinaison des réponses de Drzaus et Steve Rukuts. Cependant, je fais face à un problème lorsque je mets JsonPropertyAttribute avec un nom différent ou des plafonds pour la propriété. Par exemple:

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

Inclure UnderlyingName en considération de résoudre le problème:

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;
}
1
répondu Baron Ch'ng 2017-11-29 18:01:08