2015-10-26 2 views
10

У меня есть класс пользователя. И два подкласса. Родитель и ребенок. Я получаю json с моего сервера с {"user": "..."} и должен преобразовать его в родительский или дочерний в зависимости от user.typeПользовательский конвертер для подкласса с Moshi

Как я понимаю, мне нужно добавить пользовательский конвертер таким образом:

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

Вот моя реализация UserAdapter. Я знаю, что это манекен, но это не работает даже так:

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

Прежде всего я получаю следующее исключение с этим кодом.

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

И, во-вторых, я считаю, что есть лучший способ сделать это. Пожалуйста посоветуй.

Обновление. вот StackTrace за ошибки:

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) 

ответ

6

Это, мне кажется, как, например, вы хотите следовать за пользовательские де/сериализации ваших данных JSON: https://github.com/square/moshi#another-example

Он использует промежуточный класс, соответствующий JSON и Моши автоматически раздует вас. Затем вы можете использовать завышенные данные для создания специализированных пользовательских классов. Например:

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

Теперь адаптер:

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

Я думаю, что это гораздо чище, и позволяет Moshi делать свою вещь, которая создает объекты из JSON и создания JSON из объектов. Нет, обманывая старомодным JSONObject!

Для теста:

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

Эй, спасибо за answear, я постараюсь, чтобы проверить код на этой неделе и пометить его как Corect, если он работает – Defuera

+0

@Defuera Ну как? При удаче? – savanto

+0

@savanto Требуется ли создание адаптера? –

3

Вы, вероятно, пытались осуществить вы разбор в соответствии с: https://github.com/square/moshi#custom-type-adapters

Там Строка используется в качестве аргумента метода @FromJson, поэтому он может быть магически разобран в какое-то отображение вспомогательный класс или String, и мы должны разобрать его вручную, не так ли? На самом деле нет, вы можете либо использовать вспомогательный класс сопоставления , либо.

Таким образом, ваше исключение Expected a string but was BEGIN_OBJECT at path $.user было вызвано тем, что Моши пытался получить этого пользователя в виде строки (потому что это то, что вы подразумевали в своем адаптере), тогда как это просто другой объект.

Мне не нравится разбор всех возможных полей на какой-либо вспомогательный класс, так как в случае полиморфизма этот класс может стать очень большим, и вам нужно полагаться или запоминать/комментировать код.

Вы можете обрабатывать его в качестве карты - то есть по умолчанию модель для неизвестных типов - и преобразовать его в формат JSON, так что в вашем случае будет выглядеть примерно так:

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

Конечно, вы можете просто обрабатывать карту непосредственно: извлекать строку типа и затем анализировать остальную часть карты в выбранном классе.Тогда нет необходимости использовать JSONObject вообще с хорошим преимуществом, не зависящим от Android и более легким тестированием синтаксического анализа.

@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; 
    } 
Смежные вопросы