Java 使用改型使用GSON获取嵌套JSON对象

Java 使用改型使用GSON获取嵌套JSON对象,java,android,json,gson,retrofit,Java,Android,Json,Gson,Retrofit,我正在使用android应用程序中的API,所有JSON响应如下: { 'status': 'OK', 'reason': 'Everything was fine', 'content': { < some data here > } { “状态”:“确定”, ‘原因’:‘一切都很好’, “内容”:{ } 问题是,我所有的POJO都有一个状态,原因字段,在内容字段中是我想要的真正的POJO 有没有办法创建一个Gson的自定义转换器,以

我正在使用android应用程序中的API,所有JSON响应如下:

{
    'status': 'OK',
    'reason': 'Everything was fine',
    'content': {
         < some data here >
}
{
“状态”:“确定”,
‘原因’:‘一切都很好’,
“内容”:{
<这里有一些数据>
}
问题是,我所有的POJO都有一个
状态
原因
字段,在
内容
字段中是我想要的真正的POJO


有没有办法创建一个Gson的自定义转换器,以便始终提取
内容
字段,以便改型返回适当的POJO?

您可以编写一个自定义反序列化程序,返回嵌入的对象

假设您的JSON是:

{
    "status":"OK",
    "reason":"some reason",
    "content" : 
    {
        "foo": 123,
        "bar": "some value"
    }
}
然后您将拥有一个
内容
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 gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer())
        .create();
Gson gson = new GsonBuilder()
    .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
    .build();
您可以将JSON直接反序列化到您的
内容

Content c = gson.fromJson(myJson, Content.class);
编辑以添加评论:

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

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

    }
}

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

    }
}
类重新反序列化器实现JsonDeserializer{
私有类mClass;
私有字符串mKey;
公共重新反序列化程序(类targetClass,字符串键){
mClass=targetClass;
mKey=键;
}
@凌驾
公共T反序列化(JsonElement je,类型类型,JsonDeserializationContext jdc)
抛出JsonParseException{
JsonElement content=je.getAsJsonObject().get(mKey);
返回新的Gson().fromJson(内容,mClass);
}
}
然后,为了从上面解析示例负载,我们可以注册GSON反序列化器:

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 gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer())
        .create();
Gson gson = new GsonBuilder()
    .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
    .build();
Gson Gson=new GsonBuilder()
.registerTypeAdapter(Content.class,新的重新反序列化程序(Content.class,“Content”))
.build();

@BrianRoach的解决方案是正确的。值得注意的是,在特殊情况下,如果嵌套的自定义对象都需要自定义
TypeAdapter
,则必须将
TypeAdapter
注册到GSON的新实例中,否则将永远不会调用第二个
TypeAdapter
d、 这是因为我们正在自定义反序列化程序中创建一个新的
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;
}
您需要注册
子内容
类型适配器

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实现JsonDeserializer{
私人期末班助教;
私有最终对象MNestedSerializer;
公共MyDeserializer(类nestedClazz、对象nestedDeserializer){
mNestedClazz=nestedClazz;
mnestedserializer=nestedDeserializer;
}
@凌驾
公共T反序列化(JsonElement je,类型类型,JsonDeserializationContext jdc)抛出JsonParseException{
//从解析的JSON中获取“content”元素
JsonElement content=je.getAsJsonObject().get(“content”);
//反序列化它。您使用一个新的Gson实例来避免无限递归
//到这个反序列化程序
GsonBuilder=新的GsonBuilder();
if(mNestedClazz!=null&&mnestedserializer!=null){
注册类型适配器(mNestedClazz、mnestddeserializer);
}
返回builder.create().fromJson(内容、类型);
}
}
然后像这样创建它:

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

这也可以很容易地用于嵌套的“内容”情况,只需使用空值传入一个新的
MyDeserializer

这是与@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);

    }
}
类重新反序列化器实现JsonDeserializer{
私有类mClass;
私有字符串mKey;
公共重新反序列化程序(类targetClass){
mClass=targetClass;
mKey=mClass.getSimpleName();
}
@凌驾
公共T反序列化(JsonElement je,类型类型,JsonDeserializationContext jdc)
抛出JsonParseException{
JsonElement content=je.getAsJsonObject().get(mKey);
返回新的Gson().fromJson(内容,mClass);
}
}
然后,为了解析上面的示例负载,我们可以注册GSON反序列化器。这是有问题的,因为键是区分大小写的,所以类名的大小写必须与JSON键的大小写匹配

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

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

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

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() );
}
接收变压器:

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() );
}
protected Observable.Transformer

在我的例子中,每个响应的“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
    }
}
在这种情况下,我使用了上面列出的类似解决方案,但不得不对其进行调整。你可以看到要点。它有点太大了,无法在SOF上发布


注释
@InnerKey(“content”)
被使用,其余的代码是为了方便它在Gson中的使用。

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


请看

一个更好的解决方案可能是:

public class ApiResponse<T> {
    public T data;
    public String status;
    public String reason;
}
公共类响应{
公共数据;
公共字符串状态;
公共字符串原因;
}
然后,像这样定义您的服务

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

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