【问题标题】:Custom converter to subclass with Moshi使用 Moshi 自定义转换器到子类
【发布时间】:2016-01-26 01:47:53
【问题描述】:

我有一个用户类。和两个子类。父母和孩子。 我使用 {"user":"..."} 从我的服务器获取 json 并且需要根据 user.type 将其转换为父级或子级

据我了解,我需要以这种方式添加自定义转换器:

        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

其次,我相信有更好的方法来做到这一点。请指教。

更新。这是错误的堆栈跟踪:

 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)

【问题讨论】:

    标签: android square moshi


    【解决方案1】:

    在我看来,这就像您想要对 JSON 数据进行自定义反序列化/序列化的示例:https://github.com/square/moshi#another-example

    它使用了一个对应于 JSON 结构的中间类,Moshi 会自动为你充气。然后,您可以使用膨胀的数据来构建您的专业用户类。例如:

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

    【讨论】:

    • 嘿,谢谢你的回答,这周我会试着检查你的代码,如果它有效,就把它标记为正确
    • @Defuera Ну как?运气好吗?
    • @savanto 是否需要创建适配器?
    • 这没有意义。你在哪里定义你的User 类??
    • 我认为这绝不是“更清洁”。对我来说,犯错的地方太多了,这个要反序列化的通用类可能会随着更多 User 子类的出现而变得非常大。
    【解决方案2】:

    您可能尝试根据以下方式实现解析:https://github.com/square/moshi#custom-type-adapters

    String 被用作@FromJson 方法的参数,所以它可以被神奇地解析为一些映射帮助类或字符串,我们必须手动解析它,对吧?实际上没有,您可以使用映射辅助类 Map。

    因此,您的异常 Expected a string but was BEGIN_OBJECT at path $.user 是由于 Moshi 试图将该用户作为字符串获取(因为这是您在适配器中暗示的内容),而它只是另一个对象。

    我不喜欢将所有可能的字段解析为某个辅助类,因为在多态的情况下,该类可能会变得非常大,您需要依赖或记住/注释代码。

    您可以将其作为地图处理 - 这是未知类型的默认模型 - 并将其转换为 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;
        }
    

    当然,您可以直接处理 map:检索“type”字符串,然后将 map 的其余部分解析为选择的类。那么就完全不需要使用 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;
        }
    

    【讨论】:

      【解决方案3】:

      现在有一个更好的方法来做到这一点,使用PolymorphicJsonAdapterFactory。见https://proandroiddev.com/moshi-polymorphic-adapter-is-d25deebbd7c5

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-08-19
        • 2018-06-17
        • 2014-11-19
        • 1970-01-01
        • 1970-01-01
        • 2020-10-31
        • 2017-02-08
        • 2018-11-30
        相关资源
        最近更新 更多