web-dev-qa-db-fra.com

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

Je dois pouvoir contrôler comment/si certaines propriétés d'une classe sont sérialisées. Le cas le plus simple est [ScriptIgnore]. Toutefois, je souhaite uniquement que ces attributs soient pris en compte pour cette situation de sérialisation spécifique sur laquelle je travaille. Si d'autres modules en aval de l'application souhaitent également sérialiser ces objets, aucun de ces attributs ne doit gêner.

Je pensais donc utiliser un attribut personnalisé MyAttribute sur les propriétés et initialiser l'instance spécifique de JsonSerializer avec un hook qui sait rechercher cet attribut.

À première vue, aucun des points d'ancrage disponibles dans JSON.NET ne fournira la valeur PropertyInfo à la propriété actuelle pour effectuer une telle inspection - seule la valeur de la propriété. Est-ce que je manque quelque chose? Ou une meilleure façon d'aborder cela?

51
Rex M

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

L'article présente deux méthodes: 

  1. Créez une méthode qui renvoie une valeur bool en fonction d'une convention de dénomination que Json.Net suivra pour déterminer s'il convient ou non de sérialiser la propriété.
  2. Créez un résolveur de contrat personnalisé qui ignore la propriété. 

Des deux, je favorise le dernier. Ignorer complètement les attributs - utilisez-les uniquement pour ignorer les propriétés de 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 utilisez-le uniquement lorsque vous souhaitez ignorer la propriété, en laissant les autres utilisateurs de la classe libres de sérialiser la propriété ou de ne pas le laisser à leur guise. 

Edit Pour éviter la pourriture de lien, 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;
   }
}
42
Randolpho

Voici un résolveur générique "ignorer la propriété" réutilisable basé sur la réponse acceptée :

/// <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 utilisation:

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

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

Voici une méthode basée sur l'excellent contrat de sérialisation de drzaus qui utilise des expressions lambda. Ajoutez-le simplement à la même classe. Après tout, qui ne préfère pas que le compilateur fasse 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);
26
Steve Rukuts

Je ne me soucie pas de définir les noms de propriété en tant que chaînes, au cas où ils changeraient cela romprait mon autre code.

J'avais plusieurs "modes de visualisation" sur les objets que je devais sérialiser, alors j'ai fini par faire quelque chose comme ceci dans le résolveur de contrat (mode de visualisation 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 que cela vaut la peine d'avoir un code plus maintenable.

3
frattaro

J'ai eu de bons résultats avec la combinaison des réponses drzaus et Steve Rukuts. Toutefois, je rencontre un problème lorsque je définis JsonPropertyAttribute avec un nom ou des majuscules différents pour la propriété. Par exemple:

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

Inclure UnderlyingName en considération résout 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
Baron Ch'ng

Si vous souhaitez utiliser F # (ou simplement utiliser une API non optimisée pour C #), la bibliothèque FSharp.JsonSkippable vous permet de contrôler de manière simple et fortement typée si une propriété donnée doit être incluse lors de la sérialisation (et déterminer si une propriété a été incluse lors de la désérialisation), et de plus, contrôler/déterminer l'exclusion séparément de la nullité. (Divulgation complète: je suis l'auteur de la bibliothèque.)

0
cmeeren