Practical encounter with defprotocol in Clojure for JSONising MongoDB’s ObjectId

I’ve seen examples that make use of defprotocol in Clojure, but have never tried it out myself. Until now.

I’m working on the librarian-clojure project which aims at offering a book manager in Clojure. It’s more for learning Clojure while developing something potentially useful. I managed to encourage Konrad Garus to join the project and I believe we’re doing great together. My dream came true – I’ve got a Clojure project with a teammate.

The project uses many solutions that I’ve been only theoretically aware of. Quite recently jQuery joined the club as we needed to send JSON data between a browser and a server.

After a couple of days reading the book jQuery Recipes from Apress I felt ready to give jQuery a try. I knew that MongoDB, which we use in the project, can return JSON data, so I thought it’s a matter of passing it along to the browser. I couldn’t have been more wrong.

It turned out that the following simple query

(with-mongo db
  (fetch :books :where query))

returns a JSON-like structure, e.g. a list of JSON items:

({:_id #<ObjectId 4f4e5a7726b228abdf05cf93>, :author Jacek, :title Tytul} {:_id 0, :author Mickiewicz, :title Konrad Wallenrod})

As you might’ve noticed, it’s not an array, but a list so clojure.data.json/json-str spat the following exception:

java.lang.Exception: Don't know how to write JSON of class org.bson.types.ObjectId
     at clojure.data.json$write_json_generic.invoke(json.clj:276)
     at clojure.data.json$fn__447$G__442__456.invoke(json.clj:197)
     at clojure.data.json$write_json_object.invoke(json.clj:242)
     at clojure.data.json$fn__447$G__442__456.invoke(json.clj:197)
     at clojure.data.json$write_json_array.invoke(json.clj:255)
     at clojure.data.json$fn__447$G__442__456.invoke(json.clj:197)
     at clojure.data.json$json_str.doInvoke(json.clj:316)
     at clojure.lang.RestFn.invoke(RestFn.java:410)
     at librarian_clojure.books$get_books_json.invoke(books.clj:58)
     ...

Having looked at the source code of clojure.data.json/json-str I noticed that it uses write-json that’s part of the Write-JSON protocol. When I looked at the series of extend’s I knew I’d have to extend the protocol to handle MongoDB’s org.bson.types.ObjectId.

With the following functions I’m now able to use json-str without a problem.

(defn- write-json-mongodb-objectid [x out escape-unicode?]
  (json/write-json (str x) out escape-unicode?))

(extend org.bson.types.ObjectId json/Write-JSON
  {:write-json write-json-mongodb-objectid})

The output is now as follows:

[{"_id":"4f4e5a7726b228abdf05cf93","author":"Jacek","title":"Tytul"},{"_id":0,"author":"Mickiewicz","title":"Konrad Wallenrod"}]

http://jsonlint.com/ says it’s a valid JSON so I’m glad I could sort it out at one sit.

I’ve always been said that to truly master a language one needs to stay with it for entire days. It’s much easier when you’ve got a project and you’re paid for doing it, but without a paid project, you’re not doomed at all. You simply have to start your own. And I did and thanks to Konrad I’m having so much fun as I’m learning Clojure and the auxiliary solutions. Life couldn’t have been better!

Be Sociable, Share!
This entry was posted in Frameworks, Languages.

2 Responses to Practical encounter with defprotocol in Clojure for JSONising MongoDB’s ObjectId

  1. Jason Hickner (@jhickner) says:

    FYI – If you’re using congomongo you can just (fetch :books :where query :as :json) and get json back instead of clojure data structures.

  2. Mike Bridge says:

    I think the internals to clojure.data.json have changed. Stuart Sierra (http://dev.clojure.org/jira/browse/DJSON-4) says the forward-compatible way to do it is to add a :value-fn to the write-str call, e.g.:

    (defn json-writestr
    [s]
    (json/write-str s :value-fn (fn [k v] (if (instance? ObjectId v) (str v) v))))

    … though I agree that a protocol is much cleaner.

Leave a Reply

%d bloggers like this: