2015-03-18 10 views
8

Sto cercando di deserializzare alcuni JSON a varie sottoclassi utilizzando un custom JsonConverterpolimorfico JSON deserializzazione non riuscendo utilizzando Json.Net

ho seguito this quasi al punto.

mio astratta della classe base:

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

E il mio derivato JsonConverter:

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 è solo una classe statica contenente alcune costanti di stringa.

Il file JSON viene deserializzato in questo modo:

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

Ora, il mio problema è che ogni volta che esegue il codice ottengo il seguente errore:

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. 

Il file JSON in questione si presenta come this:

{ 
    "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" 
} 

Mi sembra che abbia problemi di deserializzazione dell'oggetto nidificato s, come faccio ad aggirarlo?

+0

ho scoperto che ho avuto esattamente lo stesso errore se ho usato il JsonConvert.DeserializeObject e superato un impostazioni serializzatore con mio convertitore aggiunto. Quando ho usato lo stesso sovraccarico di come ha funzionato ... Non so se questo sia il JsonSerializer in errore o la mia comprensione delle impostazioni del serializzatore. –

risposta

14

In primo luogo, SourceType non è presente per la voce di menu "Sottomenu Test" in JSON.

In secondo luogo, non è sufficiente utilizzare ToObject a causa della proprietà Submenus, che deve essere gestita in modo ricorsivo.

Il seguente ReadJson funzionerà:

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

Avevo intenzione di fare il tuo "btw" dopo che ho ottenuto questo lavoro, il codice originale non aveva polimorfismo, ma ho deciso di estenderlo per modularizzare la funzionalità –

+1

Ho cancellato quella "btw".Ho appena trovato le tue proprietà attualmente nella base 'MenuItem' dovrebbe essere essenziale. : P –

0

La ragione si stanno ottenendo l'errore è perché la classe MenuItem è contrassegnato come abstract. Suppongo che l'abbia fatto per applicare l'implementazione del metodo DisplayContents() nelle classi ereditate.

Un modo diverso di permettere al JSON da leggere, a ciò che Mouhong Lin suggerito, è quello di fare una base Interface per la vostra struttura MenuItem, hanno la classe MenuItem implementi l'interfaccia con una versione di base del metodo DisplayContents(), segnare come virtuale e quindi sostituirlo nelle sottoclassi ereditate.
Questo approccio garantisce che verrà sempre visualizzato qualcosa quando si chiama DisplayContents() e si rimuove l'errore che si sta ottenendo.

Una versione molto rozza e semplificata delle classi e l'interfaccia:

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