I've written a simple example project called bson-example-java which attempts to demonstrate how to simply serialize and deserialize object trees in Java using Jackson with bson4jackson.
Unfortunately, it seems that there is a bug in bson4jackson's implementation of reading back binary values.
Serialization
I'm serializing org.bson.ObjectId
objects using the following serializer:
@Override public void serialize (ObjectId value, JsonGenerator jgen,
SerializerProvider provider) throws IOException, JsonGenerationException {
byte[] bytes = value.toByteArray();
logger.debug("Writing {]-byte binary ObjectId: {}, hex: {}", bytes.length, bytes,
encodeHexString(bytes));
jgen.writeBinary(bytes);
}
When I run my example, I can see from the output that it's doing things properly:
11:21:00.993 [main] DEBUG BinaryObjectIdSerializer - Writing 12-byte binary ObjectId: [83, -110, 6, -116, -28, -80, 103, 125, 59, -2, -110, -50], hex: 5392068ce4b0677d3bfe92ce
11:21:01.009 [main] DEBUG BinaryObjectIdSerializer - Writing 12-byte binary ObjectId: [83, -110, 6, -116, -28, -80, 103, 125, 59, -2, -110, -52], hex: 5392068ce4b0677d3bfe92cc
11:21:01.009 [main] DEBUG BinaryObjectIdSerializer - Writing 12-byte binary ObjectId: [83, -110, 6, -116, -28, -80, 103, 125, 59, -2, -110, -51], hex: 5392068ce4b0677d3bfe92cd
I can also use bsondump
to see my generated BSON tree and everything looks right:
$ bsondump /tmp/example.bson
{
"_id" : { "$binary" : "U5IGjOSwZ307/pLO", "$type" : "00" },
"title" : "The Beginning of Everything",
"date" : Date( 0 ),
"tags" : [
{ "name" : "beginning", "_id" : { "$binary" : "U5IGjOSwZ307/pLM", "$type" : "00" } },
{ "name" : "first", "_id" : { "$binary" : "U5IGjOSwZ307/pLN", "$type" : "00" } }
],
"body" : "Wow, such much is wow yes!"
}
Un base64-ing the binary id values and converting to hexadecimal yields:
5392068ce4b0577d3bfe92ce
5392068ce4b0677d3bfe92cc
5392068ce4b0677d3bfe92cd
Therefore, I've determined that the serialization is working properly and that it's only the deserialization that we encounter a problem.
Deserialization
The bug is experienced when attempting to read back the binary data into memory during deserialization. Instead of receiving back 12 bytes, I get either 10 or 11 bytes (it seems that depending on the value of the bytes, I get 10 or 11 bytes... weird). Here is my deserializer:
@Override public ObjectId deserialize (JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
byte[] value = jp.getBinaryValue();
logger.debug("Read {}-byte ObjectId: {}; hex: {}", value.length, value,
encodeHexString(value));
return new ObjectId(value);
}
Here's the output from my logs:
11:49:43.397 [main] DEBUG BinaryObjectIdDeserializer - Read 11-byte ObjectId: [91, 66, 64, 49, 52, 53, 52, 54, 50, 98, 99]; hex: 5b42403134353436326263
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: need 12 bytes (through reference chain: org.tkassembled.example.bson.data.Article["_id"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:232)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:197)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1420)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:244)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:118)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1270)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:865)
at org.tkassembled.example.bson.BSONReaderApplication.main(BSONReaderApplication.java:42)
Caused by: java.lang.IllegalArgumentException: need 12 bytes
at org.bson.types.ObjectId.<init>(ObjectId.java:203)
at org.tkassembled.example.bson.deserialize.BinaryObjectIdDeserializer.deserialize(BinaryObjectIdDeserializer.java:35)
at org.tkassembled.example.bson.deserialize.BinaryObjectIdDeserializer.deserialize(BinaryObjectIdDeserializer.java:1)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:525)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:106)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:242)
... 4 more
As is noted, it returns an entirely different ObjectId, and only 11 bytes of data. If I instead change my deserializer to do byte[] value = (byte[]) jp.getEmbeddedObject();
, I actually do get the ObjectId properly:
11:52:06.714 [main] DEBUG BinaryObjectIdDeserializer - Read 12-byte ObjectId: [83, -110, 6, -116, -28, -80, 103, 125, 59, -2, -110, -50]; hex: 5392068ce4b0677d3bfe92ce
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.Date out of VALUE_EMBEDDED_OBJECT token
at [Source: de.undercouch.bson4jackson.io.LittleEndianInputStream@5a4bb836; pos: 71] (through reference chain: org.tkassembled.example.bson.data.Article["date"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:691)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:626)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:166)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:260)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:241)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:525)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:99)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:242)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:118)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1270)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:865)
at org.tkassembled.example.bson.BSONReaderApplication.main(BSONReaderApplication.java:42)
However, it seems to cause problems with reading the next field, and breaks the deserialization of the rest of the tree.
Therefore, it seems clear that there's a bug in the readBinaryValue
logic present in bson4jackson.