Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB: trying to read Long from JSON causes java.lang.Integer cannot be cast to java.lang.Long

I have a code that reads data in a specific format from MongoDB. I need to test it.

In order to do so, I create a JSON with the data I want to test:

{
  "id": ObjectId("57552e32e4b0839ede67e0af"),
  "serial" : 574000690,
  "startDate" : ISODate("2016-08-22T23:01:56.000Z"),
  "endDate" : ISODate("2016-10-22T22:01:56.000Z"),
  "reason": ""
}

This is the object that supposed to be created:

public static class MyObject implements Serializable{
    private String id;
    private long serial;
    private Date startDate;
    private Date endDate;
    private String reason;
}

I have a code that reads a JSON file and creates a Mongo document, and write to a DB:

List<Document> docs = dirAsDbObjects(dir + File.separator + 
subDir.getName()).collect(Collectors.toList());

docs.forEach(docManipulator);
docs.forEach(doc -> doc.putIfAbsent("_id", new ObjectId()));

ret.addAll(docs);

MongoDatabase db = mongoClient.getDatabase(dbName);
MongoCollection<Document> coll = db.getCollection(subDir.getName());

List<InsertOneModel<Document>> inserts = docs.stream().map(InsertOneModel::new).collect(Collectors.toList());
coll.bulkWrite(inserts);

Once the data is written, there's a code that tries to read the data from MongoDB and populate it into a MyObject instance:

    public MyObject(Document doc) {
        id = doc.getObjectId(DBConstants.ID).toString();
        serial = doc.getLong(DBConstants.SERIAL);
        startDate = doc.getDate("startDate");
        reason = doc.getString("source");
    }

Which fails on a error message:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long

Because of the line serial = doc.getLong(DBConstants.SERIAL);. It basically gets the number from my JSON as "Integer" thus unable to read it as long.

I've tried the following line, and it works:

Long.parseLong(doc.get(DBConstants.SERIAL).toString())

But is it the best solution here? Will .toString() always gets me the string representation of a number? Is there a way to keep a number in JSON that would cause it to be read as Long?

Update:

@glytching gave an excellent answer!

I also discovered another way, apparently if you wrap the numerical elemnt of JSON in NumberLong it will be parsed as Long when translated from Mongo document.

So using the old code, I was able to make it work by changing my JSON like so:

{
  "id": ObjectId("57552e32e4b0839ede67e0af"),
  "serial" : NumberLong(574000690),
  "startDate" : ISODate("2016-08-22T23:01:56.000Z"),
  "endDate" : ISODate("2016-10-22T22:01:56.000Z"),
  "reason": ""
}
like image 651
AlexM Avatar asked Nov 05 '17 12:11

AlexM


1 Answers

The Mongo Java driver has determined that the value of serial can 'fit' in an INT32 so it treats it as such. When you invoke doc.getLong() you are asking the driver to cast its Integer to a Long, hence the class cast exception. If, for example, the value of serial was 2147483648 (i.e. the max integer value + 1) then the Mongo Java driver would deem that to be an INT64 and you could then safely invoke doc.getLong().

So, since (a) you have modelled this attribute as a Long in your class model and (b) not every persisted value of this attribute requires storage as an INT64 ... you have to be sensitive to its persisted type when converting it into a Long.

How? Well, as long as the serial attribute is persisted as some sort of number (e.g. INT32, INT64) then this call ...

doc.get(DBConstants.SERIAL)

... will always return an object which is some subclass of java.lang.Number and therefore casting to Number and using longValue() will work.

For example:

serial = ((Number) doc.get(DBConstants.SERIAL)).longValue()
like image 54
glytching Avatar answered Nov 07 '22 17:11

glytching