JavaScript中的JSON查找

68

除了循环查找JSON中的数据之外,是否还有更好的方法?这是用于编辑和删除的。

for(var k in objJsonResp) {
  if (objJsonResp[k].txtId == id) {
    if (action == 'delete') {
      objJsonResp.splice(k,1);
    } else {
      objJsonResp[k] = newVal;
    }
    break;
  }
}
数据以一个由字典组成的列表形式呈现,例如:
[
  {id:value, pId:value, cId:value,...},
  {id:value, pId:value, cId:value,...},
  ...
]

有没有人注意到删除不会改变长度和@deleted位置上的项目可能保持为未定义(除非它是最后一个元素),减少长度以纠正这一点会删除最后一个元素吗?Splice似乎会更新它们。 - Tom
7个回答

194
(您不是在搜索“JSON”,而是在搜索数组——在这种情况下,JSON字符串已经被反序列化为对象图,即数组。)
一些选项:

使用对象而不是数组

如果您控制生成此内容,则必须是一个数组吗?如果不是,有一个更简单的方法。
假设这是您的原始数据:
[
    {"id": "one",   "pId": "foo1", "cId": "bar1"},
    {"id": "two",   "pId": "foo2", "cId": "bar2"},
    {"id": "three", "pId": "foo3", "cId": "bar3"}
]

你能改为以下方式吗?

{
    "one":   {"pId": "foo1", "cId": "bar1"},
    "two":   {"pId": "foo2", "cId": "bar2"},
    "three": {"pId": "foo3", "cId": "bar3"}
}

然后通过ID找到相关条目非常简单:

id = "one"; // Or whatever
var entry = objJsonResp[id];

...正在更新它:

objJsonResp[id] = /* New value */;

...并将其移除:

delete objJsonResp[id];

这利用了JavaScript中的一个特点,即您可以使用属性名称作为字符串对对象进行索引--该字符串可以是文字,也可以像上面的id一样来自变量。

添加ID到索引映射

(愚蠢的想法,早于上述内容。因历史原因而保留。)
看起来您需要将其作为数组,此时除了搜索数组之外没有更好的方法,除非您想在其上放置地图,如果您控制对象的生成,则可以这样做。例如,假设您最初有以下内容:
[
    {"id": "one",   "pId": "foo1", "cId": "bar1"},
    {"id": "two",   "pId": "foo2", "cId": "bar2"},
    {"id": "three", "pId": "foo3", "cId": "bar3"}
]

生成的代码可以提供一个id-to-index映射:
{
    "index": {
        "one": 0, "two": 1, "three": 2
    },
    "data": [
        {"id": "one",   "pId": "foo1", "cId": "bar1"},
        {"id": "two",   "pId": "foo2", "cId": "bar2"},
        {"id": "three", "pId": "foo3", "cId": "bar3"}
    ]
}

那么,获取变量id中的id条目就很简单了:

var index = objJsonResp.index[id];
var obj = objJsonResp.data[index];

这利用了您可以使用属性名称对对象进行索引的事实。

当然,如果您这样做,您必须在修改数组时更新映射,这可能会成为一个维护问题。

但是,如果您无法控制对象的生成,或者更新id-to-index映射太多代码和/或维护问题,则必须进行暴力搜索。

暴力搜索(已更正)

有点 OT(尽管您确实问是否有更好的方法 :-)),但是您循环遍历数组的代码是不正确的。 Details here,但是您不能使用for..in来循环遍历数组索引(或者更确切地说,如果这样做,您必须特别小心才能这样做); for..in循环遍历对象的属性,而不是数组的索引。对于非稀疏数组(而您的数组是非稀疏的),最好的选择是使用标准的老式循环:

var k;
for (k = 0; k < someArray.length; ++k) { /* ... */ }

或者

var k;
for (k = someArray.length - 1; k >= 0; --k) { /* ... */ }

无论您喜欢哪种方法(后者并不总是在所有实现中更快,这对我来说很不直观,但我们就是这样)。 (对于一个稀疏数组,您可以使用for..in,但需要特别注意避免陷阱;有关详细信息,请参见上面链接的文章。) 在数组上使用for..in在简单情况下似乎是有效的,因为数组具有每个索引的属性,并且它们的唯一其他默认属性(length和它们的方法)被标记为不可枚举。 但是,只要您设置(或框架设置)数组对象上的任何其他属性(这是完全有效的;数组只是具有一些围绕length属性的特殊处理的对象),它就会中断。

谢谢,我会选择第一种方式,看起来更好。 - zapping
伟大的技巧:for (var i = someArray.length; 0 < --i;) { /* ... */ } - Fabián Heredia Montiel
2
@TJCrowder - 这对于真正的对象图不起作用。您只覆盖了一层(顶部)。如果此模型更复杂,则无法找到嵌套值。 - Travis J
1
@TravisJ:非常正确。原帖中的数据没有嵌套。如果数据是嵌套的,那么很容易制作上述代码的递归版本。 :-) - T.J. Crowder
3
@T.J.Crowder - 我曾认为需要一种递归版本,并且已经写了一个。我建议给出的解决方案只是顶层,以便了解您的想法。如果其他人对使用我下面发布的递归版本感兴趣,可以参考它。 - Travis J

8
我曾遇到一个有多个嵌套对象的复杂模型问题。一个很好的例子是这样的:假设你有一张自己的拍立得照片,然后把它放进汽车的后备箱里。这辆汽车又被放进了一个大木箱里。这个大木箱又被放在一艘有许多其他木箱的大船的货舱里。我必须搜索货舱、检查木箱、检查后备箱,然后寻找我的照片是否存在。
我在网上找不到任何好的解决方案来处理这个问题,而使用 .filter() 只适用于数组。大多数解决方案建议只需检查是否存在 model["yourpicture"] 即可。但这种方法非常不理想,因为从例子中可以看出,它只能搜索货船的货舱,而我需要一种更深入地探索的方法。
这就是我制作的递归解决方案。在注释中,我已经确认需要一个递归版本,我觉得如果有人遇到类似的复杂情况,我可以分享一下我的解决方案。
function ContainsKeyValue( obj, key, value ){
    if( obj[key] === value ) return true;
    for( all in obj )
    {
        if( obj[all] != null && obj[all][key] === value ){
            return true;
        }
        if( typeof obj[all] == "object" && obj[all]!= null ){
            var found = ContainsKeyValue( obj[all], key, value );
            if( found == true ) return true;
        }
    }
    return false;
}

这将从图中给定的对象开始遍历,并向下递归查找任何发现的对象。我使用它的方式如下:
var liveData = [];
for( var items in viewmodel.Crates )
{
    if( ContainsKeyValue( viewmodel.Crates[items], "PictureId", 6 ) === true )
    {
        liveData.push( viewmodel.Crates[items] );
    }
}

这将生成一个包含我的图片的货箱数组。

8

您可以使用这个JavaScript库——DefiantJS来进行Zapping。无需将JSON数据重构为对象以便于搜索。相反,您可以使用类似于XPath表达式的方式来搜索JSON结构:

    var data = [
   {
      "id": "one",
      "pId": "foo1",
      "cId": "bar1"
   },
   {
      "id": "two",
      "pId": "foo2",
      "cId": "bar2"
   },
   {
      "id": "three",
      "pId": "foo3",
      "cId": "bar3"
   }
],
res = JSON.search( data, '//*[id="one"]' );

console.log( res[0].cId );
// 'bar1'

DefiantJS扩展了全局对象JSON,增加了一个新方法:“search”,该方法返回匹配项数组(如果没有找到则为空数组)。您可以通过将JSON数据粘贴并在此处测试不同的XPath查询来自行尝试: http://www.defiantjs.com/#xpath_evaluator 正如您所知,XPath是一种标准化的查询语言。

谢谢,我会记得下次试一试。 - zapping

0
如果您的 JSON 数据以某种方式排序,则可以实现各种搜索。但是,如果您没有处理大量数据,则在此处使用 O(n) 操作(就像您目前所做的一样)可能就足够了。其他任何操作都可能过度设计。

0

通用解决方案

我们在很多数据处理中使用object-scan。它有一些不错的特性,尤其是可以按删除安全顺序遍历。以下是如何实现查找、删除和替换的方法,以解决您的问题。

// const objectScan = require('object-scan');

const tool = (() => {
  const scanner = objectScan(['[*]'], {
    abort: true,
    rtn: 'bool',
    filterFn: ({
      value, parent, property, context
    }) => {
      if (value.id === context.id) {
        context.fn({ value, parent, property });
        return true;
      }
      return false;
    }
  });
  return {
    add: (data, id, obj) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property + 1, 0, obj) }),
    del: (data, id) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property, 1) }),
    mod: (data, id, prop, v = undefined) => scanner(data, {
      id,
      fn: ({ value }) => {
        if (value !== undefined) {
          value[prop] = v;
        } else {
          delete value[prop];
        }
      }
    })
  };
})();

// -------------------------------

const data = [ { id: 'one', pId: 'foo1', cId: 'bar1' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ];
const toAdd = { id: 'two', pId: 'foo2', cId: 'bar2' };

const exec = (fn) => {
  console.log('---------------');
  console.log(fn.toString());
  console.log(fn());
  console.log(data);
};

exec(() => tool.add(data, 'one', toAdd));
exec(() => tool.mod(data, 'one', 'pId', 'zzz'));
exec(() => tool.mod(data, 'one', 'other', 'test'));
exec(() => tool.mod(data, 'one', 'gone', 'delete me'));
exec(() => tool.mod(data, 'one', 'gone'));
exec(() => tool.del(data, 'three'));

// => ---------------
// => () => tool.add(data, 'one', toAdd)
// => true
// => [ { id: 'one', pId: 'foo1', cId: 'bar1' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'pId', 'zzz')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'other', 'test')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'gone', 'delete me')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: 'delete me' }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.mod(data, 'one', 'gone')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: undefined }, { id: 'two', pId: 'foo2', cId: 'bar2' }, { id: 'three', pId: 'foo3', cId: 'bar3' } ]
// => ---------------
// => () => tool.del(data, 'three')
// => true
// => [ { id: 'one', pId: 'zzz', cId: 'bar1', other: 'test', gone: undefined }, { id: 'two', pId: 'foo2', cId: 'bar2' } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.7.1"></script>

声明:本人是object-scan的作者。


0

好的。我知道这是一篇旧帖子,但也许这可以帮助其他人。虽然这不是向后兼容的,但几乎无关紧要,因为Internet Explorer正在被淘汰。

实现所需功能最简单的方法:

function findInJson(objJsonResp, key, value, aType){        
     if(aType=="edit"){
        return objJsonResp.find(x=> x[key] == value);
     }else{//delete
         var a =objJsonResp.find(x=> x[key] == value);
         objJsonResp.splice(objJsonResp.indexOf(a),1);
     }
}

如果您将类型(type)设置为'edit',它将返回您想要编辑的项目。如果提供其他任何内容或不提供任何内容,则默认为删除(delete)。如果您愿意,可以翻转条件语句。


-1
如果您在应用程序中的多个位置执行此操作,使用客户端JSON数据库会更有意义,因为创建自定义搜索功能比替代方案更混乱且难以维护。
请查看ForerunnerDB,它为您提供了一个非常强大的客户端JSON数据库系统,并包括一个非常简单的查询语言,帮助您实现您要寻找的功能:
// Create a new instance of ForerunnerDB and then ask for a database
var fdb = new ForerunnerDB(),
    db = fdb.db('myTestDatabase'),
    coll;

// Create our new collection (like a MySQL table) and change the default
// primary key from "_id" to "id"
coll = db.collection('myCollection', {primaryKey: 'id'});

// Insert our records into the collection
coll.insert([
    {"name":"my Name","id":12,"type":"car owner"},
    {"name":"my Name2","id":13,"type":"car owner2"},
    {"name":"my Name4","id":14,"type":"car owner3"},
    {"name":"my Name4","id":15,"type":"car owner5"}
]);

// Search the collection for the string "my nam" as a case insensitive
// regular expression - this search will match all records because every
// name field has the text "my Nam" in it
var searchResultArray = coll.find({
    name: /my nam/i
});

console.log(searchResultArray);

/* Outputs
[
    {"name":"my Name","id":12,"type":"car owner"},
    {"name":"my Name2","id":13,"type":"car owner2"},
    {"name":"my Name4","id":14,"type":"car owner3"},
    {"name":"my Name4","id":15,"type":"car owner5"}
]
*/

声明:我是ForerunnerDB的开发者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接