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.