使用改进使用GSON获取嵌套的JSON对象

mik*_*lar 104 java android json gson retrofit

我正在从我的Android应用程序中使用API​​,并且所有JSON响应都是这样的:

{
    'status': 'OK',
    'reason': 'Everything was fine',
    'content': {
         < some data here >
}
Run Code Online (Sandbox Code Playgroud)

问题是,我所有的POJO有status,reason字段,里面content领域是真正的POJO我想要的.

有没有办法创建一个Gson的自定义转换器来提取总是content字段,所以改造返回适当的POJO?

Bri*_*ach 160

您将编写一个返回嵌入对象的自定义反序列化器.

假设您的JSON是:

{
    "status":"OK",
    "reason":"some reason",
    "content" : 
    {
        "foo": 123,
        "bar": "some value"
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你有一个ContentPOJO:

class Content
{
    public int foo;
    public String bar;
}
Run Code Online (Sandbox Code Playgroud)

然后你写一个反序列化器:

class MyDeserializer implements JsonDeserializer<Content>
{
    @Override
    public Content deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

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

    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您构造一个Gsonwith GsonBuilder并注册反序列化器:

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer())
        .create();
Run Code Online (Sandbox Code Playgroud)

您可以直接将您的JSON反序列化为Content:

Content c = gson.fromJson(myJson, Content.class);
Run Code Online (Sandbox Code Playgroud)

编辑以添加评论:

如果您有不同类型的消息但它们都具有"内容"字段,则可以通过执行以下操作使反序列化器具有通用性:

class MyDeserializer<T> implements JsonDeserializer<T>
{
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

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

    }
}
Run Code Online (Sandbox Code Playgroud)

您只需为每种类型注册一个实例:

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer<Content>())
        .registerTypeAdapter(DiffContent.class, new MyDeserializer<DiffContent>())
        .create();
Run Code Online (Sandbox Code Playgroud)

当你调用.fromJson()类型被带入反序列化器时,它应该适用于所有类型.

最后在创建Retrofit实例时:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
Run Code Online (Sandbox Code Playgroud)

  • @feresr你可以在Retrofit的`RestAdapter.Builder`类中调用`setConverter(new GsonConverter(gson))` (7认同)
  • 哇,太棒了!谢谢!:D 有什么方法可以概括该解决方案,这样我就不必为每种类型的响应创建一个 JsonDeserializer 吗? (2认同)
  • @BrianRoach谢谢,很好的答案..我应该用分离的反序列化器注册`Person.class`和`List <Person> .class` /`Person [] .class`吗? (2认同)
  • 是否也有可能获得“状态”和“原因”?例如,如果所有请求都返回了它们,我们是否可以将它们放在超类中,并使用子类(它们是“内容”中的实际POJO)? (2认同)

小智 14

@ BrianRoach的解决方案是正确的解决方案.值得注意的是,在您需要自定义的嵌套自定义对象的特殊情况下TypeAdapter,您必须TypeAdapter使用新的GSON实例注册,否则TypeAdapter永远不会调用第二个.这是因为我们Gson在自定义反序列化器中创建了一个新实例.

例如,如果您有以下json:

{
    "status": "OK",
    "reason": "some reason",
    "content": {
        "foo": 123,
        "bar": "some value",
        "subcontent": {
            "useless": "field",
            "data": {
                "baz": "values"
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并且您希望将此JSON映射到以下对象:

class MainContent
{
    public int foo;
    public String bar;
    public SubContent subcontent;
}

class SubContent
{
    public String baz;
}
Run Code Online (Sandbox Code Playgroud)

你需要注册SubContentTypeAdapter.为了更强大,您可以执行以下操作:

public class MyDeserializer<T> implements JsonDeserializer<T> {
    private final Class mNestedClazz;
    private final Object mNestedDeserializer;

    public MyDeserializer(Class nestedClazz, Object nestedDeserializer) {
        mNestedClazz = nestedClazz;
        mNestedDeserializer = nestedDeserializer;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        GsonBuilder builder = new GsonBuilder();
        if (mNestedClazz != null && mNestedDeserializer != null) {
            builder.registerTypeAdapter(mNestedClazz, mNestedDeserializer);
        }
        return builder.create().fromJson(content, type);

    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样创建它:

MyDeserializer<Content> myDeserializer = new MyDeserializer<Content>(SubContent.class,
                    new SubContentDeserializer());
Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create();
Run Code Online (Sandbox Code Playgroud)

这可以很容易地用于嵌套的"内容"情况,只需传入一个MyDeserializer带有null值的新实例.

  • @ Mr.Tea这将是`java.lang.reflect.Type` (2认同)

Mat*_*lak 10

有点晚,但希望这会对某人有所帮助.

只需创建以下TypeAdapterFactory即可.

    public class ItemTypeAdapterFactory implements TypeAdapterFactory {

      public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {

        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {

            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            public T read(JsonReader in) throws IOException {

                JsonElement jsonElement = elementAdapter.read(in);
                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    if (jsonObject.has("content")) {
                        jsonElement = jsonObject.get("content");
                    }
                }

                return delegate.fromJsonTree(jsonElement);
            }
        }.nullSafe();
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其添加到您的GSON构建器中:

.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
Run Code Online (Sandbox Code Playgroud)

要么

 yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
Run Code Online (Sandbox Code Playgroud)


小智 6

继续Brian的想法,因为我们几乎总是拥有许多REST资源,每个资源都有自己的根,所以推广反序列化可能很有用:

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> targetClass, String key) {
        mClass = targetClass;
        mKey = key;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonElement content = je.getAsJsonObject().get(mKey);
        return new Gson().fromJson(content, mClass);

    }
}
Run Code Online (Sandbox Code Playgroud)

然后从上面解析样本有效负载,我们可以注册GSON反序列化器:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
    .build();
Run Code Online (Sandbox Code Playgroud)


raf*_*kob 6

几天前有同样的问题.我用响应包装器类和RxJava转换器来解决这个问题,我认为这是一个非常灵活的解决方案:

包装:

public class ApiResponse<T> {
    public String status;
    public String reason;
    public T content;
}
Run Code Online (Sandbox Code Playgroud)

当状态不正常时,抛出自定义异常:

public class ApiException extends RuntimeException {
    private final String reason;

    public ApiException(String reason) {
        this.reason = reason;
    }

    public String getReason() {
        return apiError;
    }
}
Run Code Online (Sandbox Code Playgroud)

Rx变压器:

protected <T> Observable.Transformer<ApiResponse<T>, T> applySchedulersAndExtractData() {
    return observable -> observable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .map(tApiResponse -> {
                if (!tApiResponse.status.equals("OK"))
                    throw new ApiException(tApiResponse.reason);
                else
                    return tApiResponse.content;
            });
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

// Call definition:
@GET("/api/getMyPojo")
Observable<ApiResponse<MyPojo>> getConfig();

// Call invoke:
webservice.getMyPojo()
        .compose(applySchedulersAndExtractData())
        .subscribe(this::handleSuccess, this::handleError);


private void handleSuccess(MyPojo mypojo) {
    // handle success
}

private void handleError(Throwable t) {
    getView().showSnackbar( ((ApiException) throwable).getReason() );
}
Run Code Online (Sandbox Code Playgroud)

我的主题: Retrofit 2 RxJava - Gson - "全局"反序列化,更改响应类型