问题
I'm looking for a way to search a JSON object to check if it contains a given value, and if it exists, then unset it.
My data is structured as follows (commented with explanation):
// Search within the 'seats' array for a given 'guestID', if it exists, unset it
tables = [
{
"_id":{
$oid: "one"
},
"seats":[
{ "guestId":"01" },
{ "guestId":"02" },
{ "guestId":"03" }
]
},
{
"_id":{
$oid: "two"
},
"seats":[
{ "guestId":"11" },
{ "guestId":"12" },
{ "guestId":"13" }
]
}
]
I am using underscore for this project, and I've tried using _.pluck(tables, 'seats') and then _.foreach but I was having to nest multiple _.foreach statements to access the seats array for searching, and I'm not sure if that's a best practice or not. Is there an easier way that I'm not aware of?
This data is being returned from the mongolab REST api. Is this something I should be doing in my initial XHR request, rather than getting a large object and then trying to parse through it client-side?
If this were an SQL request I'd just be able to do something like select tables.seats where guestId = XXX
回答1:
Whenever I've come across these kinds of situations in the past, a recursive search function has always been invaluable... here's one of mine I had lying around (I extended it by adding a remove method):
function o ( target ) {
/// if an instance of o has been created, then handle it
if ( this instanceof o ) {
/// O INSTANCE:
/// an instance of o acts like a reference or pointer
/// to how the value it targets was reached.
this.value = target;
this.key = arguments[1];
this.parent = arguments[2];
this.toString = function(){ return 'o('+this.key+' = '+this.value+')';}
}
/// if no instance being created fall back to the 'scan object' code
else {
/// RECURSIVE CODE:
/// the _ function is responsible for accepting the
/// attributeName and attributeValue search
var _ = function ( key, value, modifier ) {
var i, v, tar = ( modifier ? modifier.target : target ), items = [];
/// if we are dealing with an o instance, handle slightly differently
if ( tar instanceof o ) {
for ( i in tar.value ) {
/// check to see if our current key and value
/// match our search terms
if ( _.test( i, (v=tar.value[i]), key, value ) ) {
items.push(new o(v, i, tar));
};
};
}
/// if no o instance treat as a normal object or array
else {
for ( i in tar ) {
if ( (v = tar[i]) ) {
/// if we are an instance of o, recurse to actually
/// check the items within
if ( v instanceof o ) {
items = items.concat( _( key, value, {target:v} ) );
}
/// check to see if our current key and value match
/// our search terms
else if ( _.test( i, v, key, value ) ) {
items.push(new o(v, i, tar));
};
};
};
};
/// if in modifier mode, don't rely on storing in scope,
/// return the calculated items instead
if ( modifier ) {
return items;
}
else {
/// update what we are targeting
target = items;
/// return our underscore function
return _;
};
};
/// FUNCTION DECLARATIONS:
/// a quick test to see if the key and value match (or either or)
_.test = function ( i,v,key,value ) {
var havekey = ( key !== null && key !== undefined ),
haveval = ( value !== null && value !== undefined ),
passkey = ( havekey && (i == key || key === '*') ),
passval = ( haveval && (v == value || value === '*') );
return ( havekey && haveval && passkey && passval ) ||
( havekey && !haveval && passkey ) ||
( haveval && !havekey && passval );
};
/// calculate the path needed to reach the object within the structure
_.path = function () {
var i = target.length, paths = [], path, cur, last;
while ( i-- ) {
cur = target[i]; path = [];
do{ last = cur; if ( cur instanceof o ){ path.unshift( cur.key ); } }
while( (cur = cur.parent) );
paths.push(path.join('/'));
};
return ( paths.length == 1 ? paths[0] : paths );
};
/// remove the item we are targeting by stepping back
/// and deleting ourselves from the previous parent
_.remove = function ( removeEntireObject ) {
var i = target.length, paths, path, cur, last;
while ( i-- ) {
cur = target[i];
/// remove the object that has the found attribute
if ( removeEntireObject ) {
if ( cur.parent.parent ) {
cur.parent.parent.value[cur.parent.key] = null;
delete cur.parent.parent.value[cur.parent.key];
}
}
/// otherwise remove only the targeted attribute
else {
cur.parent.value[cur.key] = null;
delete cur.parent.value[cur.key];
}
};
return _;
};
/// a useful function for backwards navigation
_.parent = function () {
var i = target.length, cur, items = [], values = [];
while ( i-- ) {
cur = target[i];
/// remove the object that has the found attribute
if ( cur && cur.parent ) {
/// store the values as we go so we can
/// spot and remove duplicated parents
if ( values.indexOf(cur.parent.value) === -1 ) {
items.push(cur.parent);
values.push(cur.parent.value);
}
}
};
target = items;
return _;
}
/// slimple debugging
_.alert = function () {
var i = target.length, cur;
while ( i-- ) {
cur = target[i];
alert(cur);
};
return _;
};
return _;
};
};
Example usage:
/// remove only the guestId object with a value '01'
o(tables)('*')('seats')('*')('guestId', '01').remove( true );
or:
/// remove the 'guestIds' object in the first slot for either seat
o(tables)('*')('seats')(0)('guestId', '*').parent().remove();
or:
/// remove all 'guestIds' from the first seat
o(tables)(0)('seats')('*')('guestId').parent().remove();
Explanation:
- You must always start by calling
o(my_object_to_parse). - Passing one parameter acts as an
attributeNamesearch. - Passing two parameters acts as an
attributeNameandattributeValuesearch. - The
*acts as a simple wildcard which can be useful to handle basic arrays. - The
*can be used as theattributeNameorattributeValue. - Each successive request moves one level further into the structure.
I haven't used this code in a while, so it may not be 100% bug free or optimal. However it seems to work for your particular use case... and it also managed to handle everything I threw at it whilst testing. It should be easy enough to extend with more methods beyond .remove(), .parent(), .path() and .alert() and it might be best to add in some error checking.
来源:https://stackoverflow.com/questions/12163160/searching-json-object-key-containing-value-then-unset-it