J'ai une définition de classe qui contient une propriété qui renvoie une interface.
public class Foo
{
public int Number { get; set; }
public ISomething Thing { get; set; }
}
Tenter de sérialiser la classe Foo à l'aide de Json.NET me donne un message d'erreur du type "Impossible de créer une instance de type" ISomething ". ISomething peut être une interface ou une classe abstraite."
Existe-t-il un attribut ou un convertisseur Json.NET qui me permettrait de spécifier une Something
classe concrète à utiliser lors de la désérialisation?
.net
serialization
json.net
dthrasher
la source
la source
Réponses:
L'une des choses que vous pouvez faire avec Json.NET est:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
L'
TypeNameHandling
indicateur ajoutera une$type
propriété au JSON, ce qui permet à Json.NET de savoir dans quel type concret il a besoin pour désérialiser l'objet. Cela vous permet de désérialiser un objet tout en remplissant une interface ou une classe de base abstraite.L'inconvénient, cependant, est que cela est très spécifique à Json.NET. le
$type
sera un type complet, donc si vous le sérialisez avec des informations de type, le désérialiseur doit également être en mesure de le comprendre.Documentation: Paramètres de sérialisation avec Json.NET
la source
TypeNameHandling
. Voir la mise en garde TypeNameHandling dans Newtonsoft Json pour plus de détails.Vous pouvez y parvenir grâce à l'utilisation de la classe JsonConverter. Supposons que vous ayez une classe avec une propriété d'interface;
public class Organisation { public string Name { get; set; } [JsonConverter(typeof(TycoonConverter))] public IPerson Owner { get; set; } } public interface IPerson { string Name { get; set; } } public class Tycoon : IPerson { public string Name { get; set; } }
Votre JsonConverter est responsable de la sérialisation et de la désérialisation de la propriété sous-jacente;
public class TycoonConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(IPerson)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return serializer.Deserialize<Tycoon>(reader); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // Left as an exercise to the reader :) throw new NotImplementedException(); } }
Lorsque vous travaillez avec une organisation désérialisée via Json.Net, l'adresse IPerson sous-jacente de la propriété Owner sera de type Tycoon.
la source
Au lieu de passer un objet JsonSerializerSettings personnalisé à JsonConvert.SerializeObject () avec l'option TypeNameHandling.Objects, comme mentionné précédemment, vous pouvez simplement marquer cette propriété d'interface spécifique avec un attribut afin que le JSON généré ne soit pas gonflé avec les propriétés "$ type" sur CHAQUE objet:
public class Foo { public int Number { get; set; } // Add "$type" property containing type info of concrete class. [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )] public ISomething { get; set; } }
la source
Dans la version la plus récente du convertisseur Newtonsoft Json tiers, vous pouvez définir un constructeur avec un type concret relatif à la propriété interfacée.
public class Foo { public int Number { get; private set; } public ISomething IsSomething { get; private set; } public Foo(int number, Something concreteType) { Number = number; IsSomething = concreteType; } }
Tant que Something implémente ISomething, cela devrait fonctionner. Ne placez pas non plus de constructeur vide par défaut au cas où le convertisseur JSon tente de l'utiliser, vous devez le forcer à utiliser le constructeur contenant le type concret.
PS. cela vous permet également de rendre vos setters privés.
la source
J'ai eu le même problème, alors j'ai créé mon propre convertisseur qui utilise l'argument des types connus.
public class JsonKnownTypeConverter : JsonConverter { public IEnumerable<Type> KnownTypes { get; set; } public JsonKnownTypeConverter(IEnumerable<Type> knownTypes) { KnownTypes = knownTypes; } protected object Create(Type objectType, JObject jObject) { if (jObject["$type"] != null) { string typeName = jObject["$type"].ToString(); return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+","))); } throw new InvalidOperationException("No supported type"); } public override bool CanConvert(Type objectType) { if (KnownTypes == null) return false; return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Load JObject from stream JObject jObject = JObject.Load(reader); // Create target object based on JObject var target = Create(objectType, jObject); // Populate the object properties serializer.Populate(jObject.CreateReader(), target); return target; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
J'ai défini deux méthodes d'extension pour la désérialisation et la sérialisation:
public static class AltiJsonSerializer { public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null) { if (string.IsNullOrEmpty(jsonString)) return default(T); return JsonConvert.DeserializeObject<T>(jsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Converters = new List<JsonConverter> ( new JsonConverter[] { new JsonKnownTypeConverter(knownTypes) } ) } ); } public static string SerializeJson(this object objectToSerialize) { return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}); } }
Vous pouvez définir votre propre façon de comparer et d'identifier les types dans les convertis, j'utilise uniquement le nom de la classe.
la source
Normalement, j'ai toujours utilisé la solution avec
TypeNameHandling
comme suggéré par DanielT, mais dans les cas ici, je n'ai pas eu de contrôle sur le JSON entrant (et je ne peux donc pas m'assurer qu'il inclut une$type
propriété), j'ai écrit un convertisseur personnalisé qui vous permet simplement de spécifier explicitement le type de béton:public class Model { [JsonConverter(typeof(ConcreteTypeConverter<Something>))] public ISomething TheThing { get; set; } }
Cela utilise simplement l'implémentation du sérialiseur par défaut de Json.Net tout en spécifiant explicitement le type concret.
Le code source et un aperçu sont disponibles sur ce billet de blog .
la source
Je voulais juste compléter l'exemple que @Daniel T. nous a montré ci-dessus:
Si vous utilisez ce code pour sérialiser votre objet:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
Le code pour désérialiser le json devrait ressembler à ceci:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);
Voici comment un json se conforme lors de l'utilisation de l'
TypeNameHandling
indicateur:la source
Je me suis demandé la même chose, mais j'ai bien peur que cela ne puisse pas être fait.
Regardons les choses de cette façon. Vous remettez à JSon.net une chaîne de données et un type dans lequel désérialiser. Que doit faire JSON.net quand il atteint cet ISomething? Il ne peut pas créer un nouveau type de ISomething car ISomething n'est pas un objet. Il ne peut pas non plus créer un objet qui implémente ISomething, car il ne sait pas lequel des nombreux objets qui peuvent hériter ISomething il devrait utiliser. Les interfaces sont quelque chose qui peut être automatiquement sérialisé, mais pas automatiquement désérialisé.
Ce que je ferais serait de chercher à remplacer ISomething par une classe de base. En utilisant cela, vous pourrez peut-être obtenir l'effet que vous recherchez.
la source
Voici une référence à un article écrit par ScottGu
Sur cette base, j'ai écrit un code qui, je pense, pourrait être utile
public interface IEducationalInstitute { string Name { get; set; } } public class School : IEducationalInstitute { private string name; #region IEducationalInstitute Members public string Name { get { return name; } set { name = value; } } #endregion } public class Student { public IEducationalInstitute LocalSchool { get; set; } public int ID { get; set; } } public static class JSONHelper { public static string ToJSON(this object obj) { JavaScriptSerializer serializer = new JavaScriptSerializer(); return serializer.Serialize(obj); } public static string ToJSON(this object obj, int depth) { JavaScriptSerializer serializer = new JavaScriptSerializer(); serializer.RecursionLimit = depth; return serializer.Serialize(obj); } }
Et c'est comme ça que tu appellerais ça
School myFavSchool = new School() { Name = "JFK High School" }; Student sam = new Student() { ID = 1, LocalSchool = myFavSchool }; string jSONstring = sam.ToJSON(); Console.WriteLine(jSONstring); //Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}
Si je comprends bien, je ne pense pas que vous ayez besoin de spécifier une classe concrète qui implémente l'interface pour la sérialisation JSON.
la source