Wednesday, June 20, 2012

The In and Outs of MongoDB and Jackson


MongoDB has been a great NoSQL solution to work with so far but, there are a few gotchas that I found needed some exploring to get sorted out properly for a Mongo noob.

I have been working with RESTful services for the past couple years but to date, my NoSQL solution has primarily been Membase. Membase is good at what it does but, it is a bit sparse on features compared to the RDBMs that I am accustomed to working with. No, don't worry I am not about to jump into a debate of NoSQL vs RDBMs, I use my soldering iron to solder and my multi-meter to measure volts, each tool for its job! On the matter of Membase vs MongoDB though I have to say that I have quicky developed a strong preference for Mongo. This new appreciation comes from the field indexing and the ability to use regex to search for specific data. Perhaps this is just me finding some similarities between RDBM and Mongo? Sure that and living with out those features in the past has simply resulted in writing code for things that are usually standard issue.

In my current project I am using MongoDB with Java JAX-RS and Jackson. So far things have fit together quite nicely though I did come across a few issues that I had a hard time tracking down detailed information about. I had to just puzzle out these issues and thought someone might find the info useful, not to mention I can look them up when I inevitably forget them in 6 months once all that information is replaced with whatever comes next.

One of those things I love about a MySQL db driver is that it thoughtfully returns the ID for a newly created record so you can easily access it for further use or abuse. MongoDB also does this but, it wasn't at all obvious how the Java driver was returning this value. At first I was doing a query back to the collection to grab the new _id but, this method left me just feeling less then confident that things would always turn out right. What I didn't realize was the Java driver was thoughtfully returning the value by inserting it in the BasicDBObject used to create the insert. I felt a little silly not thinking to look there first but.. well it just wasn't what I was expecting. Here's the code to illustrate.


BasicDBObject dbObject = MongoModelFactory.buildDBObject(model);
DBCollection dbColl = getCollection(collectionName.getValue());
WriteResult result = dbColl.insert(dbObject);

//The _id values is populated in the dbObject after the insert.
dbObject.get("_id");

Related to getting the id comes a Jackson issue. Because the _id is not stored as a String, but as an ObjectId which makes deserializing the value with the Jackson ObjectMapper a bit on the complicated side. The trick here is to create a MongoID object which contains a JsonCreator that knows how to construct the _id.$oid value from the JSON. This was by far the simplest way to use Jackson to get this information out and the object is reusable across all of my DataObjects.  Here's the code:

public class MongoID {
  private String $oid;

  public MongoID(){  
  }
 
  public String get$oid() {
    return $oid;
  }
 
  public void set$oid(String $oid) {
    this.$oid = $oid;
  }
 
  @JsonCreator
  public static String fromJSON(String val) 
      throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();
    MongoID a = mapper.readValue(val,MongoID.class);
    return a.get$oid();
  }
}

The above class is utilized in my BaseModel abstract object to provide a hint to the Jackson ObjectMapper as to what field this is basically by providing a fromJSON method for Mongo's ObjectId . 

The last challenge I ran into was also a Jackson related issue, specifically in serializing an object to JSON and back again. Jackson doesn't like really complicated JSON lists and due to class erasure it has a hard time figuring out what sort of objects are contained in a list. Such a case is simular to the following JSON:

{
    "_id": "4fc151c9ebb11be7d1ae4905",
    "mapPreferences": {
        "bgColor": "#fff",
        "gridColor": "#000"
    },
    "maps": [
        {
            "cols": 30,
            "title": "TestMap",
            "rows": 30
        },
        {
            "cols": 32,
            "title": "TestMap2",
            "rows": 32
        }
    ]
}

Jackson doesn't really have issues with a list of strings but try a list of objects or a multi-dimensional array and Jackson is stumped as to what to do because of  alack of information.  The JsonCreator again provides a solution to this issue! In the above example we have a field called "maps" which contains an array of MapData Objects, with out providing a JsonCreator, Jackson won't know what object is contained in the list and can't instantiate a list with objects of that type. To solve this List of unknown objects issue I have written the folloing code in the MapData Object :

@JsonCreator
  public AdventureData(@JsonProperty("adventureId") String advId, 
          @JsonProperty("mapPreferences") MapPreferences mapPrefs,
       @JsonProperty("maps") Object maps) 
         throws JsonParseException, JsonMappingException, 
                IOException
  {
    this.adventureId = advId;
    this.mapPreferences = mapPrefs;
   
    ObjectMapper mapper = new ObjectMapper();
    String mapsString = mapper.writeValueAsString(maps);
    this.maps = mapper.readValue(mapsString, 
        TypeFactory.collectionType(List.class, MapData.class));
  }

In the constructor above the parameters define the objects but in the case of the "maps" field  it is left to be an Object because it will be deseralized separately since casting to a List is obfuscated by Type Erasure. To construct the List of MapData objects I then used the ObjectMapper to convert the list back to a JSON String then deserialize it! To make it clear to the ObjectMapper what sort of object is in the List I use the TypeFactory to indicate that we have a Collection of the List type here and it contains MapData.  That's about all there is too that. You will need to give the MapData object it's own JsonCreator similar to the one above but that's all you have to do. If only it had been that easy to find a sample on how this is done! I will say that if you know of another way to make this happen please let me know because the Object to string conversion feels a little bit like a Hack though it works perfectly.

I hope this helps you out when your working with Mongo and Jackson for the first time! (After reviewing this post I have come to realize that I will need to describe my generics based DAO that I am using for MongoDB to better explain what I'm doing with the _id value. Look for it in the future!)