使用改造获取带有 GSON 的嵌套 JSON 对象
Posted
技术标签:
【中文标题】使用改造获取带有 GSON 的嵌套 JSON 对象【英文标题】:Get nested JSON object with GSON using retrofit 【发布时间】:2014-05-29 00:22:08 【问题描述】:我正在使用我的 android 应用程序中的 API,所有 JSON 响应都是这样的:
'status': 'OK',
'reason': 'Everything was fine',
'content':
< some data here >
问题是我所有的 POJO 都有一个 status
、reason
字段,而在 content
字段里面是我想要的真正的 POJO。
有什么方法可以创建一个 Gson 的自定义转换器来始终提取 content
字段,因此改造返回适当的 POJO?
【问题讨论】:
sites.google.com/site/gson/… 我阅读了文档,但我不知道该怎么做... :( 我不知道如何编写代码来解决我的问题 我很好奇你为什么不只是格式化你的 POJO 类来处理这些状态结果。 【参考方案1】:您将编写一个返回嵌入对象的自定义反序列化器。
假设您的 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();
您可以将 JSON 直接反序列化为您的 Content
:
Content c = gson.fromJson(myJson, Content.class);
编辑以从 cmets 添加:
如果您有不同类型的消息,但它们都具有“内容”字段,则可以通过以下操作使反序列化器通用:
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()
时,该类型被带入反序列化器,因此它应该适用于您的所有类型。
最后在创建 Retrofit 实例时:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
【讨论】:
哇,太棒了!谢谢! :D 有什么方法可以概括解决方案,这样我就不必为每种类型的响应创建一个 JsonDeserializer? 这太棒了!一件事要改变: Gson gson = new GsonBuilder().create();而不是 Gson gson = new GsonBuilder().build();有两个例子。 @feresr 你可以在 Retrofit 的RestAdapter.Builder
类中调用setConverter(new GsonConverter(gson))
@BrianRoach 谢谢,很好的回答.. 我应该用分离的解串器注册Person.class
和List<Person>.class
/Person[].class
吗?
还有可能获得“状态”和“原因”吗?例如,如果所有请求都返回它们,我们是否可以将它们放在超类中并使用来自“内容”的实际 POJO 的子类?【参考方案2】:
@BrianRoach 的解决方案是正确的解决方案。值得注意的是,在嵌套自定义对象且都需要自定义TypeAdapter
的特殊情况下,您必须将TypeAdapter
注册到GSON 的新实例,否则第二个@987654323 @ 永远不会被调用。这是因为我们在自定义反序列化器中创建了一个新的 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;
您需要注册SubContent
的TypeAdapter
。为了更健壮,您可以执行以下操作:
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();
这也可以很容易地用于嵌套的“内容”案例,只需传入一个带有空值的 MyDeserializer
的新实例。
【讨论】:
“类型”来自哪个包?有一百万个包包含“类型”类。谢谢。 @Mr.Tea 会是java.lang.reflect.Type
SubContentDeserializer 类在哪里? @KMarlow【参考方案3】:
有点晚了,但希望这会对某人有所帮助。
只需创建以下 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());
或
yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
【讨论】:
这正是我的样子。因为我有很多用“数据”节点包装的类型,我不能将 TypeAdapter 添加到每个类型。谢谢! @SergeyIrisov 欢迎您。你可以投票给这个答案,让它变得更高:) 如何传递多个jsonElement
?.比如我有content
、content1
等。
我认为你的后端开发人员应该改变结构,不要传递内容,content1...这种方法有什么优势?
谢谢!这是完美的答案。 @Marin Petrulak:优点是这种形式可以适应未来的变化。 “内容”是响应内容。未来它们可能会出现新的字段,如“version”、“lastUpdated”、“sessionToken”等。如果你没有事先包装你的响应内容,你会在你的代码中遇到一堆变通方法来适应新的结构。【参考方案4】:
几天前遇到了同样的问题。我已经使用响应包装类和 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;
接收变压器:
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() );
我的话题: Retrofit 2 RxJava - Gson - "Global" deserialization, change response type
【讨论】:
MyPojo 长什么样子? @IgorGanapolsky MyPojo 可以随心所欲。它应该与从服务器检索到的内容数据相匹配。此类的结构应根据您的序列化转换器(Gson、Jackson 等)进行调整。 @rafakob 你能帮我解决我的问题吗?很难以最简单的方式尝试在我的嵌套 json 中获取一个字段。这是我的问题:***.com/questions/56501897/…【参考方案5】:继续 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);
然后为了解析上面的样本payload,我们可以注册GSON反序列化器:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
.build();
【讨论】:
【参考方案6】:更好的解决方案可能是这样..
public class ApiResponse<T>
public T data;
public String status;
public String reason;
然后,像这样定义您的服务..
Observable<ApiResponse<YourClass>> updateDevice(..);
【讨论】:
【参考方案7】:根据@Brian Roach 和@rafakob 的回答,我通过以下方式完成此操作
来自服务器的 JSON 响应
"status": true,
"code": 200,
"message": "Success",
"data":
"fullname": "Rohan",
"role": 1
通用数据处理类
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();
API调用
@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());
);
【讨论】:
【参考方案8】:这与@AYarulin 的解决方案相同,但假设类名是 JSON 键名。这样你只需要传递类名。
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();
【讨论】:
【参考方案9】:这是基于 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)
【讨论】:
【参考方案10】:在我的例子中,每个响应的“内容”键都会改变。示例:
// 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 上发布它有点太大了。
使用注解@InnerKey("content")
,其余代码方便与Gson配合使用。
【讨论】:
你能帮我解决我的问题吗?花点时间以最简单的方式从嵌套的 json 中获取字段:***.com/questions/56501897/…【参考方案11】:不要忘记 GSON 从 JSON 反序列化最多的所有类成员和内部类成员的 @SerializedName
和 @Expose
注释。
看https://***.com/a/40239512/1676736
【讨论】:
【参考方案12】:另一个简单的解决方案:
JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
Content content = gson.fromJson(parsed.get("content"), Content.class);
【讨论】:
【参考方案13】:还有一个更简单的方法,只要把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。
【讨论】:
以上是关于使用改造获取带有 GSON 的嵌套 JSON 对象的主要内容,如果未能解决你的问题,请参考以下文章
android使用gson解析嵌套复杂的json数据,数据怎么显示到布局上,布局怎么写
在 Android 上通过 GSON 从嵌套 JSON 数组中获取价值? [复制]
如何访问数组改造 2 / android studio 中的对象“播放器”