【问题标题】:Deserializing fails for a class implementing Collection with Jackson使用 Jackson 实现 Collection 的类的反序列化失败
【发布时间】:2014-07-22 17:50:08
【问题描述】:

我有以下 JSON:

{
  "item": [
    { "foo": 1 },
    { "foo": 2 }
  ]
} 

这基本上是一个包含项目集合的对象。

所以我做了一个类来反序列化它:

public class ItemList {
  @JsonProperty("item")
  List<Item> items;

  // Getters, setters & co.
  // ...
}

到目前为止一切正常。

现在,为了让我在其他地方的生活更轻松,我决定能够迭代 ItemList 对象并让它实现 Collection 接口会很好。

所以基本上我的课变成了:

public class ItemList implements Collection<Item>, Iterable<Item> {
  @JsonProperty("item")
  List<Item> items;

  // Getters, setters & co.

  // Generated all method delegates to items. For instance:
  public Item get(int position) {
    return items.get(position);
  }
}

实施工作正常且良好。但是,反序列化现在失败了。

看起来杰克逊很困惑:

com.fasterxml.jackson.databind.JsonMappingException: 不能 从 START_OBJECT 令牌中反序列化 com.example.ItemList 的实例

我尝试添加@JsonDeserialize(as=ItemList.class),但没有成功。

怎么走?

【问题讨论】:

  • 我很确定stackoverflow.com/users/22656/jon-skeet 将能够回答这个问题。此外,您也许可以在stackoverflow.com/a/21279016/1382251 中找到一些有用的信息。
  • 虽然这是一个很好的问题,但如果您只想迭代,为什么要让您的类成为集合而不是可迭代?
  • @chrylis: 这样我就可以for (Item t : itemList)
  • 这不能回答我的问题。增强的 for 循环只需要Iterable
  • @chrylis: 是的,它是别的东西,一些代码正在使用 List.addAll (这需要 Collection)。虽然没有什么戏剧性的,但我想让它变得干净,以便可以将一个列表添加到另一个列表中。其他可能需要集合的 API 也是如此(例如我认为的 Comparator 或 sort & co。)。

标签: java json jackson


【解决方案1】:

显然它不起作用,因为 Jackson 对 Java 集合类型使用标准集合反序列化器,它对 ItemList 属性一无所知。

可以让它工作,但不是以一种非常优雅的方式。您需要配置 ObjectMapper 以替换为相应类型手动创建的 bean 反序列化器上的默认集合反序列化器。我在BeanDeserializerModifier 中为所有使用自定义注释注释的类编写了一个示例。

请注意,我必须重写 ObjectMapper 才能访问受保护的方法 createDeserializationContextObjectMapper 以创建正确的反序列化上下文,因为 bean 修饰符无权访问它。

代码如下:

public class JacksonCustomList {
    public static final String JSON = "{\n" +
            "  \"item\": [\n" +
            "    { \"foo\": 1 },\n" +
            "    { \"foo\": 2 }\n" +
            "  ]\n" +
            "} ";

    @Retention(RetentionPolicy.RUNTIME)
    public static @interface PreferBeanDeserializer {

    }

    public static class Item {
        public int foo;

        @Override
        public String toString() {
            return String.valueOf(foo);
        }
    }

    @PreferBeanDeserializer
    public static class ItemList extends ArrayList<Item> {
        @JsonProperty("item")
        public List<Item> items;

        @Override
        public String toString() {
            return items.toString();
        }
    }

    public static class Modifier extends BeanDeserializerModifier {
        private final MyObjectMapper mapper;

        public Modifier(final MyObjectMapper mapper) {
            this.mapper = mapper;
        }

        @Override
        public JsonDeserializer<?> modifyCollectionDeserializer(
                final DeserializationConfig config,
                final CollectionType type,
                final BeanDescription beanDesc,
                final JsonDeserializer<?> deserializer) {
            if (type.getRawClass().getAnnotation(PreferBeanDeserializer.class) != null) {
                DeserializationContext context = mapper.createContext(config);
                try {
                    return context.getFactory().createBeanDeserializer(context, type, beanDesc);
                } catch (JsonMappingException e) {
                   throw new IllegalStateException(e);
                }

            }
            return super.modifyCollectionDeserializer(config, type, beanDesc, deserializer);
        }
    }

    public static class MyObjectMapper extends ObjectMapper {
        public DeserializationContext createContext(final DeserializationConfig cfg) {
            return super.createDeserializationContext(getDeserializationContext().getParser(), cfg);
        }
    }

    public static void main(String[] args) throws IOException {
        final MyObjectMapper mapper = new MyObjectMapper();
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new Modifier(mapper));

        mapper.registerModule(module);
        System.out.println(mapper.readValue(JSON, ItemList.class));
    }

}

【讨论】:

  • +1 不错。我花了几个小时尝试使用BeanDeserializerModifier.modifyCollectionDeserializer 提出类似的解决方案,但不知道如何获得DeserializationContext
  • 看起来是一种优雅的处理方式,我试试看。
  • 一个建议:您应该能够将ContextualDeserializer 与自定义集合反序列化器结合起来,而不是子类化ObjectMapper:这使您可以访问查找/修改所需的值反序列化器,并且保证它将被正确初始化。看看默认的集合序列化器来处理一些特殊情况(对多态类型的支持)可能是有意义的。
【解决方案2】:

如果您认为item 属性是根值,则可以使用@JsonRootName annotation 如下更改ItemList 类:

@JsonRootName("item")
public class ItemList implements Collection<Item>, Iterable<Item> {
    private List<Item> items = new ArrayList<>();

    public Item get(int position) {
        return items.get(position);
    }

    // implemented methods deferring to delegate
    // ...
}

如果您随后激活 UNWRAP_ROOT_VALUE deserialization feature,则一切正常:

String json = "{\"item\": [{\"foo\": 1}, {\"foo\": 2}]}";
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.reader(ItemList.class);

ItemList itemList = reader
        .with(DeserializationFeature.UNWRAP_ROOT_VALUE)
        .readValue(json);

启用WRAP_ROOT_VALUE serialization feature 后,序列化同样有效:

ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer();

Item item1 = new Item();
item1.setFoo(1);

Item item2 = new Item();
item2.setFoo(2);

ItemList itemList = new ItemList();
itemList.add(item1);
itemList.add(item2);

String json = writer
        .with(SerializationFeature.WRAP_ROOT_VALUE)
        .writeValueAsString(itemList);

// json contains {"item":[{"foo":1},{"foo":2}]}

如果您的ItemList 包含也需要序列化/反序列化的其他属性(实际列表除外),则此解决方案显然不够。

【讨论】:

  • 不错的解决方法,但我想保留将来处理额外属性的可能性。
猜你喜欢
  • 1970-01-01
  • 2014-07-16
  • 2018-12-26
  • 2019-06-01
  • 2019-09-28
  • 1970-01-01
  • 2020-08-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多