Get nested JSON object with GSON using retrofit

I'm consuming an API from my android app, and all the JSON responses are like this:

{
'status': 'OK',
'reason': 'Everything was fine',
'content': {
< some data here >
}

The problem is that all my POJOs have a status, reason fields, and inside the content field is the real POJO I want.

Is there any way to create a custom converter of Gson to extract always the content field, so retrofit returns the appropiate POJO?

105134 次浏览

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

假设您的 JSON 是:

{
"status":"OK",
"reason":"some reason",
"content" :
{
"foo": 123,
"bar": "some value"
}
}

然后你会得到一个 Content POJO:

class Content
{
public int foo;
public String bar;
}

然后写一个反序列化器:

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);


}
}

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

Gson gson =
new GsonBuilder()
.registerTypeAdapter(Content.class, new MyDeserializer())
.create();

You can deserialize your JSON straight to your Content:

Content c = gson.fromJson(myJson, Content.class);

编辑添加注释:

如果您有不同类型的消息,但它们都有“ content”字段,那么您可以通过以下方法使得反序列化器具有通用性:

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);


}
}

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

Gson gson =
new GsonBuilder()
.registerTypeAdapter(Content.class, new MyDeserializer<Content>())
.registerTypeAdapter(DiffContent.class, new MyDeserializer<DiffContent>())
.create();

当您调用 .fromJson()时,该类型会进入反序列化器,因此它应该能够适用于所有类型。

最后,当创建一个 Revifit 实例时:

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();

继续 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);


}
}

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

Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
.build();

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

例如,如果您有以下 json:

{
"status": "OK",
"reason": "some reason",
"content": {
"foo": 123,
"bar": "some value",
"subcontent": {
"useless": "field",
"data": {
"baz": "values"
}
}
}
}

您希望将这个 JSON 映射到以下对象:

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


class SubContent
{
public String baz;
}

您需要注册 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);


}
}

然后像这样创造它:

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

This could easily be used for the nested "content" case as well by simply passing in a new instance of MyDeserializer with null values.

这是与@AYarulin 相同的解决方案,但是假设类名是 JSON 键名。这样,您只需要传递 Class 名称。

 class RestDeserializer<T> implements JsonDeserializer<T> {


private Class<T> mClass;
private String mKey;


public RestDeserializer(Class<T> targetClass) {
mClass = targetClass;
mKey = mClass.getSimpleName();
}


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


}
}

然后从上面解析样例有效负载,我们可以注册 GSON 反序列化器。这是有问题的,因为 Key 是区分大小写的,所以类名的大小写必须与 JSON 键的大小写相匹配。

Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class))
.build();

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

包装纸:

public class ApiResponse<T> {
public String status;
public String reason;
public T content;
}

当状态不正常时,要引发的自定义异常:

public class ApiException extends RuntimeException {
private final String reason;


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


public String getReason() {
return apiError;
}
}

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;
});
}

示例用法:

// 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() );
}

我的主题是: 改进2 RxJava-Gson-“ Global”反序列化,更改响应类型

在我的例子中,每个响应的“ content”键都会改变。例如:

// Root is hotel
{
status : "ok",
statusCode : 200,
hotels : [{
name : "Taj Palace",
location : {
lat : 12
lng : 77
}


}, {
name : "Plaza",
location : {
lat : 12
lng : 77
}
}]
}


//Root is city


{
status : "ok",
statusCode : 200,
city : {
name : "Vegas",
location : {
lat : 12
lng : 77
}
}

在这种情况下,我使用了上面列出的类似解决方案,但必须对其进行调整。您可以看到要点 here。放在 SOF 上有点太大了。

The annotation @InnerKey("content") is used and the rest of the code is to facilitate it's usage with Gson.

不要忘记 @SerializedName@Expose注释的所有类成员和内部类成员,最反序列化从 JSON 的 GSON。

看看 https://stackoverflow.com/a/40239512/1676736

一个更好的解决方案可能是这样. 。

public class ApiResponse<T> {
public T data;
public String status;
public String reason;
}

然后,像这样定义你的服务. 。

Observable<ApiResponse<YourClass>> updateDevice(..);

有点晚了,但希望这能帮到别人。

Just create following 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();
}
}

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

.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

or

 yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

这是根据 Brian Roach 和 AYarulin 的答案制作的 Kotlin 版本。

class RestDeserializer<T>(targetClass: Class<T>, key: String?) : JsonDeserializer<T> {
val targetClass = targetClass
val key = key


override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
val data = json!!.asJsonObject.get(key ?: "")


return Gson().fromJson(data, targetClass)
}
}

根据“ Brian Roach”和“ rafakob”的回答,我是这样做的

来自服务器的 Json 响应

{
"status": true,
"code": 200,
"message": "Success",
"data": {
"fullname": "Rohan",
"role": 1
}
}

Common data handler class

public class ApiResponse<T> {
@SerializedName("status")
public boolean status;


@SerializedName("code")
public int code;


@SerializedName("message")
public String reason;


@SerializedName("data")
public T content;
}

自定义序列化程序

static class MyDeserializer<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);


}
}

Gson 对象

Gson gson = new GsonBuilder()
.registerTypeAdapter(ApiResponse.class, new MyDeserializer<ApiResponse>())
.create();

阿比来电话了

 @FormUrlEncoded
@POST("/loginUser")
Observable<ApiResponse<Profile>> signIn(@Field("email") String username, @Field("password") String password);


restService.signIn(username, password)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<ApiResponse<Profile>>() {
@Override
public void onCompleted() {
Log.i("login", "On complete");
}


@Override
public void onError(Throwable e) {
Log.i("login", e.toString());
}


@Override
public void onNext(ApiResponse<Profile> response) {
Profile profile= response.content;
Log.i("login", profile.getFullname());
}
});

另一个简单的解决办法:

JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
Content content = gson.fromJson(parsed.get("content"), Content.class);

还有一种更简单的方法,只需将 content子对象看作另一个类:

class Content {
var foo = 0
var bar: String? = null
}


class Response {
var statis: String? = null
var reason: String? = null
var content: Content? = null
}

现在您可以使用 Response类型来反序列化 json。