【问题标题】:JsonSerializer not working for nested objects using GSONJsonSerializer 不适用于使用 GSON 的嵌套对象
【发布时间】:2017-08-03 23:46:24
【问题描述】:

我有一个如下所示的 json 对象:

{
    "user": {
        "id": 1234
        ... 
        "photos": [
            {
                "url": "http://....."
                ...
            },
            {
                "url": "http://....."
                ...
            }
        ]
    }
}

我想为userphotos 编写一个自定义反序列化器。

所以我有:

public class User {
    private long id;
    private ArrayList<Photo> photos;
    ... 

    public static class Deserializer implements JsonDeserializer<User> {
        ... // does the custom serialization of the User object 
    }  
}

public class Photo {
    private String url;
    ... 

    public static class Deserializer implements JsonDeserializer<Photos> {
        ... // does the custom serialization of the Photo object 
    }  
}

在初始化时我这样做:

new GsonBuilder()
   .registerTypeAdapter(User.class, new User.Deserializer());
   .registerTypeAdapter(Photos.class, new Photos.Deserializer());

但是,当我反序列化 User 类时,它会命中 User 的反序列化器,但从未命中 Photo 的反序列化器。但是,如果我得到一个 json,其中的照片对象没有嵌套在用户 json 对象中,如下所示:

{
    "photos": [
         {
             "url": "http://....."
              ...
         },
         {
             "url": "http://....."
              ...
         },
         {
             "url": "http://....."
              ...
         }
]

它会正确命中Photo的反序列化器

【问题讨论】:

  • 您能告诉我为什么需要自定义反序列化器来处理这种情况吗?
  • 我上面给出的例子是一个非常简单的例子。我在应用中使用的对象要复杂得多。
  • 您好,我在 GitHub 上看到了您的问题:github.com/google/gson/issues/1034 -- 既然您知道 Gson 类型适配器现在是如何工作的,能否请您关闭它?谢谢。

标签: java json serialization gson android


【解决方案1】:

简而言之,有一条非正式规则:一旦你为一个某些类型声明了一个类型适配器(或原则上共享相同概念的(反)序列化器),那么你就有了自己管理其实例化及其子字段。因此,当您反序列化最顶层的User 时,它的idphotos 将自行反序列化。请注意,一旦您像 gson.fromJson(..., Photo.class) 一样明确请求或隐式应用它(对于后者 Gson 默认使用内置策略,请参阅 ReflectiveTypeAdapterFactory 例如),就会调用 Photo.Deserializer 通过反序列化上下文。如果您不绑定User.Deserializer,则同样的原则适用于User,因此Gson 使用ReflectiveTypeAdapterFactory.Adapter&lt;T&gt;,它只是使用反射遍历所有字段本身。更短:Gson 不会合并多个策略(至少在默认情况下),因此您要么将对象构造和设置委托给 Gson,要么完全实例化它。

知道User.Deserializer可以如下实现:

final class User {

    final long id;
    final List<Photo> photos;

    private User(final long id, final List<Photo> photos) {
        this.id = id;
        this.photos = photos;
    }

    static final class Deserializer
            implements JsonDeserializer<User> {

        private static final Type photoListType = new TypeToken<List<Photo>>() {
        }.getType();

        @Override
        public User deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
            // Note that you must pick up properties first
            final JsonObject jsonObject = jsonElement.getAsJsonObject();
            return new User(
                    // And then delegate them to the deserialization context specifying the target type
                    context.deserialize(jsonObject.get("id"), long.class),
                    // You can deconstruct JsonElement recursively, but deserialization context respects Gson context built with GsonBuilder
                    // This also does trigger the Photo.Deserializer
                    context.deserialize(jsonObject.get("photos"), photoListType)
            );
        }

    }

}

我假设您的代码中的Photos 是一个错字,它应该是Photo。如果不是,那么可能会为Photos 实施类似的解决方案。

final class Photo {

    final String url;

    private Photo(final String url) {
        this.url = url;
    }

    static final class Deserializer
            implements JsonDeserializer<Photo> {

        @Override
        public Photo deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
            final JsonObject jsonObject = jsonElement.getAsJsonObject();
            return new Photo(
                    // jsonObject.get("url").getAsString() can be more simple, but it does not respect Gson instance configuration
                    context.deserialize(jsonObject.get("url"), String.class)
            );
        }

    }

}

如何使用:

final class Wrapper {

    final User user;

    private Wrapper(final User user) {
        this.user = user;
    }

}
final Gson gson = new GsonBuilder()
        .registerTypeAdapter(User.class, new User.Deserializer())
        .registerTypeAdapter(Photo.class, new Photo.Deserializer())
        .create();
final Wrapper wrapper = gson.fromJson(JSON, Wrapper.class);
System.out.println(wrapper.user.id);
wrapper.user.photos.forEach(p -> System.out.println(p.url));

输出:

1234
http://.....
http://.....

【讨论】:

  • 啊,这就是我害怕的。并且没有办法合并多个策略(默认情况下你没有提到)
  • @Sree 是的,这就是简单的代价。一个示例自定义类型适配器工厂,可用于将一些策略合并在一起:github.com/google/gson/blob/master/extras/src/main/java/com/… -- a delegate-first-and-then-post-process-somehow approach。
  • 也就是说,你只需要为父类型声明一个类型适配器——孩子的类型适配器会是多余的吗?
【解决方案2】:

我认为这些类与 json 文件不匹配。在这个 json 文件中:

{
    "user": {
        "id": 1234
        ... 
        "photos": {
            "abc": {
                "url": "http://....."
                ...
            }
        }
    }
}

你有

public class User {
    private int id;
    private Photos photos;
} 

public class Photos {
   private MyUrl abc;
   private MyUrl bcd;
   private MyUrl cde;
   ...
}

public class MyUrl {
    private String url;
}

要获取照片的 ArrayList,json 应该如下所示(注意方括号及其内容):

{
    "user": {
        "id": 1234
        ... 
        "photos": [
             { "url": "http://....." },
             { "url": "http://....." },
                ...
             { "url": "http://....." }
            ]
        }
    }
}

最后一位杰森回应:

public class User {
    private int id;
    ...
    private ArrayList<Photo> photos;
} 

public class Photo{
    private String url;
}

【讨论】:

  • 抱歉,这个例子有误。我更新了它以遵守我的 java 类。否则它会打击反序列化器,所以我认为代码正在运行。这只是从另一个反序列化器调用它的问题,这似乎是问题
  • 它说照片的地方真的是照片。这是一个错字。不是两个不同的类别。对吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多