简体   繁体   English

转换包含嵌套JSONString的JSONObject时发生JsonMappingException

[英]JsonMappingException when converting a JSONObject containing a nested JSONString

I'm trying to run batch requests against Facebook with Jersey. 我正在尝试使用Jersey对Facebook运行批处理请求。 The problem is Facebook returns the strangest of structures - a mix of JSONObject and JSONString: 问题是Facebook返回了最奇怪的结构-JSONObject和JSONString的混合:

[
   {
      "code": 200,
      "headers": [
         {
            "name": "Access-Control-Allow-Origin",
            "value": "*"
         },
         <!-- some more headers... -->
      ],
      "body": "{\n   \"message\": \"Hello World!\",\n   \"id\": \"...\",\n   \"created_time\": \"2012-10-17T07:18:02+0000\"\n 
                   <!-- ... -->
               }"
   }
]

Now when I try to use the Jackson ObjectMapper to deserialize this mess, I get a 现在,当我尝试使用Jackson ObjectMapper反序列化此混乱时,我得到了

JsonMappingException: Can not instantiate value of type [simple type, class package.to.Post] from JSON String; no single-String constructor/factory method (through reference chain: package.to.BatchResult["body"])

This is the POJO structure I'm using: 这是我正在使用的POJO结构:

public class BatchResult<T> {

    private T body;
    private int code;
    private List<BatchResultHeader> headers;
    // ...
}

public class BatchResultHeader {

    private String name;
    private String value;
    // ...
}

public class Post {

    private String message;
    // ...
}

I send the batch request like this. 我这样发送批处理请求 Params contains the batch parameter and the batch request as defined in the documentation. 参数包含文档中定义的批处理参数和批处理请求。 Also required is the POST call for batch requests. 还需要批处理请求的POST调用。 So like I said the call should be fine, as the resulting JSON is as expected (see above): 因此,就像我说的那样,调用应该没问题,因为生成的JSON符合预期 (参见上文):

Client client = Client.create();
WebResource webResource = client.resource("https://graph.facebook.com");
ClientResponse response = webResource.type("application/x-www-form-urlencoded")
            .post(ClientResponse.class, params);
String json = response.getEntity(String.class);

Now I just use the ObjectMapper to deserialize : 现在,我只使用ObjectMapper 反序列化

TypeReference<List<BatchResult<Post>>> ref = new TypeReference<List<BatchResult<Post>>>(){});
ObjectMapper mapper = new ObjectMapper();
List<BatchResult<Post>> batchResults = mapper.readValue(json, ref);

Using @JsonCreator? 使用@JsonCreator吗?

So when search for this exception I found the suggestion to use the @JsonCreator annotation with my Post constructor. 因此,当搜索此异常时,我发现了在Post构造函数中使用@JsonCreator批注的建议。 That however leads to a 然而,这导致

java.lang.IllegalArgumentException: Argument #0 of constructor [constructor for package.to.Post, annotations: {interface org.codehaus.jackson.annotate.JsonCreator=@org.codehaus.jackson.annotate.JsonCreator()}] has no property name annotation; must have name when multiple-paramater constructor annotated as Creator

The solution to this seems to be to annotate each of the POJO's properties individually and that's the point where I said: No thank you, certainly not! 解决方案似乎是分别注释POJO的每个属性,这就是我说的重点:不,谢谢,当然不会!

So the question remains: Is there any way to deserialize this mix maybe with an ObjectMapper setting? 因此问题仍然存在:是否可以通过ObjectMapper设置对这种混合进行反序列化?

Or maybe I can tell Jersey to filter the incoming formatted String and cut all the whitespaces before sending it to Jackson for deserialization? 或者,也许我可以告诉Jersey过滤传入的格式化String并剪切所有空格,然后再将其发送给Jackson进行反序列化?


My unsatisfying Workaround: 我不满意的解决方法:

When I tell my BatchResult class that body is a String , it works and I get a BatchResult with a String body . 当我告诉BatchResult类该body是一个String ,它可以工作,并且我得到一个带有String body的BatchResult。 Now using the ObjectMapper again on that String body with the Post.class type it deserializes correctly. 现在,在具有Post.class类型的String body上再次使用ObjectMapper ,可以正确地反序列化。 This is my current solution but it looks messy and it should not be my job to iterate over the deserialization results to deserialize its elements... Why can't Jackson figure this out on its own? 这是我当前的解决方案,但看起来很混乱,并且不应该在反序列化结果上进行迭代以反序列化其元素,这不是我的工作...为什么Jackson不能自己弄清楚这个问题?

Just Getting the Body as a Post 只是把身体作为一个职位

If you are not looking to deserialize the body, then you just need to give the Post object a constructor that takes type string: 如果您不希望反序列化主体,则只需为Post对象提供一个采用字符串类型的构造函数:

public class Post {
    public Post( String message ) {
      this.message = message;
    }
    private String message;
    // ...
}

Unmarshalling the Body into Post 解体到岗位

If you would instead like to populate the Post object with the JSON contained in the string, you can use a JsonDeserializer. 如果您想使用字符串中包含的JSON填充Post对象,则可以使用JsonDeserializer。 First, you will need to define a deserializer that reads in the string value and then unmarshalls it. 首先,您将需要定义一个反序列化器,该反序列化器将读取字符串值,然后将其解组。 Here is an example implementation: 这是一个示例实现:

import java.io.IOException;

import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.ObjectCodec;
import org.codehaus.jackson.map.BeanProperty;
import org.codehaus.jackson.map.ContextualDeserializer;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectReader;

public class EmbeddedJsonDeserializer
  extends JsonDeserializer<Object>
  implements ContextualDeserializer<Object>
{
  Class<?> type = null;
  public EmbeddedJsonDeserializer() {
  }

  public EmbeddedJsonDeserializer( Class<?> type ) {
    this.type = type;
  }

  @Override
  public Object deserialize(JsonParser parser, DeserializationContext context)
      throws IOException, JsonProcessingException {
    JsonToken curr = parser.getCurrentToken();
    if (curr == JsonToken.VALUE_STRING) {
      if( type == null ) return parser.getText();
      ObjectCodec codec = parser.getCodec();
      if( codec == null ) {
        return new ObjectMapper().readValue(parser.getText(), type);
      }
      else if( codec instanceof ObjectMapper ) {
        return ((ObjectMapper)codec).readValue(parser.getText(), type);
      }
      else if( codec instanceof ObjectReader ) {
        return ((ObjectReader)codec).withType(type).readValue(parser.getText());
      }
      else {
        return new ObjectMapper().readValue(parser.getText(), type);
      }
    }
    throw context.mappingException(type);
  }

  public JsonDeserializer<Object> createContextual(DeserializationConfig config, BeanProperty property) throws JsonMappingException {
    return new EmbeddedJsonDeserializer(property.getType().getRawClass());
  }
}

Then you need to add a @JsonDeserialize annotation to the body field, specifying the deserializer to use: 然后,您需要在主体字段中添加@JsonDeserialize批注,指定要使用的反序列化器:

public class BatchResult<T> {
  @JsonDeserialize(using=EmbeddedJsonDeserializer.class)
  private T body;
  ...

You should now be able to get embedded JSON into your Post objects. 现在,您应该能够将JSON嵌入到Post对象中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM