Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
271 views
in Technique[技术] by (71.8m points)

android - Using Gson and Retrofit 2 to deserialize complex API responses

I'm using Retrofit 2 and Gson and I'm having trouble deserializing responses from my API. Here's my scenario:

I have a model object named Employee that has three fields: id, name, age.

I have an API that returns a singular Employee object like this:

{
    "status": "success",
    "code": 200,
    "data": {
        "id": "123",
        "id_to_name": {
            "123" : "John Doe"
        },
        "id_to_age": {
            "123" : 30
        }
    }
}

And a list of Employee objects like this:

{
    "status": "success",
    "code": 200,
    "data": [
        {
            "id": "123",
            "id_to_name": {
                "123" : "John Doe"
            },
            "id_to_age": {
                "123" : 30
            }
        },
        {
            "id": "456",
            "id_to_name": {
                "456" : "Jane Smith"
            },
            "id_to_age": {
                "456" : 35
            }
        },
    ]
}

There are three main things to consider here:

  1. API responses return in a generic wrapper, with the important part inside of the data field.
  2. The API returns objects in a format that doesn't directly correspond to the fields on the model (for example, the value taken from id_to_age needs be mapped to the age field on the model)
  3. The data field in the API response can be a singular object, or a list of objects.

How do I implement deserialization with Gson such that it handles these three cases elegantly?

Ideally, I'd prefer to do this entirely with TypeAdapter or TypeAdapterFactory instead of paying the performance penalty of JsonDeserializer. Ultimately, I want to end up with an instance of Employee or List<Employee> such that it satisfies this interface:

public interface EmployeeService {

    @GET("/v1/employees/{employee_id}")
    Observable<Employee> getEmployee(@Path("employee_id") String employeeId);

    @GET("/v1/employees")
    Observable<List<Employee>> getEmployees();

}

This earlier question I posted discusses my first attempt at this, but it fails to consider a few of the gotchas mentioned above: Using Retrofit and RxJava, how do I deserialize JSON when it doesn't map directly to a model object?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I would suggest using a JsonDeserializer because there is not so many levels of nesting in the response, so it won't be a big performance hit.

Classes would look something like this:

Service interface needs to be adjusted for the generic response:

interface EmployeeService {

    @GET("/v1/employees/{employee_id}")
    Observable<DataResponse<Employee>> getEmployee(@Path("employee_id") String employeeId);

    @GET("/v1/employees")
    Observable<DataResponse<List<Employee>>> getEmployees();

}

This is a generic data response:

class DataResponse<T> {

    @SerializedName("data") private T data;

    public T getData() {
        return data;
    }
}

Employee model:

class Employee {

    final String id;
    final String name;
    final int age;

    Employee(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

}

Employee deserializer:

class EmployeeDeserializer implements JsonDeserializer<Employee> {

    @Override
    public Employee deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {

        JsonObject employeeObject = json.getAsJsonObject();
        String id = employeeObject.get("id").getAsString();
        String name = employeeObject.getAsJsonObject("id_to_name").entrySet().iterator().next().getValue().getAsString();
        int age = employeeObject.getAsJsonObject("id_to_age").entrySet().iterator().next().getValue().getAsInt();

        return new Employee(id, name, age);
    }
}

The problem with the response is that name and age are contained inside of an JSON object whitch translates to a Map in Java so it requires a bit more work to parse it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

56.9k users

...