8

I'm working with a REST API that returns a JSON document that starts as follows and includes a "collection" of items with string IDs like "ABC". Note the "routes" field, which contains a series of fields called "ABC", "ABD", "ABE" etc, however routes is not represented as an array in the json, so all these

{
"status":true,
"page":1,
"per_page":500,
"total_count":1234,
"total_pages":8,
"total_on_page":500,
"routes":{
    "ABC":[
    {
        "value":22313,
        <......>

I'm using Retrofit and the problem is the routes field is not an array (despite the fact conceptually it certainly is) and Retrofit/Gson require me to create a model object for routes with field vars abc, abd, etc - this is not practical as the data changes. For various reasons changing the server API is hard, so I'm looking to work around this on the Android client.

I figure these are options:

  • Intercept the JSON document before it reaches Gson and tweak the document, possibly with a customised Gson parser, or by intercepting the HTTP response.

  • Bypass the JSON parsing, and acquire the JSON document from Retrofit (I've yet to figure out how to do this, or if it's possible)

  • Use some feature of Retrofit I'm unaware of to map field names to a collection.

I'd appreciate help, especially if there's a quick and easy way to resolve this.

2 Answers 2

14

It turns out that Retrofit's use of Gson by default makes it fairly easy to add a custom deserialiser to handle the portion of the JSON document that was the problem.

RestAdapter restAdapter = new RestAdapter.Builder()
        .setEndpoint(ApiDefinition.BASE_URL)
        .setConverter(getGsonConverter())
        .build();

public Converter getGsonConverter() {
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(RouteList.class, new RouteTypeAdapter())
            .create();
    return  new GsonConverter(gson);
}


public class RouteTypeAdapter implements JsonDeserializer<RouteList> {
    @Override
    public RouteList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        Gson gson = new Gson();
        RouteList routeList = new RouteList();
        JsonObject jsonObject = json.getAsJsonObject();
        for (Map.Entry<String,JsonElement> elementJson : jsonObject.entrySet()){
            RouteList wardsRoutes = gson.fromJson(elementJson.getValue().getAsJsonArray(), RouteList.class);
            routeList.addAll(wardsRoutes);
        }
        return routeList;
    }

}
Sign up to request clarification or add additional context in comments.

2 Comments

I've been looking for this for so long... That's the clean way to do it, thanks!
what is ApiDefinition.BASE_URL?
2

After calling RestService, don't use Model Name as argument, you have to use Default Response class from retrofit library.

RestService Method

@FormUrlEncoded
    @POST(GlobalVariables.LOGIN_URL)
    void Login(@Field("email") String key, @Field("password") String value, Callback<Response> callback);

Calling method in Activity

getService().Login(email, password, new MyCallback<Response>(context, true, null)
{
    @Override
    public void failure(RetrofitError arg0)
     {
        // TODO Auto-generated method stub
        UtilitySingleton.dismissDialog((BaseActivity<?>) context);
        System.out.println(arg0.getResponse());
      }

    @Override
    public void success(Response arg0, Response arg1)
    {
         String result = null;
         StringBuilder sb = null;
         InputStream is = null;
         try
         {
                is = arg1.getBody().in();
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null)
                {
                    sb.append(line + "\n");
                    result = sb.toString();
                    System.out.println("Result :: " + result);
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    });

1 Comment

Writing deserializer is much cleaner. Also the parsing is also done on the background thread, unlike your approach. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.