Java 使用改造获取带有 GSON 的嵌套 JSON 对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23070298/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get nested JSON object with GSON using retrofit
提问by mikelar
I'm consuming an API from my android app, and all the JSON responses are like this:
我正在从我的 android 应用程序中使用一个 API,所有的 JSON 响应都是这样的:
{
'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.
问题是我所有的 POJO 都有一个status
,reason
字段,并且该content
字段内部是我想要的真正的 POJO。
Is there any way to create a custom converter of Gson to extract always the content
field, so retrofit returns the appropiate POJO?
有什么方法可以创建 Gson 的自定义转换器来始终提取该content
字段,以便改造返回适当的 POJO?
采纳答案by Brian Roach
You would write a custom deserializer that returns the embedded object.
您将编写一个返回嵌入对象的自定义反序列化器。
Let's say your JSON is:
假设您的 JSON 是:
{
"status":"OK",
"reason":"some reason",
"content" :
{
"foo": 123,
"bar": "some value"
}
}
You'd then have a Content
POJO:
然后你会有一个Content
POJO:
class Content
{
public int foo;
public String bar;
}
Then you write a deserializer:
然后你写一个反序列化器:
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);
}
}
Now if you construct a Gson
with GsonBuilder
and register the deserializer:
现在,如果您构造一个Gson
withGsonBuilder
并注册反序列化器:
Gson gson =
new GsonBuilder()
.registerTypeAdapter(Content.class, new MyDeserializer())
.create();
You can deserialize your JSON straight to your Content
:
您可以将 JSON 直接反序列化为Content
:
Content c = gson.fromJson(myJson, Content.class);
Edit to add from comments:
编辑以从评论中添加:
If you have different types of messages but they all have the "content" field, you can make the Deserializer generic by doing:
如果您有不同类型的消息,但它们都有“内容”字段,您可以通过执行以下操作使反序列化器通用:
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);
}
}
You just have to register an instance for each of your types:
您只需要为每种类型注册一个实例:
Gson gson =
new GsonBuilder()
.registerTypeAdapter(Content.class, new MyDeserializer<Content>())
.registerTypeAdapter(DiffContent.class, new MyDeserializer<DiffContent>())
.create();
When you call .fromJson()
the type is carried into the deserializer, so it should then work for all your types.
当您调用.fromJson()
该类型时,该类型会被带入解串器,因此它应该适用于您的所有类型。
And finally when creating a Retrofit instance:
最后在创建 Retrofit 实例时:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
回答by AYarulin
Continuing Brian's idea, because we almost always have many REST resources each with it's own root, it could be useful to generalize deserialization:
继续 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);
}
}
Then to parse sample payload from above, we can register GSON deserializer:
然后从上面解析示例有效负载,我们可以注册 GSON 反序列化器:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
.build();
回答by KMarlow
@BrianRoach's solution is the correct solution. It is worth noting that in the special case where you have nested custom objects that both need a custom TypeAdapter
, you must register the TypeAdapter
with the new instance of GSON, otherwise the second TypeAdapter
will never be called. This is because we are creating a new Gson
instance inside our custom deserializer.
@BrianRoach 的解决方案是正确的解决方案。值得注意的是,在您嵌套的自定义对象都需要一个 custom 的特殊情况下TypeAdapter
,您必须TypeAdapter
使用GSON的新实例注册,否则TypeAdapter
将永远不会调用第二个。这是因为我们正在Gson
自定义反序列化器中创建一个新实例。
For example, if you had the following json:
例如,如果您有以下 json:
{
"status": "OK",
"reason": "some reason",
"content": {
"foo": 123,
"bar": "some value",
"subcontent": {
"useless": "field",
"data": {
"baz": "values"
}
}
}
}
And you wanted this JSON to be mapped to the following objects:
您希望将此 JSON 映射到以下对象:
class MainContent
{
public int foo;
public String bar;
public SubContent subcontent;
}
class SubContent
{
public String baz;
}
You would need to register the SubContent
's TypeAdapter
. To be more robust, you could do the following:
您需要注册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);
}
}
and then create it like so:
然后像这样创建它:
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.
通过简单地传入一个MyDeserializer
空值的新实例,这也可以很容易地用于嵌套的“内容”情况。
回答by Barry MSIH
This is the same solution as @AYarulin but assume the class name is the JSON key name. This way you only need to pass the Class name.
这与@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);
}
}
Then to parse sample payload from above, we can register GSON deserializer. This is problematic as the Key is case sensitive, so the case of the class name must match the case of the JSON key.
然后从上面解析示例有效负载,我们可以注册 GSON 解串器。这是有问题的,因为 Key 区分大小写,因此类名的大小写必须与 JSON 键的大小写匹配。
Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class))
.build();
回答by rafakob
Had the same problem couple of days ago. I've solve this using response wrapper class and RxJava transformer, which I think is quite flexiable solution:
前几天遇到同样的问题。我已经使用响应包装器类和 RxJava 转换器解决了这个问题,我认为这是非常灵活的解决方案:
Wrapper:
包装:
public class ApiResponse<T> {
public String status;
public String reason;
public T content;
}
Custom exception to throw, when status is not OK:
自定义异常抛出,当状态不正常时:
public class ApiException extends RuntimeException {
private final String reason;
public ApiException(String reason) {
this.reason = reason;
}
public String getReason() {
return apiError;
}
}
Rx transformer:
接收变压器:
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;
});
}
Example usage:
用法示例:
// 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() );
}
My topic: Retrofit 2 RxJava - Gson - "Global" deserialization, change response type
回答by Varun Achar
In my case, the "content" key would change for each response. Example:
就我而言,每个响应的“内容”键都会改变。例子:
// 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
}
}
In such cases I used a similar solution as listed above but had to tweak it. You can see the gist here. It's a little too large to post it here on SOF.
在这种情况下,我使用了上面列出的类似解决方案,但不得不对其进行调整。你可以在这里看到要点。把它张贴在 SOF 上有点太大了。
The annotation @InnerKey("content")
is used and the rest of the code is to facilitate it's usage with Gson.
使用了注解@InnerKey("content")
,其余的代码是为了方便它与 Gson 的使用。
回答by Sayed Abolfazl Fatemi
Don't forget @SerializedName
and @Expose
annotations for all Class members and Inner Class members that most deserialized from JSON by GSON.
不要忘了@SerializedName
,并@Expose
为所有类成员和嵌套类的成员,大部分由GSON从JSON反序列化的注释。
回答by Federico J Farina
A better solution could be this..
更好的解决方案可能是这样..
public class ApiResponse<T> {
public T data;
public String status;
public String reason;
}
Then, define your service like this..
然后,像这样定义您的服务..
Observable<ApiResponse<YourClass>> updateDevice(..);
回答by Matin Petrulak
Bit late but hopefully this will help someone.
有点晚了,但希望这会对某人有所帮助。
Just create following TypeAdapterFactory.
只需创建以下 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();
}
}
and add it into your GSON builder :
并将其添加到您的 GSON 构建器中:
.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
or
或者
yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
回答by RamwiseMatt
Here's a Kotlin version based on the answers by Brian Roach and AYarulin.
这是基于 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)
}
}