我有一个包含对象和数组的嵌套数据结构。我如何提取信息,即访问一个特定的或多个值(或键)?

例如:

var data = {
    code: 42,
    items: [{
        id: 1,
        name: 'foo'
    }, {
        id: 2,
        name: 'bar'
    }]
};

我如何访问项目中的第二个项目的名称?


当前回答

如果您试图在JSON字符串中查找路径,可以将数据转储到https://jsonpathfinder.com并单击GUI元素。它将生成元素路径的JS语法。

除此之外,对于您可能想要迭代的任何数组,用循环替换相关的数组偏移下标,如[0]。

这里有一个简单版本的工具,你可以在这里或在https://ggorlen.github.io/json-dive/上运行。单击要将路径复制到剪贴板的节点。

/* code minified to make the tool easier to run without having to scroll */ let bracketsOnly=!1,lastHighlighted={style:{}};const keyToStr=t=>!bracketsOnly&&/^[a-zA-Z_$][a-zA-Z$_\d]*$/.test(t)?`.${toHTML(t)}`:`[&quot;${toHTML(t)}&quot;]`,pathToData=t=>`data-path="data${t.join("")}"`,htmlSpecialChars={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;","\t":"\\t","\r":"\\r","\n":"\\n"," ":"&nbsp;"},toHTML=t=>(""+t).replace(/[&<>"'\t\r\n ]/g,t=>htmlSpecialChars[t]),makeArray=(t,e)=>`\n [<ul ${pathToData(e)}>\n ${t.map((t,a)=>{e.push(`[${a}]`);const n=`<li ${pathToData(e)}>\n ${pathify(t,e).trim()},\n </li>`;return e.pop(),n}).join("")}\n </ul>]\n`,makeObj=(t,e)=>`\n {<ul ${pathToData(e)}>\n ${Object.entries(t).map(([t,a])=>{e.push(keyToStr(t));const n=`<li ${pathToData(e)}>\n "${toHTML(t)}": ${pathify(a,e).trim()},\n </li>`;return e.pop(),n}).join("")}\n </ul>}\n`,pathify=(t,e=[])=>Array.isArray(t)?makeArray(t,e):"object"==typeof t&&t!=null?makeObj(t,e):toHTML("string"==typeof t?`"${t}"`:t),defaultJSON='{\n "corge": "test JSON... \\n asdf\\t asdf",\n "foo-bar": [\n {"id": 42},\n [42, {"foo": {"baz": {"ba r<>!\\t": true, "4quux": "garply"}}}]\n ]\n}',$=document.querySelector.bind(document),$$=document.querySelectorAll.bind(document),resultEl=$("#result"),pathEl=$("#path"),tryToJSON=t=>{try{resultEl.innerHTML=pathify(JSON.parse(t)),$("#error").innerText=""}catch(t){resultEl.innerHTML="",$("#error").innerText=t}},copyToClipboard=t=>{const e=document.createElement("textarea");e.textContent=t,document.body.appendChild(e),e.select(),document.execCommand("copy"),document.body.removeChild(e)},flashAlert=(t,e=2e3)=>{const a=document.createElement("div");a.textContent=t,a.classList.add("alert"),document.body.appendChild(a),setTimeout(()=>a.remove(),e)},handleClick=t=>{t.stopPropagation(),copyToClipboard(t.target.dataset.path),flashAlert("copied!"),$("#path-out").textContent=t.target.dataset.path},handleMouseOut=t=>{lastHighlighted.style.background="transparent",pathEl.style.display="none"},handleMouseOver=t=>{pathEl.textContent=t.target.dataset.path,pathEl.style.left=`${t.pageX+30}px`,pathEl.style.top=`${t.pageY}px`,pathEl.style.display="block",lastHighlighted.style.background="transparent",(lastHighlighted=t.target.closest("li")).style.background="#0ff"},handleNewJSON=t=>{tryToJSON(t.target.value),[...$$("#result *")].forEach(t=>{t.addEventListener("click",handleClick),t.addEventListener("mouseout",handleMouseOut),t.addEventListener("mouseover",handleMouseOver)})};$("textarea").addEventListener("change",handleNewJSON),$("textarea").addEventListener("keyup",handleNewJSON),$("textarea").value=defaultJSON,$("#brackets").addEventListener("change",t=>{bracketsOnly=!bracketsOnly,handleNewJSON({target:{value:$("textarea").value}})}),handleNewJSON({target:{value:defaultJSON}}); /**/ *{box-sizing:border-box;font-family:monospace;margin:0;padding:0}html{height:100%}#path-out{background-color:#0f0;padding:.3em}body{margin:0;height:100%;position:relative;background:#f8f8f8}textarea{width:100%;height:110px;resize:vertical}#opts{background:#e8e8e8;padding:.3em}#opts label{padding:.3em}#path{background:#000;transition:all 50ms;color:#fff;padding:.2em;position:absolute;display:none}#error{margin:.5em;color:red}#result ul{list-style:none}#result li{cursor:pointer;border-left:1em solid transparent}#result li:hover{border-color:#ff0}.alert{background:#f0f;padding:.2em;position:fixed;bottom:10px;right:10px} <!-- --> <div class="wrapper"><textarea></textarea><div id="opts"><label>brackets only: <input id="brackets"type="checkbox"></label></div><div id="path-out">click a node to copy path to clipboard</div><div id="path"></div><div id="result"></div><div id="error"></div></div>

Unminified(也可以在GitHub上找到):

let bracketsOnly = false; let lastHighlighted = {style: {}}; const keyToStr = k => !bracketsOnly && /^[a-zA-Z_$][a-zA-Z$_\d]*$/.test(k) ? `.${toHTML(k)}` : `[&quot;${toHTML(k)}&quot;]` ; const pathToData = p => `data-path="data${p.join("")}"`; const htmlSpecialChars = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;", "\t": "\\t", "\r": "\\r", "\n": "\\n", " ": "&nbsp;", }; const toHTML = x => ("" + x) .replace(/[&<>"'\t\r\n ]/g, m => htmlSpecialChars[m]) ; const makeArray = (x, path) => ` [<ul ${pathToData(path)}> ${x.map((e, i) => { path.push(`[${i}]`); const html = `<li ${pathToData(path)}> ${pathify(e, path).trim()}, </li>`; path.pop(); return html; }).join("")} </ul>] `; const makeObj = (x, path) => ` {<ul ${pathToData(path)}> ${Object.entries(x).map(([k, v]) => { path.push(keyToStr(k)); const html = `<li ${pathToData(path)}> "${toHTML(k)}": ${pathify(v, path).trim()}, </li>`; path.pop(); return html; }).join("")} </ul>} `; const pathify = (x, path=[]) => { if (Array.isArray(x)) { return makeArray(x, path); } else if (typeof x === "object" && x !== null) { return makeObj(x, path); } return toHTML(typeof x === "string" ? `"${x}"` : x); }; const defaultJSON = `{ "corge": "test JSON... \\n asdf\\t asdf", "foo-bar": [ {"id": 42}, [42, {"foo": {"baz": {"ba r<>!\\t": true, "4quux": "garply"}}}] ] }`; const $ = document.querySelector.bind(document); const $$ = document.querySelectorAll.bind(document); const resultEl = $("#result"); const pathEl = $("#path"); const tryToJSON = v => { try { resultEl.innerHTML = pathify(JSON.parse(v)); $("#error").innerText = ""; } catch (err) { resultEl.innerHTML = ""; $("#error").innerText = err; } }; const copyToClipboard = text => { const ta = document.createElement("textarea"); ta.textContent = text; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); }; const flashAlert = (text, timeoutMS=2000) => { const alert = document.createElement("div"); alert.textContent = text; alert.classList.add("alert"); document.body.appendChild(alert); setTimeout(() => alert.remove(), timeoutMS); }; const handleClick = e => { e.stopPropagation(); copyToClipboard(e.target.dataset.path); flashAlert("copied!"); $("#path-out").textContent = e.target.dataset.path; }; const handleMouseOut = e => { lastHighlighted.style.background = "transparent"; pathEl.style.display = "none"; }; const handleMouseOver = e => { pathEl.textContent = e.target.dataset.path; pathEl.style.left = `${e.pageX + 30}px`; pathEl.style.top = `${e.pageY}px`; pathEl.style.display = "block"; lastHighlighted.style.background = "transparent"; lastHighlighted = e.target.closest("li"); lastHighlighted.style.background = "#0ff"; }; const handleNewJSON = e => { tryToJSON(e.target.value); [...$$("#result *")].forEach(e => { e.addEventListener("click", handleClick); e.addEventListener("mouseout", handleMouseOut); e.addEventListener("mouseover", handleMouseOver); }); }; $("textarea").addEventListener("change", handleNewJSON); $("textarea").addEventListener("keyup", handleNewJSON); $("textarea").value = defaultJSON; $("#brackets").addEventListener("change", e => { bracketsOnly = !bracketsOnly; handleNewJSON({target: {value: $("textarea").value}}); }); handleNewJSON({target: {value: defaultJSON}}); * { box-sizing: border-box; font-family: monospace; margin: 0; padding: 0; } html { height: 100%; } #path-out { background-color: #0f0; padding: 0.3em; } body { margin: 0; height: 100%; position: relative; background: #f8f8f8; } textarea { width: 100%; height: 110px; resize: vertical; } #opts { background: #e8e8e8; padding: 0.3em; } #opts label { padding: 0.3em; } #path { background: black; transition: all 0.05s; color: white; padding: 0.2em; position: absolute; display: none; } #error { margin: 0.5em; color: red; } #result ul { list-style: none; } #result li { cursor: pointer; border-left: 1em solid transparent; } #result li:hover { border-color: #ff0; } .alert { background: #f0f; padding: 0.2em; position: fixed; bottom: 10px; right: 10px; } <div class="wrapper"> <textarea></textarea> <div id="opts"> <label> brackets only: <input id="brackets" type="checkbox"> </label> </div> <div id="path-out">click a node to copy path to clipboard</div> <div id="path"></div> <div id="result"></div> <div id="error"></div> </div>

这并不是为了代替学习如何钓鱼,但一旦你知道了,可以节省时间。

其他回答

对象和数组有很多内置方法,可以帮助您处理数据。

注意:在很多例子中,我都使用了箭头函数。它们类似于函数表达式,但是它们在词法上绑定this值。

Object.keys(), Object.values() (ES 2017)和Object.entries() (ES 2017)

object .keys()返回对象键的数组,object .values()返回对象值的数组,object .entries()返回格式为[key, value]的对象键和相应值的数组。

Const obj = { 答:1 b: 2 c: 3 } console.log(Object.keys(obj)) // ['a', 'b', 'c'] console.log(Object.values(obj)) // [1,2,3] console.log (Object.entries (obj)) / / [[a, 1] [b 2], [' c ', 3]]

Object.entries()带有for-of循环和析构赋值

Const obj = { 答:1 b: 2 c: 3 } for (const [key, value] of Object.entries(obj)) { Console.log (' key: ${key}, value: ${value} ') }

使用for-of循环和解构赋值迭代Object.entries()的结果非常方便。

For-of循环允许迭代数组元素。语法为(const element of array)(我们可以用var或let替换const,但如果不打算修改element,最好使用const)。

析构赋值允许您从数组或对象中提取值并将它们赋给变量。在本例中,const [key, value]意味着不是将[key, value]数组赋值给element,而是将该数组的第一个元素赋值给key,将第二个元素赋值给value。它等价于:

for (const element of Object.entries(obj)) {
  const key = element[0]
       ,value = element[1]
}

如您所见,解构使这变得简单得多。

Array.prototype.every()和Array.prototype.some()

如果指定的回调函数对数组中的每个元素都返回true,则every()方法返回true。如果指定的回调函数对某些(至少一个)元素返回true,则some()方法返回true。

Const arr = [1,2,3] // true,因为每个元素都大于0 console.log(加勒比海盗。每个(x => x > 0)) // false,因为3^2大于5 console.log(加勒比海盗。数学。Pow (x, 2) < 5)) // true,因为2是偶数(除以2的余数是0) console.log(加勒比海盗。(x => x % 2 === 0)) // false,因为所有元素都不等于5 console.log(加勒比海盗。一些(x => x == 5))

Array.prototype.find()和Array.prototype.filter()

find()方法返回满足所提供回调函数的第一个元素。filter()方法返回满足所提供回调函数的所有元素的数组。

const arr = [1, 2, 3] // 2, because 2^2 !== 2 console.log(arr.find(x => x !== Math.pow(x, 2))) // 1, because it's the first element console.log(arr.find(x => true)) // undefined, because none of the elements equals 7 console.log(arr.find(x => x === 7)) // [2, 3], because these elements are greater than 1 console.log(arr.filter(x => x > 1)) // [1, 2, 3], because the function returns true for all elements console.log(arr.filter(x => true)) // [], because none of the elements equals neither 6 nor 7 console.log(arr.filter(x => x === 6 || x === 7))

Array.prototype.map ()

map()方法返回一个数组,其中包含对数组元素调用所提供的回调函数的结果。

Const arr = [1,2,3] console.log(加勒比海盗。Map (x => x + 1)) // [2,3,4] console.log(加勒比海盗。地图(x = > String.fromCharCode (96 + x))) / / (a, b, c的) console.log(加勒比海盗。Map (x => x)) // [1,2,3] (no-op) console.log(加勒比海盗。map(x =>数学。Pow (x, 2)) // [1,4,9] console.log(arr.map(String)) // ['1', '2', '3']

Array.prototype.reduce ()

reduce()方法通过调用提供的带有两个元素的回调函数,将数组减少为单个值。

Const arr = [1,2,3] //数组元素的和 console.log(加勒比海盗。Reduce ((a, b) => a + b)) // 6 //数组中最大的数字。 console.log(加勒比海盗。Reduce ((a, b) => a > b ?A: b)) // 3

reduce()方法有一个可选的第二个参数,它是初始值。当调用reduce()的数组可以有零个或一个元素时,这很有用。例如,如果我们想创建一个函数sum(),它接受一个数组作为参数,并返回所有元素的和,我们可以这样写:

Const sum = arr => arr。Reduce ((a, b) => a + b, 0) Console.log (sum([])) // 0 Console.log (sum([4])) // 4 Console.log (sum([2,5])

要访问嵌套属性,需要指定其名称,然后搜索对象。

如果你已经知道确切的路径,那么你可以像这样在你的脚本中硬编码它:

data['items'][1]['name']

这些也有用——

data.items[1].name
data['items'][1].name
data.items[1]['name']

如果您事先不知道确切的名称,或者用户是为您提供名称的人。然后需要动态搜索数据结构。有些人建议可以使用for循环来完成搜索,但是有一种非常简单的方法可以使用Array.reduce遍历路径。

const data = { code: 42, items: [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] }
const path = [ 'items', '1', 'name']
let result = path.reduce((a,v) => a[v], data)

路径的意思是:首先获取带有关键项的对象,它恰好是一个数组。然后取第1个元素(0个索引数组)。最后取该数组元素中键名的对象,恰好是字符串bar。

如果路径很长,甚至可以使用String。分开让这一切更容易-

'items.1.name'.split('.').reduce((a,v) => a[v], data)

这只是简单的JavaScript,没有使用任何第三方库,如jQuery或lodash。

有时,使用字符串访问嵌套对象是可取的。例如,最简单的方法是第一级

var obj = { hello: "world" };
var key = "hello";
alert(obj[key]);//world

但复杂的json通常不是这样。随着json变得越来越复杂,在json中查找值的方法也变得越来越复杂。导航json的递归方法是最好的,如何利用递归取决于要搜索的数据类型。如果涉及到条件语句,json搜索可能是一个很好的工具。

如果已经知道要访问的属性,但是路径很复杂,例如在这个对象中

var obj = {
 arr: [
    { id: 1, name: "larry" },    
    { id: 2, name: "curly" },
    { id: 3, name: "moe" }
 ]
};

你知道你想要得到对象中数组的第一个结果,也许你想使用

var moe = obj["arr[0].name"];

然而,这将导致一个异常,因为对象没有该名称的属性。能够使用这种方法的解决方案是将对象的树形面平直。这可以递归完成。

function flatten(obj){
 var root = {};
 (function tree(obj, index){
   var suffix = toString.call(obj) == "[object Array]" ? "]" : "";
   for(var key in obj){
    if(!obj.hasOwnProperty(key))continue;
    root[index+key+suffix] = obj[key];
    if( toString.call(obj[key]) == "[object Array]" )tree(obj[key],index+key+suffix+"[");
    if( toString.call(obj[key]) == "[object Object]" )tree(obj[key],index+key+suffix+".");   
   }
 })(obj,"");
 return root;
}

现在,这个复杂的物体可以被平面化

var obj = previous definition;
var flat = flatten(obj);
var moe = flat["arr[0].name"];//moe

下面是使用这种方法的jsFiddle演示。

你可以使用lodash _get函数:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// => 3

动态方法

在下面的deep(data,key)函数中,您可以使用任意键字符串-在您的情况下,items[1].name(您可以在任何级别使用数组符号[i]) -如果key无效,则返回undefined。

让深= (o, k) = > k.split (' . ') .reduce ((a、c、i) = > { 让m = c.match (/(.*?)\[(\ d *) \] /); 如果(m && a!=null && a[m[1]]!=null) return a[m[1]][+m[2]]; 返回a==null ?答:[c]; }, o); / /测试 Let key = 'items[1].name' //任意深键 Let data = { 42岁的代码: 名称:项目:[{id: 11日“foo”},{id: 22岁的名字:“酒吧”},) }; Console.log (key,'=', deep(data,key));