16

I'm saving an object with a java.util.Date field into a MongoDB 3.2 instance.

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(myObject);
collection.insertOne(Document.parse(json));

the String contains:

"captured": 1454549266735

then I read it from the MongoDB instance:

    final Document document = collection.find(eq("key", value)).first();
    final String json = document.toJson();
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    xx = mapper.readValue(json, MyClass.class);

the deserialization fails:

java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.Date out of START_OBJECT token

I see that the json string created by "document.toJson()" contains:

"captured": {
    "$numberLong": "1454550216318"
}

instead of what was there originally ("captured": 1454549266735) MongoDB docs say they started using "MongoDB Extended Json". I tried both Jackson 1 and 2 to parse it - no luck.

what is the easiest way to convert those Document objects provided by MongoDB 3 to Java POJOs? maybe I can skip toJson() step altogether?

I tried mongojack - that one does not support MongoDB3.

Looked at couple other POJO mappers listed on MongoDB docs page - they all require putting their custom annotations to Java classes.

4 Answers 4

17

You should define and use custom JsonWriterSettings to fine-tune JSON generation:

 JsonWriterSettings settings = JsonWriterSettings.builder()
         .int64Converter((value, writer) -> writer.writeNumber(value.toString()))
         .build();

 String json = new Document("a", 12).append("b", 14L).toJson(settings);

Will produce:

 { "a" : 12, "b" : 14 }

If you will not use custom settings then document will produce extended json:

 { "a" : 12, "b" : { "$numberLong" : "14" } }
Sign up to request clarification or add additional context in comments.

3 Comments

A potential problem with this approach is that large values of longs can lose precision. Some JSON implementations use only doubles. These have a 52 bit mantissa, which is 12 fewer bits than a long. Luckily, epochal time in milliseconds currently only needs 31 bits so we'll get away with this in many cases (for 143,000 years) but beware!
Now, it is simpler with JsonMode.RELAXED: spec
How you resolved this - I am getting error - Type Mismatch required Converter[Long] Actual [(Any,Any), Nothing]. Cannot resolve symbol value.
4

This looks like Mongo Java driver bug, where Document.toJson profuces non-standard JSON even if JsonMode.STRICT is used. This problem is described in the following bug https://jira.mongodb.org/browse/JAVA-2173 for which I encourage you to vote.

A workaround is to use com.mongodb.util.JSON.serialize(document).

1 Comment

com.mongodb.util.JSON.serialize(document) was deprecated in new versions
2

I save a tag with my mongo document that specifies the original type of the object stored. I then use Gson to parse it with the name of that type. First, to create the stored Document

private static Gson gson = new Gson();

public static Document ConvertToDocument(Object rd) {
    if (rd instanceof Document)
        return (Document)rd;
    String json = gson.toJson(rd);
    Document doc = Document.parse(json); 
    doc.append(TYPE_FIELD, rd.getClass().getName());
    return doc;
}

then to read the document back into the Java,

public static Object ConvertFromDocument(Document doc) throws CAAException {
    String clazzName = doc.getString(TYPE_FIELD);
    if (clazzName == null)
        throw new RuntimeException("Document was not stored in the DB or got stored without becing created by itemToStoredDocument()");
    Class<?> clazz;
    try {
        clazz = (Class<?>) Class.forName(clazzName);
    } catch (ClassNotFoundException e) {
        throw new CAAException("Could not load class " + clazzName, e);
    }

    json = com.mongodb.util.JSON.serialize(doc);
    return gson.fromJson(json, clazz);
}

Thanks to Aleksey for pointing out JSON.serialize().

1 Comment

Isn't this gonna impact performance? I mean, you serialize a document to a string and then the same string is deserialized into the relevant POJO. That sounds very expensive to me...
0

It looks like you are using Date object inside "myObject". In that case, you should use a DateSerializer that implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> and then register it with GsonBuilder. Sample code follows:

public class My_DateSerializer implements JsonSerializer<LocalDate>,
                                                          JsonDeserializer<LocalDate> {

@Override
public LocalDate deserialize(JsonElement json, Type typeOfT,
                        JsonDeserializationContext context) throws JsonParseException {
    final String dateAsString = json.getAsString();
    final DateTimeFormatter dtf = DateTimeFormat.forPattern(DATE_FORMAT);
    if (dateAsString.length() == 0)
    {
        return null;
    }
    else
    {
        return dtf.parseLocalDate(dateAsString);
    }
}

@Override
public JsonElement serialize(LocalDate src, Type typeOfSrc,
                                                     JsonSerializationContext context) {
    String retVal;
    final DateTimeFormatter dtf = DateTimeFormat.forPattern(DATE_FORMAT);
    if (src == null)
    {
        retVal = "";
    }
    else
    {
        retVal = dtf.print(src);
    }
    return new JsonPrimitive(retVal);
}
}

Now register it with GsonBuilder:

final GsonBuilder builder = new GsonBuilder()
           .registerTypeAdapter(LocalDate.class, new My_DateSerializer());      
final Gson gson = builder.create();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.