简体   繁体   中英

How to parse a JSON reply when using retrofit when the reply is sometimes an object and sometimes an array?

I am using Retrofit to get a JSON reply.

Here are parts of my implementation -

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

and the class Bills is -

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

The BillItem class is as follows -

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

The problem is sometimes the REST API returns an Array of BillItem objects, but sometimes it is just a key-value pair. How does one handle such a situation?

When this response is received, everything works fine because the JSONArray gets mapped to the ArrayList<BillItem> -

{
   "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"
      }
   ]
}

But, sometimes the response is this, and it gives a 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"
      }
   }
}

How, does one deal with such a reply?

When you use a Gson deserializer you can check the type in the 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;
     }
}

See example: Gson deserialization - Trying to parse a JSON to an Object

As mentioned in the answer by @Gavriel a custom Deserializer needs to be written.

Here is my implementation (it might contain superfluous code) -

The Actual API definition -

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

}

This is where we actually create the BillItem class when the response is received.

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

This is the Subscription object where we receive the data.

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

        });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM