【问题标题】:Jackson deserialization - with contained ArrayList<T>Jackson 反序列化 - 包含 ArrayList<T>
【发布时间】:2012-06-14 06:40:23
【问题描述】:

早安,

我目前正在尝试使用 Jackson(使用 Jersey)使用生成 JSON(用 .NET 编写)的 REST 服务进行集成。 JSON 由可能的错误消息和对象数组组成。下面是由 Jersey 的日志过滤器生成的 JSON 返回示例:

{
    "error":null,
    "object":"[{\"Id\":16,\"Class\":\"ReportType\",\"ClassID\":\"4\",\"ListItemParent_ID\":4,\"Item\":\"Pothole\",\"Description\":\"Pothole\",\"Sequence\":1,\"LastEditDate\":null,\"LastEditor\":null,\"ItemStatus\":\"Active\",\"ItemColor\":\"#00AF64\"}]"
}

我有两个类来表示类型(外部 ListResponse):

public class ListResponse { 

    public String error;    
    public ArrayList<ListItem> object;  

    public ListResponse() { 
    }
}

和(内部 ListItem):

public class ListItem {
    @JsonProperty("Id")
    public int id;      
    @JsonProperty("Class")
    public String classType;
    @JsonProperty("ClassID")
    public String classId;  
    @JsonProperty("ListItemParent_ID")
    public int parentId;    
    @JsonProperty("Item")
    public String item; 
    @JsonProperty("Description")
    public String description;

    @JsonAnySetter 
    public void handleUnknown(String key, Object value) {}

    public ListItem() {
    }
}

调用并返回 JSON 的类如下所示:

public class CitizenPlusService {
    private Client client = null;   
    private WebResource service = null;     

    public CitizenPlusService() {
        initializeService("http://localhost:59105/PlusService/"); 
    }

    private void initializeService(String baseURI) {    
        // Use the default client configuration. 
        ClientConfig clientConfig = new DefaultClientConfig();      
        clientConfig.getClasses().add(JacksonJsonProvider.class);                       

        client = Client.create(clientConfig);

        // Add a logging filter to track communication between server and client. 
        client.addFilter(new LoggingFilter()); 
        // Add the base URI
        service = client.resource(UriBuilder.fromUri(baseURI).build()); 
    }

    public ListResponse getListItems(String id) throws Exception
    {           
        ListResponse response = service.path("GetListItems").path(id).accept(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).get(ListResponse.class);                                  
        return response;            
    }
}

这里重要的调用是 getListItems 方法。在测试工具中运行代码,会产生以下结果:

org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
at [Source: java.io.StringReader@49497eb8; line: 1, column: 14] (through reference chain: citizenplus.types.ListResponse["object"])

请帮忙。

问候, 卡尔-彼得迈耶

【问题讨论】:

  • 你能解决这个问题吗?
  • 如果您找到一种方法来更改 .NET 中的 JSON 支持stackoverflow.com/a/14760003/2022175,我的回答可能会对您有所帮助
  • 我认为这里的问题在于输入 JSON:'object' 属性由一个 包含 JSON 数组的字符串组成,但 Jackson 需要一个原生数组(没有包装撇号)。我目前遇到同样的问题......如果我想出一个解决方案,我会告诉你;)

标签: json jackson


【解决方案1】:

您可能缺少@JsonDeserialize 属性,因为类型信息在运行时会在泛型中丢失。如果可以的话,你也应该避免使用具体的集合类。

public class ListResponse { 

    public String error;

    @JsonDeserialize(as=ArrayList.class, contentAs=ListItem.class)
    public List<ListItem> object;  

}

【讨论】:

    【解决方案2】:

    您的问题是“对象”属性值是字符串而不是数组!该字符串包含一个 JSON 数组,但 Jackson 需要一个原生数组(不带引号)。

    我遇到了同样的问题,我创建了一个自定义反序列化器,它将字符串值反序列化为所需类型的通用集合:

    public class JsonCollectionDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {
    
      private final BeanProperty    property;
    
      /**
       * Default constructor needed by Jackson to be able to call 'createContextual'.
       * Beware, that the object created here will cause a NPE when used for deserializing!
       */
      public JsonCollectionDeserializer() {
        super(Collection.class);
        this.property = null;
      }
    
      /**
       * Constructor for the actual object to be used for deserializing.
       *
       * @param property this is the property/field which is to be serialized
       */
      private JsonCollectionDeserializer(BeanProperty property) {
        super(property.getType());
        this.property = property;
      }
    
      @Override
      public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        return new JsonCollectionDeserializer(property);
      }
    
    
      @Override
      public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        switch (jp.getCurrentToken()) {
          case VALUE_STRING:
            // value is a string but we want it to be something else: unescape the string and convert it
            return JacksonUtil.MAPPER.readValue(StringUtil.unescapeXml(jp.getText()), property.getType());
          default:
            // continue as normal: find the correct deserializer for the type and call it
            return ctxt.findContextualValueDeserializer(property.getType(), property).deserialize(jp, ctxt);
        }
      }
    }
    

    请注意,如果值实际上是一个数组而不是字符串,这个反序列化器也可以工作,因为它相应地委托实际的反序列化。

    在您的示例中,您现在必须像这样注释您的集合字段:

    public class ListResponse { 
    
        public String error;    
        @JsonDeserialize(using = JsonCollectionDeserializer.class)
        public ArrayList<ListItem> object;  
    
        public ListResponse() {}    
    }
    

    应该就是这样。

    注意:JacksonUtil 和 StringUtil 是自定义类,但您可以轻松替换它们。例如使用new ObjectMapper() 和org.apache.commons.lang3.StringEscapeUtils。

    【讨论】:

    • 我喜欢这个解决方案,但在某些情况下我会看到堆栈溢出。具体来说,我有一个 List&lt;List&lt;Integer&gt;&gt; 字段。在这种情况下,return ctxt.findContextualValueDeserializer(property.getType(), property).deserialize(jp, ctxt); 在查询内部列表时会无限递归。没有机会深入研究它,但我相信这是因为传入的属性是基于外部列表,而不是内部列表。
    【解决方案3】:

    寄存器子类型有效!

    @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
    public interface Geometry {
    
    }
    
    public class Point implements Geometry{
     private String type="Point";
      ....
    }
    public class Polygon implements Geometry{
       private String type="Polygon";
      ....
    }
    public class LineString implements Geometry{
      private String type="LineString";
      ....
    }
    
    
    GeoJson geojson= null;
    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
    mapper.registerSubtypes(Polygon.class,LineString.class,Point.class);
    try {
        geojson=mapper.readValue(source, GeoJson.class);
    
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    【讨论】:

      猜你喜欢
      • 2012-05-02
      • 1970-01-01
      • 1970-01-01
      • 2012-08-23
      • 2014-08-04
      • 2012-04-07
      • 2020-07-23
      • 2023-04-11
      • 2011-09-09
      相关资源
      最近更新 更多