marcello3d/node-mongolian — Issue #91: JSON->Mongo Dot-Notation Selector (working code attached)
Author: jdarling
Created: 2012-03-06 20:21:26+00:00
State: open

I ran into this on my recent project where we basically passed in a JSON Object as the Selector to Find/Upsert operations. Of course, MongoDB likes Dot-Notation. Typically flattening is easy, except for those pesky $ specials that Mongo uses to make it so powerful in these scenarios.

So, I wrote a quick routine that takes a JSON object and flattens it taking into account the special $ keys.

Sorry if this is the wrong way to put this up for all, but I don't really know the right place to put it in the code to suggest a patch. It isn't really a core thing, and is more of a utils type thing.

Anyways, in case it could be of use to others here it is:

var testOn = {
a: 10,
b: 'hello',
c: {$gt: 10},
d: {
e: 'e value',
f: 'f value'
},
ary: [1, 2, 3, {a: 'b', b: 'c'}],
$where: "this.a == this.b",
$addToSet: {a: 1},
$pushAll: {a: [1, 2]},
$addToSet: {a: {$each: [1, 2]}},
$or: [{a: 1}, {b: 1}]
};

var unrollJSONSelector = function(selector){
var isPlainObject = function(obj) {
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
return false;
var has_own_constructor = hasOwnProperty.call(obj, 'constructor');
var has_is_property_of_method = hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf');
if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
return false;
var last_key;
for (key in obj)
last_key = key;
return typeof last_key === 'undefined' || hasOwnProperty.call(obj, last_key);
};
var _process = function(node, result, path){
var name, value, isObject, isArray, isSpecial, route, index, container;
if(path!=='') route = path+'.';
else route = '';
for(name in node){
value = node[name];
isObject = isPlainObject(value);
isArray = Array.isArray(value);
isSpecial = name.substr(0, 1)=='$';
if(isSpecial){
if(isObject){
container = result[name] = {};
for(index in value){
isObject = isPlainObject(value[index]);
isArray = Array.isArray(value[index]);
if(isObject) _process(value[index], container[index]={}, '');
else if(isArray) _process(value[index], container[index]=[], '');
else container[index] = value[index];
}
}else if(isArray){
container = result[name] = [];
for(index in value){
isObject = isPlainObject(value[index]);
isArray = Array.isArray(value[index]);
if(isObject||isArray) container.push(_process(value[index], {}, ''));
else container.push(value[index]);
}
}else{
if(path){
container = result[path] = {};
container[name] = value;
}else result[name] = value;
}
}else if(isArray){
container = result[route+name] = [];
for(index in value){
isObject = isPlainObject(value[index]);
isArray = Array.isArray(value[index]);
if(isObject||isArray) container.push(_process(value[index], {}, ''));
else container.push(value[index]);
}
}else if(isObject){
_process(value, result, route+name);
}else{
result[route+name] = value;
}
}
return result;
};

return _process(selector, {}, '');
};

console.log(testOn);
console.log('-=-=-=-=-=-');
var result = unrollJSONSelector(testOn);
console.log(result);
console.log('-=-=-=-=-=-');
console.log(result.$addToSet.a.$each);