2017-04-11 1 views
0

Vor allem - ich weiß über eine mögliche Lösung meines Problems in diesem speziellen Fall, den ich Ihnen zeigen werde (durch Speichern von Namespaces oder über Custom Deserializer während Deserialisierung, wenn Sie bekommen, was ich meine), aber das ist nicht zukunftssicher für die geplanten Bedürfnisse, also bitte nicht vorschlagen.Deserialisierung Wörterbuch der Schnittstellen über JsonConverter (JSON.net), mit type Eigenschaft

Ich habe angefangen, JSON.net vor nicht so langer Zeit zu verwenden, und dies ist mein erster Versuch bei der Erstellung von benutzerdefinierten Konverter, Beispiel, die ich nirgendwo im Web finden konnte. Ich habe ein paar Konverter verwendet, die für ähnliche Zwecke codiert wurden, also wenn ich hier ein paar unnötige Schritte habe - sei so nett, nicht nur das Problem selbst zu korrigieren, sondern auch, ob es etwas gibt, das entfernt werden kann für die Leistung, danke.

Jetzt zum Code (ich habe alle irrelevanten Dinge abgestreift). Hier ist die Interface-Implementierung:

[JsonConverter(typeof(StringEnumConverter))] 
public enum SomeInterfaceType 
{ 
    NoType = 0, 
    Type1 = 1, 
    Type2 = 2, 
    //... 
} 
[JsonConverter(typeof(SomeInterfaceConverter))] 
public interface SomeInterface 
{ 
    SomeInterfaceType StoredType { get; } 
    //... 
} 

Wie Sie sehen können, die Schnittstelle speichert einige allgemeine Informationen zur Schnittstelle im Zusammenhang sowie StoredType, die wir später für die Identifizierung von bestimmten Klasse während Deserialisierung verwenden.

Klassen sich ziemlich gerade sind nach vorn:

public class Class1 : SomeInterface 
{ 
    public SomeInterfaceType StoredType { get { return SomeInterfaceType.Type1; } } 
    //... 
} 
public class Class2 : SomeInterface 
{ 
    public SomeInterfaceType StoredType { get { return SomeInterfaceType.Type2; } } 
    //... 
} 
//... 

Es gibt durchaus ein paar von denen, und sie haben eine Menge von Eigenschaften und Funktion, aber das ist irrelevant für die Frage, so sind 2 dafür genug Beispiel.

Hier ist, wo diese Klassen (Interfaces) wird im Programm selbst gespeichert werden:

public class LowerClass 
{ 
    public Dictionary<SomeKey, List<SomeInterface>> interfaceDictionary; 
    //... 
} 
public class MiddleClass 
{ 
    private LowerClass ccc; 
    //... 
} 
public class TopClass 
{ 
    private MiddleClass sss; 
    //... 
} 

Die Menge von Klassen, die die Schnittstelle im Laufe der Zeit erhöhen implementieren, und es gibt einige andere Gründe für diese Wahl. Das Speichern von Namespaces funktioniert perfekt für diesen speziellen Fall, aber serialisierte Informationen können von verschiedenen Programmen (verschiedenen Sprachen) verwendet werden, also ist es nicht die beste Lösung, nicht einmal zu erwähnen, wenn jemand Dinge manuell ändern wird (was möglich sein sollte) oder wenn aus irgendeinem Grund der Namensraum sich in der Zukunft ändert, so wie ich schon sagte - nicht die beste Lösung.

Nun zum Kern des Problems zu bewegen:

public class SomeInterfaceConverter: JsonConverter 
{ 
    public SomeInterfaceConverter() { } 
    JsonObjectContract FindContract(JObject obj, JsonSerializer serializer) 
    { 
     SomeInterfaceType typeQ = obj["StoredType"].ToObject<SomeInterfaceType>(); 
     switch (typeQ) 
     { 
      case SomeInterfaceType.Type1: 
       { 
        return serializer.ContractResolver.ResolveContract(typeof(Class1)) as JsonObjectContract; 
       } 
      case SomeInterfaceType.Type2: 
       { 
        return serializer.ContractResolver.ResolveContract(typeof(Class2)) as JsonObjectContract; 
       } 
      //... 
      case SomeInterfaceType.NoType: 
      default: 
       { 
        return null; 
       } 
     } 
    } 
    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(SomeInterface); 
    } 
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) return null; 
     JObject obj = JObject.Load(reader); 
     var contract = FindContract(obj, serializer); 
     if (contract == null) throw new JsonSerializationException("No contract found in " + obj.ToString()); 
     existingValue = contract.DefaultCreator(); 
     using (var sr = obj.CreateReader()) 
     { 
      serializer.Populate(sr, existingValue); 
     } 
     return existingValue; 
    } 
    public override bool CanWrite { get { return false; } } 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 

ich nicht ganz sicher bin, ob ich diesen Vertrag Sache verwenden müssen, oder nicht, aber ich möchte im Grunde custom-Konverter für andere wegzulassen Klassen da draußen. Ich würde auch gerne den benutzerdefinierten Konverter für die ganze Hauptklasse weglassen und möchte den Standardkonverter für alles außer den Klassen, die SomeInterface implementieren, beibehalten (zu diesem Zeitpunkt sind sie nur in diesem bestimmten Wörterbuch vorhanden, aber das könnte sich in der Zukunft ändern auch).

FindContract wird genau wie erwartet ausgeführt, liest alles gut aus dem JSON-Objekt, gibt dann wahrscheinlich den benötigten Vertrag zurück? (im Internet gefunden). Dann wird in dieser Zeile:

existingValue = contract.DefaultCreator(); 

es wirft NullReferenceException. Vertrag ist definitiv da (wenn es richtig produziert wurde). Ich habe ein paar Stunden damit gespielt und verschiedene Workarounds versucht, die ich auf Stackoverflow und anderen Seiten finden konnte, aber ich bin immer noch mit dieser Ausnahme beschäftigt, egal was ich versuchte.

Wie ich von Anfang an gesagt habe - ich bin ziemlich neu in JSON.netto, und wahrscheinlich sollte es einfach sein. Vielleicht brauche ich diesen Vertrag nicht einmal und sollte stattdessen DeserializeObject<> oder etwas anderes verwenden (ich habe mir das auch angeschaut, konnte aber nichts nah genug finden, das könnte ich leicht in meinen Fall übertragen). Alle möglichen Kombinationen von ClassesX und TypX sind bekannt und können ohne Probleme hartcodiert werden.

Vielen Dank im Voraus für Ihre Zeit und alle wertvollen Tipps/Korrekturen.

Update:

Wie in den Kommentaren aufgefordert, hier ist JSON:

{ 
    "LowerClass": { 
    "interfaceDictionary": { 
     "Key1": [ 
     { 
      "StoredType": "Type1" 
     }, 
     { 
      "StoredType": "Type2" 
     } 
     ] 
    } 
    } 
} 

Auch habe ich beschlossen, die Art, wie ich deserialisieren, nur für den Fall hinzufügen (da JSON auslässt über die Spitzenklasse, das nicht es hat keine Relevanz für die Frage sowieso):

MiddleClass whatever = JsonConvert.DeserializeObject<MiddleClass>(source); 
+0

Hier ist eine sehr ähnliche Frage mit einer funktionierenden Lösung: [Json.Net Serialisierung des Typs mit polymorphem Child-Objekt] (https://stackoverflow.com/q/29528648/3744182). Damit wir bei Ihrem spezifischen Problem helfen können, müssen wir den JSON sehen, der die Deserialisierung nicht besteht. Sonst würden wir nur raten. Vielleicht benutzen Sie 'StringEnumConverter' inkonsistent? – dbc

+0

Danke für den Link, ich habe versucht, JsonObjectContract zu JsonContract wie in diesem Fall zu ändern, aber das hat es nicht gelöst. Nun, da ich daran denke, könnte es sein, dass JSON.net versucht, Dictionary mit dem Wert eines der Typen anstelle der Schnittstelle zu erstellen und dort einige Typen aufzufüllen? Ich werde die Frage auch mit JSON und der Art der Deserialisierung aktualisieren. – Snow

+0

Ihr Code kompiliert nicht - in 'public Dictionary > interfaceDictionary;' der Typ 'SomeKey' ist nicht definiert. Außerdem sollte 'private LowerClass ccc; '' public LowerClass LowerClass {get; einstellen; } '. Wenn ich diese behebe, funktioniert dein Code ohne Probleme. Siehe https://dotnetfiddle.net/2pFOTT. Bitte versuchen Sie, ein [mcve] für Ihr Problem bereitzustellen. – dbc

Antwort

0

landete ich durch Code JSON.net graben, und von dem, was ich verstanden - wenn es eine Möglichkeit, contract.DefaultCreator() workin zu zwingen g wie erwartet (nicht werfen NullReferenceException), dann wird es eine ziemlich komplizierte Sache zu erreichen.

Stattdessen herauszufinden versuchen, was der einfachste Weg wäre, zu „reparieren“ DefaultCreator, habe ich einfach eine Abhilfe Funktion und ersetzt es vollständig:

//replaced that: 
existingValue = contract.DefaultCreator(); 
//with that: 
existingValue = ContractToObject(type, contract); 

Die Funktion selbst:

object ContractToObject(SomeInterfaceType type, JsonObjectContract contract) 
{ 
    switch (type) 
    { 
     case SomeInterfaceType.Type1: 
      { 
       return new Class1(Params); 
      } 
     case SomeInterfaceType.Type2: 
      { 
       return new Class2(Params); 
      } 
      //... 
     case SomeInterfaceType.None: 
     default: 
      { 
       return null; 
      } 
    } 
} 
Verwandte Themen