Подтвердить что ты не робот

Модифицировать gson-конвертер для вложенных json с разными объектами

У меня есть структура JSON, как показано ниже -

{
    "status": true,
    "message": "Registration Complete.",
    "data": {
        "user": {
            "username": "user88",
            "email": "[email protected]",
            "created_on": "1426171225",
            "last_login": null,
            "active": "1",
            "first_name": "User",
            "last_name": "",
            "company": null,
            "phone": null,
            "sign_up_mode": "GOOGLE_PLUS"
        }
    }
}

Формат выше обычного. Только клавиша data может содержать различные типы информации, такие как user, product, invoice и т.д.

Я хочу держать ключи status, message и data одинаковыми в каждом ответе на отдых. data будет обрабатываться в соответствии с status, а message будет отображаться пользователю.

Так что в принципе, выше формат желательно во всех apis. Только информация внутри клавиши data будет отличаться каждый раз.

И я установил следующий класс и настроил его как gson converter - MyResponse.java

public class MyResponse<T> implements Serializable{
    private boolean status ;
    private String message ;
    private T data;

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

Deserializer.java

class Deserializer<T> implements JsonDeserializer<T>{
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException{
        JsonElement content = je.getAsJsonObject();

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion to this deserializer
        return new Gson().fromJson(content, type);

    }
}

И использовал его следующим образом:

GsonBuilder  gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); 
gsonBuilder.registerTypeAdapter(MyResponse.class, new Deserializer<MyResponse>());
...... ..... ....

restBuilder.setConverter(new GsonConverter(gsonBuilder.create()));

Сервисный интерфейс выглядит следующим образом:

@POST("/register")
public void test1(@Body MeUser meUser, Callback<MyResponse<MeUser>> apiResponseCallback);


@POST("/other")
public void test2(Callback<MyResponse<Product>> apiResponseCallback);

Проблема

Я могу получить доступ к полям status и message из внутреннего обратного вызова. Но информация внутри ключа data не анализируется, а модель типа MeUser и product всегда возвращается как пустая.

Если я изменю структуру json на следующий выше код работает отлично -

{
        "status": true,
        "message": "Registration Complete.",
        "data": {                
                "username": "user88",
                "email": "[email protected]",
                "created_on": "1426171225",
                "last_login": null,
                "active": "1",
                "first_name": "User",
                "last_name": "",
                "company": null,
                "phone": null,
                "sign_up_mode": "GOOGLE_PLUS"
        }
    }

Как я могу заставить это работать с указанием отдельного ключа внутри объекта data и его синтаксического анализа?

4b9b3361

Ответ 1

Если я могу предложить что-то изменить в json, вам нужно добавить в одно новое поле, определяющее тип данных, поэтому json должен выглядеть следующим образом:

{
   "status": true,
   "message": "Registration Complete.",
   "dataType" : "user",
   "data": {                
            "username": "user88",
            "email": "[email protected]",
            "created_on": "1426171225",
            "last_login": null,
            "active": "1",
            "first_name": "User",
            "last_name": "",
            "company": null,
            "phone": null,
            "sign_up_mode": "GOOGLE_PLUS"
    }
}

Класс MyResponse должен иметь новый файл DataType, поэтому он должен выглядеть следующим образом:

public class MyResponse<T> implements Serializable{
    private boolean status ;
    private String message ;
    private DataType dataType ;
    private T data;


    public DataType getDataType() {
        return dataType;
    }

    //... other getters and setters
}

DataType - это перечисление, которое определяет тип данных. Вы должны передать Data.class как параметр в конструкторе. Для всех типов данных вам необходимо создать новые классы. DataType перечисление должно выглядеть следующим образом:

public enum DataType {

    @SerializedName("user")
    USER(MeUser.class),
    @SerializedName("product")
    Product(Product.class),
    //other types in the same way, the important think is that 
    //the SerializedName value should be the same as dataType value from json 
    ;


    Type type;

    DataType(Type type) {
        this.type = type;
    }

    public Type getType(){
        return type;
    }
}

Дезарилизатор для Json должен выглядеть следующим образом:

public class DeserializerJson implements JsonDeserializer<MyResponse> {

    @Override
    public MyResponse deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonObject content = je.getAsJsonObject();
        MyResponse message = new Gson().fromJson(je, type);
        JsonElement data = content.get("data");
        message.setData(new Gson().fromJson(data, message.getDataType().getType()));
        return message;

    }
}

И когда вы создаете RestAdapter, в строке, где вы регистрируете Deserializator, вы должны использовать это:

 .registerTypeAdapter(MyResponse.class, new DeserializerJson())

Другие классы (типы данных), которые вы определяете как стандартные POJO для Gson в разделенных классах.

Ответ 2

Ваша проблема в том, что атрибут data определяется как T, который вы ожидаете от типов MeUser, Product и т.д., но на самом деле является объектом, который имеет внутренний атрибут, например user. Чтобы решить эту проблему, вам необходимо ввести еще один уровень классов, который имеет необходимые атрибуты user, Product, invoice и т.д. Этого можно легко достичь с помощью статических внутренних классов.

public class MeUser{
  private User user;
  public static class User{
    private String username;
    //add other attributes of the User class
  }
}

Ответ 3

Может быть немного не по теме, но что произойдет, если внутренний объект содержит свойство Date? My TypeAdapter выглядит так:

 .registerTypeAdapter(Date.class, new DateDeserializer())
                .registerTypeAdapter(GenericNotificationResponse.class, new NotificationDeserializer())
                .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
                .create();

Но из-за разбора, который делается следующим образом: message.setData(new Gson().fromJson(data, message.getDataType().getType()));

Он будет вызывать ошибку всякий раз, когда будет пытаться десериализовать свойство Date. Есть ли быстрое решение для этого?

EDIT: Отмечено, что ответ принят, определенно:) это помогло мне исправить мою проблему. Но теперь эта проблема с десериализатором даты.