2015-10-26 13 views
10

Ich habe eine Benutzerklasse. Und zwei Unterklassen. Eltern und Kind. Ich bekomme JSON von meinem Server mit {"Benutzer": "..."} und muss es in Eltern oder Kind je nach Benutzer konvertieren.TypBenutzerdefinierte Konverter zu Unterklasse mit Moshi

Wie ich verstehe, muss ich benutzerdefinierte Konverter auf diese Weise hinzufügen:

 Moshi moshi = new Moshi.Builder() 
      .add(new UserAdapter()) 
      .build(); 

Hier ist meine Implementierung von UserAdapter. Ich weiß, es ist blind, aber es funktioniert nicht einmal auf diese Weise:

public class UserAdapter { 

@FromJson 
User fromJson(String userJson) { 
    Moshi moshi = new Moshi.Builder().build(); 
    try { 
     JSONObject jsonObject = new JSONObject(userJson); 
     String accountType = jsonObject.getString("type"); 

     switch (accountType) { 
      case "Child": 
       JsonAdapter<Child> childJsonAdapter = moshi.adapter(Child.class); 
       return childJsonAdapter.fromJson(userJson); 
      case "Parent": 
       JsonAdapter<Parent> parentJsonAdapter = moshi.adapter(Parent.class); 
       return parentJsonAdapter.fromJson(userJson); 

     } 
    } catch (JSONException | IOException e) { 
     e.printStackTrace(); 
    } 

    return null; 
} 

@ToJson 
String toJson(User user) { 
    Moshi moshi = new Moshi.Builder().build(); 
    JsonAdapter<User> jsonAdapter = moshi.adapter(User.class); 
    String toJson = jsonAdapter.toJson(user); 
    return toJson; 
} 

Zunächst einmal bekomme ich folgende Ausnahme mit diesem Code.

com.squareup.moshi.JsonDataException: Expected a string but was BEGIN_OBJECT at path $.user 

Und zweitens glaube ich, dass es einen besseren Weg gibt, es zu tun. Bitte um Rat.

Upd. hier ist Stacktrace für den Fehler:

com.squareup.moshi.JsonDataException: Expected a name but was BEGIN_OBJECT at path $.user 
at com.squareup.moshi.JsonReader.nextName(JsonReader.java:782) 
at com.squareup.moshi.ClassJsonAdapter.fromJson(ClassJsonAdapter.java:141) 
at com.squareup.moshi.JsonAdapter$1.fromJson(JsonAdapter.java:68) 
at com.squareup.moshi.JsonAdapter.fromJson(JsonAdapter.java:33) 
at retrofit.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:33) 
at retrofit.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:23) 
at retrofit.OkHttpCall.parseResponse(OkHttpCall.java:148) 
at retrofit.OkHttpCall.execute(OkHttpCall.java:116) 
at retrofit.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:111) 
at retrofit.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:88) 
at rx.Observable$2.call(Observable.java:162) 
at rx.Observable$2.call(Observable.java:154) 
at rx.Observable$2.call(Observable.java:162) 
at rx.Observable$2.call(Observable.java:154) 
at rx.Observable.unsafeSubscribe(Observable.java:7710) 
at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) 
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) 
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422) 
at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152) 
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
at java.lang.Thread.run(Thread.java:818) 

Antwort

6

Diese wie das Beispiel mir scheint, dass Sie für Ihre individuelle de/Serialisierung Ihrer JSON-Daten folgen mögen: https://github.com/square/moshi#another-example

Es verwendet eine Zwischenklasse, die den JSON entspricht Struktur, und Moshi wird es automatisch für Sie aufblasen. Dann können Sie die überhöhten Daten verwenden, um Ihre spezialisierten Benutzerklassen zu erstellen. Zum Beispiel:

// Intermediate class with JSON structure 
class UserJson { 
    // Common JSON fields 
    public String type; 
    public String name; 
    // Parent JSON fields 
    public String occupation; 
    public Long salary; 
    // Child JSON fields 
    public String favorite_toy; 
    public Integer grade; 
} 

abstract class User { 
    public String type; 
    public String name; 
} 

final class Parent extends User { 
    public String occupation; 
    public Long salary; 
} 

final class Child extends User { 
    public String favoriteToy; 
    public Integer grade; 
} 

nun der Adapter:

class UserAdapter { 
    // Note that you pass in a `UserJson` object here 
    @FromJson User fromJson(UserJson userJson) { 
    switch (userJson.type) { 
    case "Parent": 
     final Parent parent = new Parent(); 
     parent.type = userJson.type; 
     parent.name = userJson.name; 
     parent.occupation = userJson.occupation; 
     parent.salary = userJson.salary; 
     return parent; 
    case "Child": 
     final Child child = new Child(); 
     child.type = userJson.type; 
     child.name = userJson.name; 
     child.favoriteToy = userJson.favorite_toy; 
     child.grade = userJson.grade; 
     return child; 
    default: 
     return null; 
    } 
    } 

    // Note that you return a `UserJson` object here. 
    @ToJson UserJson toJson(User user) { 
    final UserJson json = new UserJson(); 
    if (user instanceof Parent) { 
     json.type = "Parent"; 
     json.occupation = ((Parent) user).occupation; 
     json.salary = ((Parent) user).salary; 
    } else { 
     json.type = "Child"; 
     json.favorite_toy = ((Child) user).favoriteToy; 
     json.grade = ((Child) user).grade; 
    } 
    json.name = user.name; 
    return json; 
    } 
} 

denke ich, dass dies viel saubere und ermöglicht Moshi seine Sache zu tun, die Objekte aus JSON und der Schaffung von JSON von Objekten erstellt. Kein Herumalbern mit altmodischen JSONObject!

Zum Test:

Child child = new Child(); 
child.type = "Child"; 
child.name = "Foo"; 
child.favoriteToy = "java"; 
child.grade = 2; 
Moshi moshi = new Moshi.Builder().add(new UserAdapter()).build(); 
try { 
    // Serialize 
    JsonAdapter<User> adapter = moshi.adapter(User.class); 
    String json = adapter.toJson(child); 
    System.out.println(json); 
    // Output is: {"favorite_toy":"java","grade":2,"name":"Foo","type":"Child"} 

    // Deserialize 
    // Note the cast to `Child`, since this adapter returns `User` otherwise. 
    Child child2 = (Child) adapter.fromJson(json); 
    System.out.println(child2.name); 
    // Output is: Foo 
} catch (IOException e) { 
    e.printStackTrace(); 
} 
+0

Hey, danke für die answear, werde ich versuchen, Ihren Code in dieser Woche zu überprüfen und als corect markieren, wenn es – Defuera

+0

@Defuera Ну как funktioniert? Etwas Glück? – savanto

+0

@savanto Ist das Erstellen eines Adapters notwendig? –

3

Sie wahrscheinlich versucht zu implementieren Parsen Sie nach: https://github.com/square/moshi#custom-type-adapters

Es String als Argument @FromJson Methode verwendet wird, so kann es auf magische Weise zu einem gewissen Mapping analysiert werden Hilfsklasse oder String und wir müssen es manuell analysieren, oder? Eigentlich nein, Sie können entweder Mapping-Helper-Klasse oder Karte verwenden.

So Ihre Ausnahme Expected a string but was BEGIN_OBJECT at path $.user wurde von Moshi verursacht versuchen, dass Benutzer als String zu bekommen (denn das ist, was Sie in Ihrem Adapter angedeutet), während es nur ein weiteres Objekt.

Ich mag es nicht alle möglichen Felder zu einer Hilfsklasse zu analysieren, da im Falle von Polymorphie diese Klasse sehr groß werden kann und man sich darauf verlassen muss, Code zu merken/zu kommentieren.

Sie es als eine Karte handhaben kann - das ist Standard-Modell für unbekannte Typen - und es zu json konvertieren, so in Ihrem Fall, dass etwas aussehen würde:

@FromJson 
    User fromJson(Map<String, String> map) { 
     Moshi moshi = new Moshi.Builder().build(); 
     String userJson = moshi.adapter(Map.class).toJson(map); 
     try { 
      JSONObject jsonObject = new JSONObject(userJson); 
      String accountType = jsonObject.getString("type"); 

      switch (accountType) { 
       case "Child": 
        JsonAdapter<Child> childJsonAdapter = moshi.adapter(Child.class); 
        return childJsonAdapter.fromJson(userJson); 
       case "Parent": 
        JsonAdapter<Parent> parentJsonAdapter = moshi.adapter(Parent.class); 
        return parentJsonAdapter.fromJson(userJson); 

      } 
     } catch (JSONException | IOException e) { 
      e.printStackTrace(); 
     } 

     return null; 
    } 

Natürlich können Sie nur Karte handhaben können direkt: Abrufen der Zeichenfolge "type" und dann Parsen des Rests der Karte in die ausgewählte Klasse.Dann brauchen Sie JSONObject überhaupt nicht mehr zu nutzen, um nicht von Android abhängig zu sein und das Parsen einfacher zu testen.

@FromJson 
    User fromJson(Map<String, String> map) { 
     Moshi moshi = new Moshi.Builder().build(); 
     try { 
      String userJson = moshi.adapter(Map.class).toJson(map); 
      switch (map.get("type")) { 
       case "Child": 
        JsonAdapter<Child> childJsonAdapter = moshi.adapter(Child.class); 
        return childJsonAdapter.fromJson(userJson); 
       case "Parent": 
        JsonAdapter<Parent> parentJsonAdapter = moshi.adapter(Parent.class); 
        return parentJsonAdapter.fromJson(userJson); 

      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     return null; 
    }