【问题标题】:How to parse a JSON reply when using retrofit when the reply is sometimes an object and sometimes an array?当回复有时是对象,有时是数组时,如何在使用改造时解析 JSON 回复?
【发布时间】:2016-05-10 20:30:21
【问题描述】:

我正在使用 Retrofit 来获取 JSON 回复。

这是我的部分实现 -

@GET("/api/report/list")
Observable<Bills> listBill(@Query("employee_id") String employeeID);

Bills 类是 -

public static class Bills {
    @SerializedName("report")
    public ArrayList<BillItem> billItems;
}

BillItem类如下——

public static class BillItem {
    @SerializedName("id")
    Integer entryID;
    @SerializedName("employee_id")
    Integer employeeDBID;
    @SerializedName("type")
    String type;
    @SerializedName("subtype")
    String subtype;
    @SerializedName("date")
    String date;
    @SerializedName("to")
    String to;
    @SerializedName("from")
    String from;
    @SerializedName("amount")
    Double amount;
    @SerializedName("location")
    String location;
    @SerializedName("remark")
    String remark;
    @SerializedName("ispaid")
    String isPaid;
    @SerializedName("created_at")
    String createdAt;
    @SerializedName("updated_at")
    String updateAt;
}

问题是有时 REST API 返回一个 BillItem 对象数组,但有时它只是一个 key-value 对。如何处理这种情况?

收到此响应后,一切正常,因为 JSONArray 被映射到 ArrayList&lt;BillItem&gt; -

{
   "emp":{
      "id":41,
      "name":"",
      "email":"",
      "created_at":"2016-02-01 10:36:38",
      "updated_at":"2016-02-01 10:36:38"
   },
   "report":[
      {
     "id":175,
     "employee_id":41,
     "type":"Travel",
     "subtype":"Car",
     "date":"2016-02-02 00:00:00",
     "to":"gaha",
     "from":"hshsj",
     "amount":"64",
     "location":"",
     "remark":"shhs",
     "ispaid":false,
     "created_at":"2016-02-01 13:52:52",
     "updated_at":"2016-02-01 13:52:52"
      },
      {
     "id":179,
     "employee_id":41,
     "type":"Travel",
     "subtype":"Car",
     "date":"2016-02-01 00:00:00",
     "to":"Gsh",
     "from":"Dgdh",
     "amount":"7646",
     "location":"",
     "remark":"Shsh",
     "ispaid":false,
     "created_at":"2016-02-01 14:39:48",
     "updated_at":"2016-02-01 14:39:48"
      }
   ]
}

但是,有时响应是这样的,它给出了一个JsonSyntaxException -

{
   "emp":{
      "id":41,
      "name":"",
      "email":"",
      "created_at":"2016-02-01 10:36:38",
      "updated_at":"2016-02-01 10:36:38"
   },
   "report":{
      "1":{
     "id":175,
     "employee_id":41,
     "type":"Travel",
     "subtype":"Car",
     "date":"2016-02-02 00:00:00",
     "to":"gaha",
     "from":"hshsj",
     "amount":"64",
     "location":"",
     "remark":"shhs",
     "ispaid":false,
     "created_at":"2016-02-01 13:52:52",
     "updated_at":"2016-02-01 13:52:52"
      },
      "2":{
     "id":179,
     "employee_id":41,
     "type":"Travel",
     "subtype":"Car",":"2016-02-01 00:00:00",
     "to":"Gsh",
     "from":"Dgdh",
     "amount":"7646",
     "location":"",
     "remark":"Shsh",
     "ispaid":false,
     "created_at":"2016-02-01 14:39:48",
     "updated_at":"2016-02-01 14:39:48"
      },
      "0":{
     "id":181,
     "employee_id":41,
     "type":"Travel",
     "subtype":"Car",
     "date":"2016-02-01 00:00:00",
     "to":"ggg",
     "from":"vg",
     "amount":"0",
     "location":"",
     "remark":"cvv",
     "ispaid":false,
     "created_at":"2016-02-01 17:43:43",
     "updated_at":"2016-02-01 17:43:43"
      },
      "3":{
     "id":182,
     "employee_id":41,
     "type":"Travel",
     "subtype":"Car",
     "date":"2016-02-01 00:00:00",
     "to":"Haha",
     "from":"Ahah",
     "amount":"0",
     "location":"",
     "remark":"Ahah",
     "ispaid":false,
     "created_at":"2016-02-01 17:53:58",
     "updated_at":"2016-02-01 17:53:58"
      }
   }
}

如何处理这样的回复?

【问题讨论】:

  • 在这个答案中查看 GSON 类型适配器 -- stackoverflow.com/a/33006947/1904517
  • 你不能修复 api 以始终返回一个数组,即使只有 1 个项目,或者即使有 0 个项目?
  • @Gavriel 不幸的是,我对 API 没有任何控制权。
  • @iagreen 你能解释一下你的答案中的实现吗?
  • 它使用TypeAdapter 总是返回一个列表,它偷看值。如果它是一个数组,它照常处理,如果不是,它读取值但在单个元素列表中返回它。

标签: java json retrofit rx-java


【解决方案1】:

当您使用 Gson 反序列化器时,您可以检查 JsonElement 中的类型:

Gson gson = new GsonBuilder()
            .registerTypeAdapter(BillItem.class, new BillItemDeserializer())
            .registerTypeAdapter(Bills.class, new BillsDeserializer())
            .create();

RestAdapter.Builder builder = new RestAdapter.Builder()
            //...
            .setConverter(new GsonConverter(gson));

public class BillsDeserializer implements JsonDeserializer<StringList> {

    public Bills deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException {
         BillItemList value = new BillItemList();
         if (json.isJsonArray()) {
             for (JsonElement element : json.getAsJsonArray()) {
                 value.add(gson.fromJson(element, BillItem.class));
             }
         } else {
             value.add(gson.fromJson(element, BillItem.class));
         }
         return value;
     }
}

查看示例:Gson deserialization - Trying to parse a JSON to an Object

【讨论】:

  • Deserialization 会做什么?你能告诉我APIclass definitions应该是什么吗?
  • 按照this answer 中的建议,我定义了一个新类-public class BillItemList { @SerializedName("report") public ArrayList&lt;BillItem&gt; billItemsList; },GSon 定义为-` Gson gson = new GsonBuilder() .registerTypeAdapter(BillItemList.class, new StringListDeserializer()) .create();`
  • 反序列化器为 - public class StringListDeserializer implements JsonDeserializer&lt;BillItemList&gt; {@Override public BillItemList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { BillItemList value = new BillItemList(); if (json.isJsonArray()) { for (JsonElement element : json.getAsJsonArray()) { value.add(element.getAsString()); } } else { value.add(json.getAsString()); } return value; } }
  • @yadav_vi,我想过类似的事情,但我认为您也可以使用 Gson 来反序列化 BillItem 对象而不是 getAsString,这似乎是我的更新
【解决方案2】:

正如@Gavrielanswer 中提到的,需要编写自定义Deserializer

这是我的实现(它可能包含多余的代码) -

API 的实际定义 -

public interface ServerAPI {
    public static final String ENDPOINT = "http://xyz";

    @GET("/api/report/list")
    Observable<Bills> listBill(@Query("employee_id") String employeeID);

    public static class BillItem {
        @SerializedName("id")
        public Integer entryID;
        @SerializedName("employee_id")
        public Integer employeeDBID;
        @SerializedName("type")
        public String type;
        @SerializedName("subtype")
        public String subtype;
        @SerializedName("date")
        public String date;
        @SerializedName("to")
        public String to;
        @SerializedName("from")
        public String from;
        @SerializedName("amount")
        public Double amount;
        @SerializedName("location")
        public String location;
        @SerializedName("remark")
        public String remark;
        @SerializedName("ispaid")
        public String isPaid;
        @SerializedName("created_at")
        public String createdAt;
        @SerializedName("updated_at")
        public String updateAt;
    }

    public static class Bills {
        @SerializedName("report") // This seems to serve no purpose.
        public ArrayList<BillItem> billItems = new ArrayList<>(); // It was needed to initialize the ArrayList.

        public void add(BillItem billItem) {
            billItems.add(billItem);
        }
    }

}

这是我们在收到响应时实际创建BillItem 类的地方。

public class App extends Application {
    private static App instance;
    private static ServerAPI serverAPI;

    public static ServerAPI getServerAPI() {
        return serverAPI;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(BillItem.class, new BillItemDeserializer())
                .registerTypeAdapter(Bills.class, new BillsDeserializer())
                .create();

        instance = this;
        serverAPI = new RestAdapter.Builder()
                .setEndpoint(ServerAPI.ENDPOINT)
                .setConverter(new GsonConverter(gson))
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .setLog(new RestAdapter.Log() {
                    @Override
                    public void log(String message) {
                        Log.v("Retrofit", message);
                    }
                })
                .build().create(ServerAPI.class);
    }

    // This has nothing inside of it, it still works.
    public class BillItemDeserializer implements JsonDeserializer<BillItem> {

        @Override
        public BillItem deserialize(JsonElement json, Type typeOfT,
                                    JsonDeserializationContext context) throws
                JsonParseException {
            return null;
        }
    }

    private class BillsDeserializer implements JsonDeserializer<Bills> {
        @Override
        public Bills deserialize(JsonElement json, Type typeOfT,
                                 JsonDeserializationContext context) throws
                JsonParseException {
            Bills value = new Bills();
            Gson gson = new Gson();

            json = json.getAsJsonObject().get("report");
            if (json.isJsonArray()) {
                for (JsonElement element : json.getAsJsonArray()) {
                    value.add(gson.fromJson(element, BillItem.class));
                }
            } else {
                JsonElement element = json.getAsJsonObject();
                Set<Map.Entry<String, JsonElement>> entries = element.getAsJsonObject().entrySet();
                for (Map.Entry<String, JsonElement> entry : entries) {
                    value.add(gson.fromJson(entry.getValue(), BillItem.class));
                }
            }
            return value;
        }
    }
}

这是我们接收数据的Subscription 对象。

Subscription subscription = App.getServerAPI()
        .listBill(String.valueOf(employeeID))
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<ServerAPI.Bills>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {
                Log.i(TAG, "ERROR: Value returned: " + e.getMessage());

                e.printStackTrace();
            }

            @Override
            public void onNext(ServerAPI.Bills bills) {
                for (ServerAPI.BillItem item : bills.billItems) {
                    Log.i(TAG, "onNextBillItem: " + item);
                }
            }

        });

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-29
    • 2015-07-12
    • 1970-01-01
    • 1970-01-01
    • 2019-01-31
    • 1970-01-01
    • 2011-07-10
    相关资源
    最近更新 更多