Обновление: продолжение MongoDB Получает названия всех ключей в наборе.
Как указано Kristina, можно использовать Mongodb, отображают/уменьшают для списка ключей в наборе:
db.things.insert( { type : ['dog', 'cat'] } );
db.things.insert( { egg : ['cat'] } );
db.things.insert( { type : [] });
db.things.insert( { hello : [] } );
mr = db.runCommand({"mapreduce" : "things",
"map" : function() {
for (var key in this) { emit(key, null); }
},
"reduce" : function(key, stuff) {
return null;
}})
db[mr.result].distinct("_id")
//output: [ "_id", "egg", "hello", "type" ]
Пока мы хотим получить только ключи, расположенные на первом уровне глубины, это хорошо работает. Однако это приведет получение к сбою тех ключей, которые расположены на более глубоких уровнях. Если мы добавляем новую запись:
db.things.insert({foo: {bar: {baaar: true}}})
И мы работаем снова, карта - уменьшают +distinct отрывок выше, мы доберемся:
[ "_id", "egg", "foo", "hello", "type" ]
Но мы не получим панель и baaar ключи, которые вкладываются вниз в структуре данных. Вопрос: как я получаю все ключи, неважно, их уровень глубины? Идеально, я на самом деле хотел бы, чтобы сценарий спустился ко всему уровню глубины, произведя вывод, такой как:
["_id","egg","foo","foo.bar","foo.bar.baaar","hello","type"]
Заранее спасибо!
OK, это немного сложнее, потому что вам нужно будет использовать некоторую рекурсию.
Для того чтобы рекурсия работала, вам нужно иметь возможность хранить некоторые функции на сервере.
isArray = function (v) {
return v && typeof v === 'object' && typeof v.length === 'number' && !(v.propertyIsEnumerable('length'));
}
m_sub = function(base, value){
for(var key in value) {
emit(base + "." + key, null);
if( isArray(value[key]) || typeof value[key] == 'object'){
m_sub(base + "." + key, value[key]);
}
}
}
db.system.js.save( { _id : "isArray", value : isArray } );
db.system.js.save( { _id : "m_sub", value : m_sub } );
map = function(){
for(var key in this) {
emit(key, null);
if( isArray(this[key]) || typeof this[key] == 'object'){
m_sub(key, this[key]);
}
}
}
reduce = function(key, stuff){ return null; }
mr = db.runCommand({"mapreduce" : "things", "map" : map, "reduce" : reduce,"out": "things" + "_keys"});
db[mr.result].distinct("_id");
Результаты, которые вы получите:
["_id", "_id.isObjectId", "_id.str", "_id.tojson", "egg", "egg.0", "foo", "foo.bar", "foo.bar.baaaar", "hello", "type", "type.0", "type.1"]
Здесь есть одна очевидная проблема, мы добавляем несколько неожиданных полей: 1. данные _id 2. .0 (на яйце и типе)
Для проблемы #1 исправление относительно простое. Просто измените функцию map
. Измените this:
emit(base + "." + key, null); if( isArray...
на this:
if(key != "_id") { emit(base + "." + key, null); if( isArray... }
Проблема #2 немного более сложная. Вам нужны все ключи, а технически "egg.0" является допустимым ключом. Вы можете изменить m_sub
, чтобы игнорировать такие числовые ключи. Но также легко представить ситуацию, когда это приведет к обратному результату. Допустим, у вас есть ассоциативный массив внутри обычного массива, и вы хотите, чтобы появился "0". Остальное решение я оставлю на ваше усмотрение.