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);
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.
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);
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;
}
}
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
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;
}
}
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.
Vous pouvez ajouter l'annotation [JsonIgnore]
pour ignorer un attribut.
en plus de la réponse donnée par @monrow, vous pouvez utiliser les valeurs par défaut [DataContract] et [DataMember] .
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!
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
}