我有一个JSON文件,如下所示:[ { "dog": "lmn", "tiger": [ { "bengoltiger": { "height": { "x": 4 } }, "indiantiger": { "paw": "a", "foor": "b" } }, { "bengoltiger": { "width": { "a": 8 } }, "indiantiger": { "b": 3 } } ] }, { "dog": "pqr", "tiger": [ { "bengoltiger": { "width": { "m": 3 } }, "indiantiger": { "paw": "a", "foor": "b" } }, { "bengoltiger": { "height": { "n": 8 } }, "indiantiger": { "b": 3 } } ], "lion": 90 }]我想将其转换为在任何嵌套级别获得任何对象的所有可能的属性。对于数组,第一个对象应包含所有属性。这些值是微不足道的,但是以下解决方案考虑了任何属性的第一个遇到的值。 (例如,为“ dog”属性保留“ lmn”)预期产量:[ { "dog": "lmn", "tiger": [ { "bengoltiger": { "height": { "x": 4, "n": 8 }, "width": { "a": 8, "m": 3 } }, "indiantiger": { "paw": "a", "foor": "b", "b": 3 } } ], "lion": 90 }]这是我在嵌套问题触及之前尝试过的递归函数function consolidateArray(json) { if (Array.isArray(json)) { const reference = json[0]; json.forEach(function(element) { for (var key in element) { if (!reference.hasOwnProperty(key)) { reference[key] = element[key]; } } }); json.splice(1); this.consolidateArray(json[0]); } else if (typeof json === 'object') { for (var key in json) { if (json.hasOwnProperty(key)) { this.consolidateArray(json[key]); } } } };var json = [ { "dog": "lmn", "tiger": [ { "bengoltiger": { "height": { "x": 4 } }, "indiantiger": { "paw": "a", "foor": "b" } }, { "bengoltiger": { "width": { "a": 8 } }, "indiantiger": { "b": 3 } } ] }, { "dog": "pqr", "tiger": [ { "bengoltiger": { "width": { "m": 3 } }, "indiantiger": { "paw": "a", "foor": "b" } }, { "bengoltiger": { "height": { "n": 8 } }, "indiantiger": { "b": 3 } } ], "lion": 90 }];consolidateArray(json);alert(JSON.stringify(json, null, 2)); 最佳答案 这是一个有趣的问题。这是我想出的:// Utility functionsconst isInt = Number.isIntegerconst path = (ps = [], obj = {}) => ps .reduce ((o, p) => (o || {}) [p], obj)const assoc = (prop, val, obj) => isInt (prop) && Array .isArray (obj) ? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)] : {...obj, [prop]: val}const assocPath = ([p = undefined, ...ps], val, obj) => p == undefined ? obj : ps.length == 0 ? assoc(p, val, obj) : assoc(p, assocPath(ps, val, obj[p] || (obj[p] = isInt(ps[0]) ? [] : {})), obj)// Helper functionsfunction * getPaths(o, p = []) { if (Object(o) !== o || Object .keys (o) .length == 0) yield p if (Object(o) === o) for (let k of Object .keys (o)) yield * getPaths (o[k], [...p, isInt (Number (k)) ? Number (k) : k])}const canonicalPath = (path) => path.map (n => isInt (Number (n)) ? 0 : n)const splitPaths = (xs) => Object .values ( xs.reduce ( (a, p, _, __, cp = canonicalPath (p), key = cp .join ('\u0000')) => ({...a, [key]: a [key] || {canonical: cp, path: p} }) , {} ))// Main functionconst canonicalRep = (data) => splitPaths ([...getPaths (data)]) .reduce ( (a, {path:p, canonical}) => assocPath(canonical, path(p, data), a), Array.isArray(data) ? [] : {} ) // Testconst data = [{"dog": "lmn", "tiger": [{"bengoltiger": {"height": {"x": 4}}, "indiantiger": {"foor": "b", "paw": "a"}}, {"bengoltiger": {"width": {"a": 8}}, "indiantiger": {"b": 3}}]}, {"dog": "pqr", "lion": 90, "tiger": [{"bengoltiger": {"width": {"m": 3}}, "indiantiger": {"foor": "b", "paw": "a"}}, {"bengoltiger": {"height": {"n": 8}}, "indiantiger": {"b": 3}}]}]console .log ( canonicalRep (data))前几个函数是我将保留在系统库中的普通实用程序函数。它们在此代码之外有很多用途:isInt只是Number.isInteger的一流函数别名path沿着给定路径查找对象的嵌套属性path(['b', 1, 'c'], {a: 10, b: [{c: 20, d: 30}, {c: 40}], e: 50}) //=> 40assoc返回一个克隆原始对象的新对象,但是某个属性的值设置为或替换为提供的对象。assoc('c', 42, {a: 1, b: 2, c: 3, d: 4}) //=> {a: 1, b: 2, c: 42, d: 4}请注意,内部对象尽可能通过引用共享。assocPath做同样的事情,但是路径更深,可以根据需要构建节点。assocPath(['a', 'b', 1, 'c', 'd'], 42, {a: {b: [{x: 1}, {x: 2}], e: 3}) //=> {a: {b: [{x: 1}, {c: {d: 42}, x: 2}], e: 3}}除了isInt,这些都从Ramda借用了它们的API。 (免责声明:我是Ramda的作者。)但是这些都是独特的实现。下一个功能getPaths是another SO answer中一个功能的改编。它以path和assocPath使用的格式列出对象中的所有路径,如果相关的嵌套对象是数组,则返回整数值的数组,否则返回字符串。与从中借用的函数不同,它仅返回叶值的路径。对于您的原始对象,它返回此数据的迭代器:[ [0, "dog"], [0, "tiger", 0, "bengoltiger", "height", "x"], [0, "tiger", 0, "indiantiger", "foor"], [0, "tiger", 0, "indiantiger", "paw"], [0, "tiger", 1, "bengoltiger", "width", "a"], [0, "tiger", 1, "indiantiger", "b"], [1, "dog"], [1, "lion"], [1, "tiger", 0, "bengoltiger", "width", "m"], [1, "tiger", 0, "indiantiger", "foor"], [1, "tiger", 0, "indiantiger", "paw"], [1, "tiger", 1, "bengoltiger", "height", "n"], [1, "tiger", 1, "indiantiger", "b"]]如果我想花更多的时间在此,我将用非生成器版本替换getPaths的该版本,只是为了保持此代码的一致性。这应该不难,但是我对花更多的时间不感兴趣。我们不能直接使用这些结果来构建您的输出,因为它们所引用的数组元素超出了第一个。这就是splitPaths及其帮助程序canonicalPath进入的地方。我们通过将所有整数替换为0来创建规范路径,从而为我们提供了这样的数据结构:[{ canonical: [0, "dog"], path: [0, "dog"]}, { canonical: [0, "tiger", 0, "bengoltiger", "height", "x"], path: [0, "tiger", 0, "bengoltiger", "height", "x"]}, { canonical: [0, "tiger", 0, "indiantiger", "foor"], path: [0, "tiger", 0, "indiantiger", "foor"]}, { canonical: [0, "tiger", 0, "indiantiger", "paw"], path: [0, "tiger", 0, "indiantiger", "paw"]}, { canonical: [0, "tiger", 0, "bengoltiger", "width", "a"], path: [0, "tiger", 1, "bengoltiger", "width", "a"]}, { canonical: [0, "tiger", 0, "indiantiger", "b"], path: [0, "tiger", 1, "indiantiger", "b"]}, { canonical: [0, "lion"], path: [1, "lion"]}, { canonical: [0, "tiger", 0, "bengoltiger", "width", "m"], path: [1, "tiger", 0, "bengoltiger", "width", "m"]}, { canonical: [0, "tiger", 0, "bengoltiger", "height", "n"], path: [1, "tiger", 1, "bengoltiger", "height", "n"]}]请注意,此功能还会删除重复的规范路径。我们最初同时具有[0, "tiger", 0, "indiantiger", "foor"]和[1, "tiger", 0, "indiantiger", "foor"],但是输出仅包含第一个。它通过将路径与不可打印字符\u0000连接在一起而创建的键下,将它们存储在对象中,以实现此目的。这是完成此任务的最简单方法,但是失败模式极有可能是1,因此,如果我们真的希望我们可以进行更复杂的重复检查。我不会打扰的。最后,主要功能canonicalRep通过调用splitPaths并将结果折叠起来,使用canonical说出放置新数据的位置,然后将path函数应用于属性和原始对象。根据要求,我们的最终输出如下所示:[ { dog: "lmn", lion: 90, tiger: [ { bengoltiger: { height: { n: 8, x: 4 }, width: { a: 8, m: 3 } }, indiantiger: { b: 3, foor: "b", paw: "a" } } ] }]让我着迷的是,尽管我无法想象它有任何实际用途,但我将其视为有趣的编程挑战。但是,既然我已经对它进行了编码,我意识到它将解决几周前我搁置的当前项目中的一个问题。我可能会在星期一实施!1如果您的某些节点包含该分隔符path,则可能会发生故障模式。例如,如果您有路径\u0000和[...nodes, "abc\u0000", "def", ...nodes],它们都将映射到[...nodes, "abc", "\u0000def", ...nodes]。如果这是一个真正的问题,我们当然可以使用其他形式的重复数据删除。 09-07 15:02