2014-07-22 344 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

不錯的解決方法,但我想保留處理未來額外屬性的可能性。 –