MongoDB, Clojure, JSON and PHP – CongoMongo issues and solutions

I’m currently working on a project where I use MongoDB and Clojure to create an API only type of service. Other sites of ours are connecting to this service, these sites are all made with PHP at the moment.

Some issues have cropped up during development. The first issue has to do with how CongoMongo currently outputs json. The structure fetch returns looks like this: (“{…}” “{…}” … ). It’s a list of strings where each string is a json encoded map. What I really want to output so that I can use PHP’s json_decode on it is: “[{…}, {…} … ]”.

So why not use :as :clojure then and then contrib.json’s json-str on the Clojure map you might think? I did too and it worked as long as only objects with custom _ids were handled. Encoding objects with default ids resulted in a whining json-str.

So I opted to create a function that converts the above mentioned list of strings to the also above mentioned complete json string with an array of objects, separated by commas:

(defn fix-str [lst]
  (str "[" (string/chop (apply str (map (fn [el] (str el ",")) lst))) "]" ))

(defn get-all [col & [where]]
  (fix-str
   (fetch col
          :where (or where {})
          :as :json)))

The result is fix-str above, note how we use str-utils2’s chop to get rid of the last surplus comma. Note that there might be more elegant/faster solutions than fix-str, I’m still a Clojure beginner.

Anyway, the above works and now we have objects looking like this in json: {“_id”: {“$oid”: “4d1d84fd28b4506ed4855551”}, … }. Yep you guessed it, that $oid there is going to be a bitch when we want to access the id in PHP: $obj->_id->$oid (ouch).

The solution though is very simple, we use the fact that we can coerce an object into an array:

function getMongoId($obj){
	$arr = (array) $obj->_id;
	return $arr['$oid'];
}

Im grateful that json_decode somehow manages to decode without breaking down and crying, otherwise we would have to create something that would massage the way the id looks on the Clojure side into something more palatable on the PHP side.

Note that the premise for the above is that there is no easy way out that I might have missed, some :as :nice-json flag to CongoMongo or some way of making json-str handle the id objects.

Update: Due to various problems when running more complex queries I’ve had to implement fixing the object ids in Clojure results. Reason being I need a “clean” composite Clojure structure where I can’t have JSON strings as members as they would be doubly encoded when using json-str. With this in place I could retire the whole fix-str way of doing things if it wasn’t for the fact that it probably runs faster when fetching big collections, that’s why I’m keeping it in addition to the new functionality, like this:

(defn fix-id [hsh]
  (assoc hsh :_id (str (hsh :_id))))

(defn fix-ids [lst]
  (let [f-el (first lst)]
    (if (or (not (f-el :_id)) (string? (f-el :_id)))
     lst
     (map fix-id lst))))

(defn fix-stuff [lst]
  (when (seq lst)
    (if (string? (first lst))
     (str "[" (string/chop (apply str (map (fn [el] (str el ",")) lst))) "]" )
     (fix-ids lst))))

(defn get-all [col & [where as only]]
  (fix-stuff
   (fetch col
          :where (or where {})
          :only (or only [])
          :as (or as :json))))

Key here is the assoc and str combo to convert any objects to strings.

The PHP function needs a slight change, it now looks like this:

function mongoId($obj){
	if(is_string($obj->_id))
		return $obj->_id;
		
	$arr = (array) $obj->_id;
	return $arr['$oid'];
}

Related Posts

Tags: , , , ,