简体   繁体   English

MongoDB ObjectId 的 Jackson JSON 反序列化

[英]Jackson JSON Deserialization of MongoDB ObjectId

Ok, so first off here's the JSON that's returning from my web service.好的,首先这里是从我的 Web 服务返回的 JSON。 I'm trying to deserialize it into pojos after an asynchronous query in a ResponseHandler in my Android ContentProvider.在我的 Android ContentProvider 的 ResponseHandler 中进行异步查询后,我试图将其反序列化为 pojo。

{"exampleList" : [{
"locationId" : "00001" , 
"owners" : [ 
  { 
    "paidID" : { "$oid" : "50a9c951300493f64fbffdb6"} , 
    "userID" : { "$oid" : "50a9c951300493f64fbffdb6"}
  } , 
  { 
    "paidID" : { "$oid" : "50a9c951300493f64fbffdb7"} , 
    "userID" : { "$oid" : "50a9c951300493f64fbffdb7"}
  } 
]
}]}

At first, I was confused about the problem I was seeing, since I use the same Jackson-annotated beans for my web service as I do in my Android app--but then I realized that the owners object was never getting sent in the sample JSON to my web service (it skips the POJOs on my web service and gets added into the documents in mongoDB through atomic updates from the DAO).起初,我对我看到的问题感到困惑,因为我在我的 Web 服务中使用了与我在 Android 应用程序中相同的 Jackson 注释的 bean——但后来我意识到owners对象从未被发送到示例中JSON 到我的 Web 服务(它跳过我的 Web 服务上的 POJO,并通过来自 DAO 的原子更新添加到 mongoDB 中的文档中)。

So OK.那么好吧。 Up to now, Jackson wasn't having to handle the owners object, and now that it is it is choking on it, namely:到目前为止,Jackson 不必处理owners对象,现在它正在窒息它,即:

JsonMappingException : Can not deserialize instance of java.lang.String out of START_OBJECT token at [char position where you can find "userID" and "paidID" ] through reference chain [path to my Jackson bean which contains the owners class] JsonMappingException :无法通过引用链 [包含所有者类的我的 Jackson bean 的路径] 在 [char position where you can find "userID" and "paidID" ] 处的START_OBJECT令牌中反序列化java.lang.String实例

My Jackson bean has a wrapper, which is what that "exampleList" is all about:我的 Jackson bean 有一个包装器,这就是“exampleList”的全部内容:

public class Examples extends HashMap<String, ArrayList<Example>> {

}

And then the actual Example class:然后是实际的Example类:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Example implements Comparable<Example> {

@ObjectId @Id
private String id;

@JsonProperty(Constants.Example.location)
private String location;

@JsonProperty(Constants.Example.OWNERS)
private List<Owners> owners;

public int compareTo(Example _o) {
    return getId().compareTo(_o.getId());
}

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public String getLocation() {
    return location;
}

public void setLocation(String location) {
    this.location = location;
}
public List<Example.Owners> getOwners() {
    return owners;
}

public void setOwners(List<Example.Owners> owners) {
    this.owners = owners;
}

public Example() {
}

@JsonCreator
public Example(@Id @ObjectId String id) {
    this.id = id;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Owners implements Comparable<Owners> {

    @JsonProperty(Constants.Example.USERID)
    private String userID;

    @JsonProperty(Constants.Example.PAIDID)
    private String paidID;

    public Owners() {
    }

    public int compareTo(Owners _o) {
        return getUserID().compareTo(_o.getUserID());
    }

    @ObjectId
    public String getUserID() {
        return userID;
    }

    @ObjectId
    public void setUserID(String userID) {
        this.userID = userID;
    }

    @ObjectId
    public String getPaidID() {
        return paidID;
    }

    @ObjectId
    public void setPaidID(String paidID) {
        this.paidID = paidID;
    }

}

}

And finally, the code in the ResponseHandler where this is all failing (the 2nd line produces the JsonMappingException):最后,ResponseHandler 中的代码全部失败(第 2 行产生 JsonMappingException):

objectMapper = MongoJacksonMapperModule.configure(objectMapper);    
mExamples = objectMapper.readValue(jsonParser, Examples.class);

I have a feeling the issue is that Jackson still doesn't know how to map those $oid , which are the mongoDB ObjectIds.我有一种感觉,问题是 Jackson 仍然不知道如何映射那些$oid ,它们是 mongoDB ObjectId。 The MongoJacksonMapper library is supposed to help that by providing the @ObjectId annotation and a way to configure the ObjectMapper to use that library, but it still isn't working. MongoJacksonMapper 库应该通过提供@ObjectId注释和一种配置 ObjectMapper 以使用该库的方法来帮助实现这一点,但它仍然无法正常工作。 For some reason, it's still looking for the userID or paidID as a String, not an ObjectId.出于某种原因,它仍在寻找用户 ID 或付费 ID 作为字符串,而不是 ObjectId。 Any ideas?有任何想法吗?

Another alternative is com.fasterxml.jackson.databind.ser.std.ToStringSerializer .另一种选择是com.fasterxml.jackson.databind.ser.std.ToStringSerializer

@Id
@JsonSerialize(using = ToStringSerializer.class)
private final ObjectId id;

This will result in:这将导致:

{
  "id": "5489f420c8306b6ac8d33897"
}

For future users: Use a custom jackson deserializer to convert $oid back to ObjectId.对于未来的用户:使用自定义 jackson 反序列化器将 $oid 转换回 ObjectId。

public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {

    @Override
    public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode oid = ((JsonNode)p.readValueAsTree()).get("$oid");
        return new ObjectId(oid.asText());
    }

}

How to use:如何使用:

ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
        
mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
mapper.registerModule(mod);

YourClass obj = mapper.readValue("{your json with $oid}", YourClass.class);

My code had at least two problems that were pretty tough to track down answers to online, so I'll make sure to link here.我的代码至少有两个问题很难在网上找到答案,所以我会确保链接到这里。 Basically, child classes need a constructor in the parent class that calls Jackson's readValue() to map the child.基本上,子类需要在父类中调用 Jackson 的 readValue() 来映射子类的构造函数。 As far as mongoDB $oid's go, you should create a separate MongoId class to represent these mongo objects, and follow a similar pattern as with the child class to map the data when it comes in for deserialization.就 mongoDB $oid 而言,您应该创建一个单独的 MongoId 类来表示这些 mongo 对象,并遵循与子类类似的模式来映射数据,以便进行反序列化。 Here's a blog post I found that describes this well and provides some examples. 这是我发现的一篇博客文章,它很好地描述了这一点并提供了一些示例。

Jackson does not know how to serialize an ObjectId. Jackson 不知道如何序列化 ObjectId。 I tweaked Arny's code to serialize any ObjectId and provide this working example:我调整了 Arny 的代码以序列化任何 ObjectId 并提供以下工作示例:

public class SerialiserTest {

 private ObjectMapper mapper = new ObjectMapper();

 public static class T {
    private ObjectId objectId;

    public ObjectId getObjectId() {
        return objectId;
    }

    public void setObjectId(ObjectId objectId) {
        this.objectId = objectId;
    }
 }

 @Test
 public final void serDeser() throws IOException {
    T t = new T();
    t.setObjectId(new ObjectId());
    List<T> ls = Collections.singletonList(t);
    String json = mapper.writeValueAsString(ls);
    System.out.println(json);
    SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
    mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
    mapper.registerModule(mod);
    JavaType type = mapper.getTypeFactory().
            constructCollectionType(List.class, T.class);
    List<?> l  = mapper.readValue(json, type);
    System.out.println(l);
 }
}

public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {

 @Override
 public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    JsonNode n = (JsonNode)p.readValueAsTree();
    return new ObjectId(n.get("timestamp").asInt(), n.get("machineIdentifier").asInt(), (short) n.get("processIdentifier").asInt(), n.get("counter").asInt());
 }

}

There's an even easier way documented here which was a lifesaver for me.这里记录了一种更简单的方法这对我来说是一个救星。 Now you can use the ObjectId in Java but when you go to/from JSON it'll be a String.现在您可以在 Java 中使用 ObjectId,但是当您访问/从 JSON 访问时,它将是一个字符串。

public class ObjectIdJsonSerializer extends JsonSerializer<ObjectId> {
    @Override
    public void serialize(ObjectId o, JsonGenerator j, SerializerProvider s) throws IOException, JsonProcessingException {
        if(o == null) {
            j.writeNull();
        } else {
            j.writeString(o.toString());
        }
    }
}

And then in your beans:然后在你的豆子里:

@JsonSerialize(using=ObjectIdJsonSerializer.class)
private ObjectId id;

I did it like this:我是这样做的:

@Configuration
public class SpringWebFluxConfig {

    @Bean
    @Primary
    ObjectMapper objectMapper() {
      Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
      builder.serializerByType(ObjectId.class, new ToStringSerializer());
      builder.deserializerByType(ObjectId.class, new JsonDeserializer() {
        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt) 
            throws IOException {
          Map oid = p.readValueAs(Map.class);
          return new ObjectId(
              (Integer) oid.get("timestamp"),
              (Integer) oid.get("machineIdentifier"),
              ((Integer) oid.get("processIdentifier")).shortValue(),
              (Integer) oid.get("counter"));
        }
      });
      return builder.build();
    }
}

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

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