web-dev-qa-db-fra.com

Sérialiser uniquement les propriétés d'interface en JSON avec Json.net

Avec une simple classe/interface comme celle-ci 

public interface IThing
{
    string Name { get; set; }
}

public class Thing : IThing
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Comment puis-je obtenir la chaîne JSON avec uniquement la propriété "Nom" (uniquement les propriétés de l'interface sous-jacente)?

En fait, quand je fais ça:

var serialized = JsonConvert.SerializeObject((IThing)theObjToSerialize, Formatting.Indented);
Console.WriteLine(serialized);

Je reçois l'objet complet en tant que JSON (Id + Nom);

28
eka808

Vous pouvez utiliser la sérialisation conditionnelle. Jetez un oeil à ce link . En gros, vous devez implémenter l'interface IContractResolver, surcharger la méthode ShouldSerialize et transmettre votre résolveur au constructeur de Json Serializer.

10
Nikola Dimitroff

La méthode que j'utilise,

public class InterfaceContractResolver : DefaultContractResolver
{
    private readonly Type _InterfaceType;
    public InterfaceContractResolver (Type InterfaceType)
    {
        _InterfaceType = InterfaceType;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        //IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
        IList<JsonProperty> properties = base.CreateProperties(_InterfaceType, memberSerialization);
        return properties;
    }
}

// To serialize do this:
var settings = new JsonSerializerSettings() {
     ContractResolver = new InterfaceContractResolver (typeof(IThing))
});
string json = JsonConvert.SerializeObject(theObjToSerialize, settings);
22
user3161686

Inspiré de @ user3161686, voici une petite modification apportée à InterfaceContractResolver:

public class InterfaceContractResolver<TInterface> : DefaultContractResolver where TInterface : class
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(typeof(TInterface), memberSerialization);
        return properties;
    }
}
16
rasx

Version améliorée avec interfaces imbriquées + prise en charge des objets xsd.exe

Encore une autre variation ici. Le code provient de http://www.tomdupont.net/2015/09/how-to-only-serialize-interface.html avec les améliorations suivantes par rapport à d'autres réponses ici

  • Gère la hiérarchie, donc si vous avez un Interface2[] dans un Interface1, il sera sérialisé.
  • J'essayais de sérialiser un objet proxy WCF et le JSON résultant est apparu sous la forme {}. Il s'est avéré que toutes les propriétés étaient définies sur Ignore=true. Il a donc fallu ajouter une boucle pour que toutes les propriétés ne soient pas ignorées. 

    public class InterfaceContractResolver : DefaultContractResolver
    {
        private readonly Type[] _interfaceTypes;
    
        private readonly ConcurrentDictionary<Type, Type> _typeToSerializeMap;
    
        public InterfaceContractResolver(params Type[] interfaceTypes)
        {
            _interfaceTypes = interfaceTypes;
    
            _typeToSerializeMap = new ConcurrentDictionary<Type, Type>();
        }
    
        protected override IList<JsonProperty> CreateProperties(
            Type type,
            MemberSerialization memberSerialization)
        {
            var typeToSerialize = _typeToSerializeMap.GetOrAdd(
                type,
                t => _interfaceTypes.FirstOrDefault(
                    it => it.IsAssignableFrom(t)) ?? t);
    
            var props = base.CreateProperties(typeToSerialize, memberSerialization);
    
            // mark all props as not ignored
            foreach (var prop in props)
            {
                prop.Ignored = false;
            }
    
            return props;
        }
    }
    
10
Simon_Weaver

Une alternative à [JsonIgnore] sont les attributs [DataContract] et [DataMember] . Si votre classe est étiquetée avec [DataContract], le sérialiseur ne traitera que les propriétés étiquetées avec l'attribut [DataMember] (JsonIgnore est un modèle "opt-out" alors que DataContract est "op-in").

[DataContract]
public class Thing : IThing
{
    [DataMember]
    public int Id { get; set; }

    public string Name { get; set; }
}

La limite des deux approches est qu'elles doivent être implémentées dans la classe, vous ne pouvez pas les ajouter à la définition d'interface. 

6
Marc LaFleur

Vous pouvez ajouter l'annotation [JsonIgnore] pour ignorer un attribut.

5
monrow

en plus de la réponse donnée par @monrow, vous pouvez utiliser les valeurs par défaut [DataContract] et [DataMember] . 

http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx

2
Parv Sharma

Je voudrais partager ce que nous avons fini par faire quand confrontés à cette tâche. Compte tenu de l'interface et de la classe de l'OP ...

public interface IThing
{
    string Name { get; set; }
}

public class Thing : IThing
{
   public int Id { get; set; }
   public string Name { get; set; }
}

... nous avons créé une classe qui est l'implémentation directe de l'interface ...

public class DirectThing : IThing
{
   public string Name { get; set; }
}

Ensuite, vous avez simplement sérialisé notre instance Thing, désérialisée sous la forme DirectThing, puis Sérialisée sous la forme DirectThing:

var thing = new Thing();
JsonConvert.SerializeObject(
    JsonConvert.DeserializeObject<DirectThing>(JsonConvert.SerializeObject(thing)));

Cette approche peut fonctionner avec une longue chaîne d'héritage d'interface ... il vous suffit de créer une classe directe (DirectThing dans cet exemple) au niveau souhaité. Pas besoin de s'inquiéter de la réflexion ou des attributs.

Du point de vue de la maintenance, la classe DirectThing est facile à gérer si vous ajoutez des membres à IThing car le compilateur générera des erreurs si vous ne les avez pas également insérées dans DirectThing. Cependant, si vous supprimez un membre X de IThing et placez-le dans Thing à la place, vous devrez alors vous rappeler de le supprimer de DirectThing sinon X serait dans le résultat final.

Du point de vue des performances, trois opérations de (dé) sérialisation ont lieu ici au lieu d'une. Par conséquent, selon votre situation, vous pouvez évaluer la différence de performances des solutions à réflecteur/attribut par rapport à cette solution. Dans mon cas, je ne faisais que cela à petite échelle, donc je ne m'inquiétais pas des pertes potentielles de quelques micro/millisecondes.

J'espère que ça aide quelqu'un!

1
sammy34

Finalement, j'ai eu quand ça ne marcherait pas ... Si vous voulez avoir à l'intérieur d'un autre objet complexe, il ne sera pas correctement sérialisé.

J'ai donc créé une version qui extraira uniquement les données stockées dans un assemblage spécifique et pour les types ayant la même interface de base.

Donc, il est fait comme .Net Core JsonContractResolver.

En plus de l'extraction des données, il résout:
a) Conversion de camelCase avant d’envoyer des données au client
b) utilise la plupart des interfaces supérieures de la portée autorisée (par Assembly) c) corrige l'ordre des champs: le champ de la plupart des classes de base sera répertorié en premier et l'objet imbriqué respectera également cette règle.

public class OutputJsonResolver : DefaultContractResolver
{
    #region Static Members
    private static readonly object syncTargets = new object();
    private static readonly Dictionary<Type, IList<JsonProperty>> Targets = new Dictionary<Type, IList<JsonProperty>>();

    private static readonly Assembly CommonAssembly = typeof(ICommon).Assembly;
    #endregion

    #region Override Members
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        if (type.Assembly != OutputJsonResolver.CommonAssembly)
            return base.CreateProperties(type, memberSerialization);

        IList<JsonProperty> properties;
        if (OutputJsonResolver.Targets.TryGetValue(type, out properties) == false)
        {
            lock (OutputJsonResolver.syncTargets)
            {
                if (OutputJsonResolver.Targets.ContainsKey(type) == false)
                {
                    properties = this.CreateCustomProperties(type, memberSerialization);

                    OutputJsonResolver.Targets[type] = properties;
                }
            }
        }

        return properties;
    }
    protected override string ResolvePropertyName(string propertyName)
    {
        return propertyName.ToCase(Casing.Camel);
    }
    #endregion

    #region Assistants
    private IList<JsonProperty> CreateCustomProperties(Type type, MemberSerialization memberSerialization)
    {
        // Hierarchy
        IReadOnlyList<Type> types = this.GetTypes(type);

        // Head
        Type head = types.OrderByDescending(item => item.GetInterfaces().Length).FirstOrDefault();

        // Sources
        IList<JsonProperty> sources = base.CreateProperties(head, memberSerialization);

        // Targets
        IList<JsonProperty> targets = new List<JsonProperty>(sources.Count);

        // Repository
        IReadOnlyDistribution<Type, JsonProperty> repository = sources.ToDistribution(item => item.DeclaringType);

        foreach (Type current in types.Reverse())
        {
            IReadOnlyPage<JsonProperty> page;
            if (repository.TryGetValue(current, out page) == true)
                targets.AddRange(page);
        }

        return targets;
    }
    private IReadOnlyList<Type> GetTypes(Type type)
    {
        List<Type> types = new List<Type>();

        if (type.IsInterface == true)
            types.Add(type);

        types.AddRange(type.GetInterfaces());

        return types;
    }
    #endregion
}
1
Maxim