2015-03-18 15 views
8

Ich versuche, einige JSON, um verschiedene Unterklassen deserialisieren eine benutzerdefinierte mit JsonConverterPolymorphe JSON Deserialisierung Json.Net mit andernfalls

ich this fast bis zu dem Punkt gefolgt.

Meine abstrakte Basisklasse:

abstract class MenuItem 
{ 
    public String Title { get; set; } 
    public String Contents { get; set; } 
    public List<MenuItem> Submenus { get; set; } 
    public String Source { get; set; } 
    public String SourceType { get; set; } 
    public abstract void DisplayContents(); 
} 

Und JsonConverter meine abgeleitet:

class MenuItemConverter : JsonConverter 
    { 
     public override bool CanConvert(Type objectType) 
     { 
      return typeof(MenuItem).IsAssignableFrom(objectType); 
     } 

     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
     { 
      JObject item = JObject.Load(reader); 
      switch (item["SourceType"].Value<String>()) 
      { 
       case SourceType.File: return item.ToObject<Menu.FileMenu>(); 
       case SourceType.Folder: return item.ToObject<Menu.FolderMenu>(); 
       case SourceType.Json: return item.ToObject<Menu.JsonMenu>(); 
       case SourceType.RestGet: return item.ToObject<Menu.RestMenu>(); 
       case SourceType.Rss:  return item.ToObject<Menu.RssMenu>(); 
       case SourceType.Text: return item.ToObject<Menu.TextMenu>(); 
       case SourceType.Url:  return item.ToObject<Menu.UrlMenu>(); 
       default: throw new ArgumentException("Invalid source type"); 
      } 
     } 

     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
     { 
      throw new NotImplementedException(); 
     } 
    } 

SourceType ist nur eine statische Klasse einige String-Konstanten zu halten.

JsonConvert.DeserializeObject<MenuItem>(File.ReadAllText(menuPath), new MenuItemConverter()); 

Nun, mein Problem ist, dass, wenn ich den Code ich folgende Fehlermeldung führen erhalten:

Die JSON-Datei wird wie folgt deserialisiert

in Frage
An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code 

Additional information: Could not create an instance of type ConsoleMenu.Model.MenuItem. Type is an interface or abstract class and cannot be instantiated. Path 'Submenus[0].Title', line 5, position 21. 

Die Json-Datei wie folgt aussieht dies:

{ 
    "Title": "Main Menu", 
    "Submenus": [ 
     { 
      "Title": "Submenu 1", 
      "Contents": "This is an example of the first sub-menu", 
      "SourceType": "Text" 
     }, 
     { 
      "Title": "Submenu 2", 
      "Contents": "This is the second sub-menu", 
      "SourceType": "Text" 
     }, 
     { 
      "Title": "GitHub System Status", 
      "Contents": "{\"status\":\"ERROR\",\"body\":\"If you see this, the data failed to load\"}", 
      "Source": "https://status.github.com/api/last-message.json", 
      "SourceType": "RestGet" 
     }, 
     { 
      "Title": "TF2 Blog RSS", 
      "Contents": "If you see this message, an error has occurred", 
      "Source": "http://www.teamfortress.com/rss.xml", 
      "SourceType": "Rss" 
     }, 
     { 
      "Title": "Submenus Test", 
      "Contents": "Testing the submenu functionality", 
      "Submenus": [ 
       { 
        "Title": "Submenu 1", 
        "Contents": "This is an example of the first sub-menu", 
        "SourceType": "Text" 
       }, 
       { 
        "Title": "Submenu 2", 
        "Contents": "This is the second sub-menu", 
        "SourceType": "Text" 
       } 
      ] 
     } 
    ], 
    "SourceType": "Text" 
} 

Es scheint mir, dass es Probleme bei der Deserialisierung des verschachtelten Objekts hat s, wie komme ich da rum?

+0

Ich fand, dass ich habe genau den gleichen Fehler, wenn ich die JsonConvert.DeserializeObject verwendet und weitergegeben einen Serializer Einstellung mit meinem Konverter hinzugefügt. Als ich die selbe Überladung benutzt habe wie du es funktionierte ... Ich weiß nicht ob das der JsonSerializer ist oder ob ich die Serializer Einstellungen falsch verstanden habe. –

Antwort

14

Zuerst wird für den Menüpunkt "Submenustest" in Ihrem json verpasst.

Zweitens sollten Sie nicht einfach ToObject wegen der Submenus Eigenschaft verwenden, die rekursiv behandelt werden sollte.

Die folgenden ReadJson funktionieren:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
{ 
    var jObject = JObject.Load(reader); 
    var sourceType = jObject["SourceType"].Value<string>(); 

    object target = null; 

    switch (sourceType) 
    { 
     case SourceType.File: 
      target = new FileMenu(); break; 
     case SourceType.Folder: 
      target = new FolderMenu(); break; 
     case SourceType.Json: 
      target = new JsonMenu(); break; 
     case SourceType.RestGet: 
      target = new RestMenu(); break; 
     case SourceType.Rss: 
      target = new RssMenu(); break; 
     case SourceType.Text: 
      target = new TextMenu(); break; 
     case SourceType.Url: 
      target = new UrlMenu(); break; 
     default: 
      throw new ArgumentException("Invalid source type"); 
    } 

    serializer.Populate(jObject.CreateReader(), target); 

    return target; 
} 
+0

Ich wollte dein "BTW" machen, nachdem ich das zur Arbeit gebracht habe, der ursprüngliche Code hatte keinen Polymorphismus, aber ich entschied mich, es zu erweitern, um die Funktionalität zu modularisieren –

+1

Ich löschte das "BTW".Ich habe gerade festgestellt, dass Ihre Eigenschaften derzeit in der Basis 'MenuItem' wesentlich sein sollten. : P –

0

Der Grund, warum Sie den Fehler erhalten, weil Ihre MenuItem Klasse als abstract markiert ist. Ich vermute, dass Sie dies getan haben, um die Implementierung der DisplayContents() Methode in geerbten Klassen zu erzwingen.

Eine andere Art und Weise zu erlauben, die Json zu lesen, zu was Mouhong Lin vorgeschlagen, ist eine Basis Interface für Ihre MenuItem Struktur zu machen, haben Ihre MenuItem Klasse die Schnittstelle mit einer Basisversion der DisplayContents() Methode zu implementieren, markieren Sie ihn als virtuell und überschreiben Sie es dann in Ihren geerbten Unterklassen.
Dieser Ansatz stellt sicher, dass Sie immer etwas angezeigt bekommen, wenn Sie DisplayContents() aufrufen und den Fehler entfernen, den Sie erhalten.

eine sehr grobe und vereinfachte Version der Klassen und Schnittstellen:

public interface IMenuItem 
{ 
    String Title { get; set; } 
    String Contents { get; set; } 
    List<MenuItem> Submenus { get; set; } 
    String Source { get; set; } 
    String SourceType { get; set; } 
    void DisplayContents(); 
} 

public class MenuItem: IMenuItem 
{ 
    public String Title { get; set; } 
    public String Contents { get; set; } 
    public List<MenuItem> Submenus { get; set; } 
    public String Source { get; set; } 
    public String SourceType { get; set; } 
    public virtual void DisplayContents() { MessageBox.Show(Title); } 
} 

// Very very basic implementation of the classes, just to show what can be done 
public class FileMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title + this.GetType().ToString()); } } 
public class FolderMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title + "Folder Class"); } } 
public class JsonMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Contents); } } 
public class RestMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Source); } } 
public class RssMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(SourceType); } } 
public class TextMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title); } } 
public class UrlMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title); } } 
Verwandte Themen