2015-10-26 13 views
10

Ho una classe utente. E due sottoclassi. Genitore e figlio. Ottengo json dal mio server con {"utente": "..."} e ho bisogno di convertirlo in genitore o figlio in base a user.typeConvertitore personalizzato per sottoclasse con Moshi

Come ho capito ho bisogno di aggiungere un convertitore personalizzato in questo modo:

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

Ecco la mia implementazione di UserAdapter. Lo so che è fittizio, ma non sta funzionando neppure in questo modo:

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

Prima di tutto quello che ottiene seguente eccezione con questo codice.

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

E secondo, credo che ci sia un modo migliore per farlo. Per favore consiglio

Aggiornamento. ecco StackTrace per l'errore:

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) 

risposta

6

Questo mi sembra come l'esempio si vuole seguire per la vostra abitudine de/serializzazione dei dati JSON: https://github.com/square/moshi#another-example

Esso utilizza una classe intermedia che corrisponde al JSON struttura, e Moshi la gonfierà automaticamente per te. Quindi, puoi utilizzare i dati gonfiati per creare le tue classi utente specializzate. Per esempio:

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

Ora, l'adattatore:

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

Penso che questo è molto più pulito, e permette di Moshi per fare il suo dovere, che sta creando oggetti da JSON e la creazione di JSON da oggetti. Niente mucking con il vecchio stile JSONObject!

A prova:

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

Ehi, grazie per l'answear, cercherò di controllare il codice di questa settimana e segnare come corect se funziona – Defuera

+0

@Defuera Ну как? Qualche fortuna? – savanto

+0

@savanto È necessario creare un adattatore? –

3

Probabilmente cercato di implementare voi l'analisi in base a: https://github.com/square/moshi#custom-type-adapters

Ci Stringa viene utilizzato come argomento del metodo di @FromJson, in modo che possa essere magicamente analizzato a qualche mappatura helper class o String e dobbiamo analizzarlo manualmente, giusto? In realtà no, è possibile utilizzare la classe helper di mappatura o Mappa.

Quindi la tua eccezione Expected a string but was BEGIN_OBJECT at path $.user è stata causata da Moshi che tenta di ottenere quell'utente come una stringa (poiché questo è ciò che hai implicito nell'adattatore), mentre è solo un altro oggetto.

Non mi piace analizzare TUTTI i campi possibili a qualche classe di supporto, in quanto nel caso del polimorfismo la classe potrebbe diventare molto grande ed è necessario fare affidamento o ricordare/commentare il codice.

È possibile gestire come una mappa - che è il modello predefinito per i tipi sconosciuti - e convertirlo in JSON, così nel tuo caso, che sarebbe simile:

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

Naturalmente si può solo gestire mappa direttamente: recupera la stringa "tipo" e quindi analizza il resto della mappa nella classe scelta.Quindi non è necessario utilizzare JSONObject con il vantaggio di non dipendere da Android e di test più semplici di analisi.

@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; 
    } 
Problemi correlati