2014-07-22 337 views
10

我有以下的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:不能 反序列化com.example.ItemList的情况下进行START_OBJECT令牌

我有试图添加@JsonDeserialize(as=ItemList.class)但它并没有伎俩。

什么路要走?

+0

我敢肯定,http://stackoverflow.com/users/22656/jon-skeet将能够回答这个问题。另外,您可以在http://stackoverflow.com/a/21279016/1382251中找到一些有用的信息。 –

+1

虽然这是一个很好的问题,但如果你只想迭代,为什么要让你的类成为一个集合,而不是只能迭代? – chrylis

+0

@chrylis:这样我就可以为(Item t:itemList)' –

回答

4

很明显,它不起作用,因为杰克逊使用Java收集类型的标准收集反序列化器,它对ItemList属性一无所知。

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

请注意,我要重写ObjectMapper获得访问受保护的方法ObjectMappercreateDeserializationContext创建合适的deserialisation上下文因为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

+1好。我花了几个小时尝试使用'BeanDeserializerModifier.modifyCollectionDeserializer'来想出类似的解决方案,但无法弄清楚如何获得'DeserializationContext'。 –

+0

看起来像一个优雅的方式来处理,我会试试看。 –

+1

一个建议:不应该对子类ObjectMapper进行分类,而应该能够将ContextualDeserializer与自定义的Collection解串器结合起来:这使您可以访问所需的查找/修改值解串器,并且保证它将被正确初始化。查看默认集合序列化器来处理一些特殊情况(支持多态类型)可能是有意义的。 – StaxMan

2

如果考虑item属性为根值,可以不是改变你的ItemList类,如下所示,使用@JsonRootNameannotation

@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_VALUEdeserialization 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_VALUEserialization 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包含附加属性(不是实际列表等),也将需要被序列化/反序列化。

+0

不错的解决方法,但我想保留处理未来额外属性的可能性。 –