我从我的Android应用程序中使用API,所有JSON响应都是这样的:
{
'status': 'OK',
'reason': 'Everything was fine',
'content': {
< some data here >
}
问题是我的所有POJO都有status
,reason
字段,content
字段内是我想要的真正POJO。
有没有办法创建Gson的自定义转换器来提取content
字段,所以改装会返回合适的POJO吗?
答案 0 :(得分:155)
您可以编写一个返回嵌入对象的自定义反序列化器。
让我们说你的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);
}
}
现在,如果您使用Gson
构建GsonBuilder
并注册反序列化器:
Gson gson =
new GsonBuilder()
.registerTypeAdapter(Content.class, new MyDeserializer())
.create();
您可以直接将JSON反序列化为Content
:
Content c = gson.fromJson(myJson, Content.class);
编辑以添加评论:
如果您有不同类型的消息,但他们都有&#34;内容&#34;在字段中,您可以通过执行以下操作使反序列化器具有通用性:
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();
答案 1 :(得分:15)
@ 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;
}
您需要注册SubContent
&#39; 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();
这可以很容易地用于嵌套的内容&#34;通过简单地传递一个具有空值的MyDeserializer
的新实例。
答案 2 :(得分:9)
迟到但希望这会对某人有所帮助。
只需创建以下TypeAdapterFactory。
.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
并将其添加到您的GSON构建器中:
yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
或
team1, team2
答案 3 :(得分:7)
继续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();
答案 4 :(得分:6)
几天前有同样的问题。我使用响应包装器类和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() );
}
我的主题: Retrofit 2 RxJava - Gson - "Global" deserialization, change response type
答案 5 :(得分:3)
更好的解决方案可能就是这个......
public class ApiResponse<T> {
public T data;
public String status;
public String reason;
}
然后,像这样定义你的服务..
Observable<ApiResponse<YourClass>> updateDevice(..);
答案 6 :(得分:3)
根据@Brian Roach和@rafakob的回答,我按照以下方式完成了这个
来自服务器的Json响应
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 = new GsonBuilder()
.registerTypeAdapter(ApiResponse.class, new MyDeserializer<ApiResponse>())
.create();
Gson对象
@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());
}
});
Api通话
{{1}}
答案 7 :(得分:2)
这与@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();
答案 8 :(得分:2)
这是基于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)
}
}
答案 9 :(得分:1)
就我而言,每个回复都会改变“内容”键。例如:
// 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一起使用。
答案 10 :(得分:0)
不要忘记所有通过GSON从JSON反序列化的所有类成员和内部类成员的@SerializedName
和@Expose
注释。
答案 11 :(得分:0)
另一个简单的解决方案:
JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
Content content = gson.fromJson(parsed.get("content"), Content.class);
答案 12 :(得分:0)
有一种更简单的方法,只需将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。