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




serialization (6)

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

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

  • Gère la hiérarchie, donc si vous avez une Interface2[] dans une Interface1 elle sera sérialisée.
  • J'essayais de sérialiser un objet proxy WCF et le résultat JSON est apparu avec {} . Il s'est avéré que toutes les propriétés étaient définies sur Ignore=true . J'ai donc dû ajouter une boucle pour que toutes 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;
        }
    }
    

Avec une classe / interface simple 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é "Name" (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 + Name);


Enfin, j'ai obtenu quand cela ne fonctionnera pas ... Si vous voulez avoir à l'intérieur d'un autre objet complexe, il ne sera pas sérialisé correctement.

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

Il est donc fait en tant que .Net Core JsonContractResolver.

En plus de l'extraction de données, il résout:
a) la conversion camelCase avant d'envoyer des données au client
b) utilise l'interface la plus haute 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
}

Je voudrais partager ce que nous avons fini par faire face à 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, il suffit de sérialiser notre instance Thing , de la désérialiser en tant que DirectThing , puis de la sérialiser en tant que 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 suffit de créer une classe directe ( DirectThing dans cet exemple) au niveau qui vous intéresse. 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 IThing des erreurs si vous ne les avez pas également mises dans DirectThing . Cependant, si vous supprimez un membre X de IThing et que vous le placez dans Thing , vous devrez vous souvenir de le supprimer de DirectThing sinon X sera dans le résultat final.

Du point de vue de la performance, trois opérations de sérialisation sont effectuées ici au lieu d'une. Ainsi, en fonction de votre situation, vous pouvez évaluer la différence de performances des solutions basées sur les réflecteurs / attributs par rapport à cette solution. Dans mon cas, je ne faisais que cela à petite échelle, donc je n'étais pas préoccupé par les pertes potentielles de quelques micro / millisecondes.

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


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

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


Vous pouvez utiliser la sérialisation conditionnelle. Jetez un oeil à ce link . De manière simple, vous devez implémenter l'interface IContractResolver , surcharger la méthode ShouldSerialize et transmettre votre résolveur au constructeur du sérialiseur Json.





json.net