【问题标题】:retrofit gson converter for nested json with different objects为具有不同对象的嵌套 json 改造 gson 转换器
【发布时间】:2015-05-15 14:27:27
【问题描述】:

我的 JSON 结构如下 -

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

以上格式很常见。只有data键可以保存不同类型的信息,如userproductinvoice等。

我想在每个休息响应中保持 statusmessagedata 键相同。 data 将按照status 处理,message 将显示给用户。

所以基本上,所有 api 都需要上述格式。 只有data键里面的信息每次都会不同。

我已经设置了一个以下类并将其设置为 gson 转换器 - 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);

问题

我可以从回调内部访问statusmessage 字段。但是 data 键中的信息不会被解析,并且像 MeUserProduct 这样的模型总是返回为空。

如果我将 json 结构更改为以下代码可以完美运行 -

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

如何在 data 对象中指定单独的键并成功解析它?

【问题讨论】:

  • 想知道你在做什么
  • GSON 在解析时不考虑通用数据类型private T data;。它必须是某种特定的数据类型。
  • 使用这个数据类而不是Tclass DataClass { @SerializedName("user") UserClass user;// user object @SerializedName("product") ProductClass product;// product object @SerializedName("invoice") InvoiceClass invoice;// invoice object class UserClass { } class ProductClass { } class InvoiceClass { } }
  • 所以您总是会提前知道您是否希望 JSON 解析为 MyResponse&lt;User&gt;MyResponse&lt;Product&gt; 等?或者 Gson 是否应该自己解决这个问题 - 仅通过查看 JSON 而没有通过在 Retrofit 接口中声明具体类型来提供指导?您的 Retrofit 示例界面让我认为前者是正确的 - 在这种情况下,您可以为每个类编写(并注册!)JsonDeserializer&lt;MyResponse&lt;YourClass&gt;&gt;
  • @Gimali 如果我理解你的话。您想在数据 f.e. 中添加新字段。用户。对吗?

标签: java android json gson retrofit


【解决方案1】:

您的问题是因为data 属性被定义为T,您希望它属于MeUserProduct 等类型,但实际上是一个具有user 等内部属性的对象。为了解决这个问题,您需要引入另一个级别的类,这些类具有所需的属性userproductinvoice 等。这可以使用静态内部类轻松实现。

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

【讨论】:

  • 没有任何方法可以告诉转换器将数据中的第一个对象视为类型而不是数据本身吗?
  • 我不知道。也许@JakeWharton 可以解释一下。
【解决方案2】:

如果我可以建议更改 json 中的某些内容,您必须添加一个定义数据类型的新字段,因此 json 应该如下所示:

{
   "status": true,
   "message": "Registration Complete.",
   "dataType" : "user",
   "data": {                
            "username": "user88",
            "email": "user@domain.com",
            "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())

您定义的其他类(数据类型)如 Gson 的标准 POJO 在单独的类中。

【讨论】:

  • 是否可以将整个 json 响应作为 MyResponse.class 的对象返回 successmessage 字段?我需要它根据状态和消息字段显示消息。现在它只返回数据键。只需调整DeserializerJson 对吗??
  • 糟糕。它已经做了我需要的。伟大的。我会检查这是否也适合其他 jsons。现在看起来干净整洁。 :)
  • 什么意思“现在它只返回数据键。只需要调整 DeserializerJson 对吗?”它应该使用正确的数据创建完整的 MyResponse 对象。
  • 是的。在评论之前我没有完全阅读代码。我正在检查这个。
  • 好的。凉爽的。使用这种技术,如果代替user,我可以使用例如productList 并有一个产品列表??我肯定也会在枚举中定义这种新类型。正确的??意味着 dataType 将是 productListdata 现在可以保存 Product.class 类型的对象数组??
【解决方案3】:

可能有点跑题了,但是如果内部对象包含 Date 属性会发生什么?我的 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 属性时,它会抛出一个错误。有没有快速解决方法?

编辑:将答案标记为已接受,绝对:) 它帮助我解决了我的问题。但是现在日期反序列化器出现了这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-29
    • 2017-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-12
    • 1970-01-01
    相关资源
    最近更新 更多