7

I am accessing Mongo database from Clojure using Monger library. One thing that annoys me is switching back and forth between ObjectId instances and strings.

For example, the this code (mc/find-maps "posts" {}) will evaluate to maps with the value of _id entry set to instances of ObjectId class, while in my app I find it more useful to simply have it as a string for which I know that it is unique.

On the other hand for expressions like: (mc/find-map-by-id "posts" (new ObjectId id)) where I do use a String object for the id parameter, I have to use it to construct an instance of ObjectId.

Is there a way to make the values of _id convert between Strings in the application and ObjectId on the mongo side automatically and transparently? Some kind of option that, when enabled, creates maps with string representations of ids, and vice versa converts string representations of ids t object ids when used as parameters in queries?

If not, what other strategies are available?

Goran Jovic
  • 9,418
  • 3
  • 43
  • 75

2 Answers2

1

I share you pain on this point. Getting back ObjectID's is annoying, because you are always having to convert back and forth and if you forget then it's a hard to catch bug.

Your best bet may be to wrap the driver code to do the conversions automatically. So replace find, findOne, insert, with a thin wrapper that looks at the type and makes the conversions automatically whether you are coming in or out.

Unfortunately, I don't think there is an easier way.

Brad C
  • 720
  • 8
  • 14
0

I am a bit late to the party, but have to mention for the future generations.

I wasn't looking to solve the automatic str->ObjectID part due to the complexity of getting the code to understand which string must be treated as ObjectID and which must not. But for the ObjectID->str half there is a solution.

I ended up extending the monger.conversion/ConvertFromDBObject protocol like this:

(extend-protocol ConvertFromDBObject
    ObjectId
    (from-db-object [input keywordize]
      (str input)))

from-db-object gets called on all read as map operations, so this solves the reading part. As for getting ObjectIDs converted to string for the results of inserts and updates I haven't found any elegant solution and extended that same protocol for maps and invoked from-db-object on the results of insert and update operations in my wrapper functions. Extending the protocol for maps looks like this (fully stolen from the original implementation for DBObject:

(extend-protocol ConvertFromDBObject
    ; copied over from the implementation for DBObject
    clojure.lang.IPersistentMap
    (from-db-object [input keywordize]
      (reduce (if keywordize
                (fn [m ^String k]
                  (assoc m (keyword k) (from-db-object (.get input k) true)))
                (fn [m ^String k]
                  (assoc m k (from-db-object (.get input k) false))))
              {} (.keySet input))))
Alex Turok
  • 576
  • 3
  • 11