如何将菊花链/点符号中的 JavaScript 对象展开为具有嵌套对象和数组的对象?
Posted
技术标签:
【中文标题】如何将菊花链/点符号中的 JavaScript 对象展开为具有嵌套对象和数组的对象?【英文标题】:How to unflatten a JavaScript object in a daisy-chain/dot notation into an object with nested objects and arrays? 【发布时间】:2017-07-30 10:00:06 【问题描述】:我想像这样展开一个对象...
var obj2 =
"firstName": "John",
"lastName": "Green",
"car.make": "Honda",
"car.model": "Civic",
"car.revisions.0.miles": 10150,
"car.revisions.0.code": "REV01",
"car.revisions.0.changes": "",
"car.revisions.1.miles": 20021,
"car.revisions.1.code": "REV02",
"car.revisions.1.changes.0.type": "asthetic",
"car.revisions.1.changes.0.desc": "Left tire cap",
"car.revisions.1.changes.1.type": "mechanic",
"car.revisions.1.changes.1.desc": "Engine pressure regulator",
"visits.0.date": "2015-01-01",
"visits.0.dealer": "DEAL-001",
"visits.1.date": "2015-03-01",
"visits.1.dealer": "DEAL-002"
;
... 到具有嵌套对象和数组的对象中,如下所示:
firstName: 'John',
lastName: 'Green',
car:
make: 'Honda',
model: 'Civic',
revisions: [
miles: 10150, code: 'REV01', changes: '',
miles: 20021, code: 'REV02', changes: [
type: 'asthetic', desc: 'Left tire cap' ,
type: 'mechanic', desc: 'Engine pressure regulator'
]
]
,
visits: [
date: '2015-01-01', dealer: 'DEAL-001' ,
date: '2015-03-01', dealer: 'DEAL-002'
]
这是我的(失败的)尝试:
function unflatten(obj)
var result = ;
for (var property in obj)
if (property.indexOf('.') > -1)
var substrings = property.split('.');
console.log(substrings[0], substrings[1]);
else
result[property] = obj[property];
return result;
;
我很快就开始不必要地重复代码,以便进行对象和数组的嵌套。这绝对是需要递归的东西。有什么想法吗?
编辑:我还在another question 中提出了相反的问题,即扁平化。
【问题讨论】:
我在这里有一种强烈的似曾相识。我可以发誓我昨天读过这个问题。 -- 编辑:好吧,这与yesterday's question相反。 上一个问题是关于flatten,这一个是关于unflatten
。感觉像是一些家庭任务
【参考方案1】:
您可以先使用for...in
循环来循环对象属性,然后在.
处拆分每个键,然后使用reduce 构建嵌套属性。
var obj2 = "firstName":"John","lastName":"Green","car.make":"Honda","car.model":"Civic","car.revisions.0.miles":10150,"car.revisions.0.code":"REV01","car.revisions.0.changes":"","car.revisions.1.miles":20021,"car.revisions.1.code":"REV02","car.revisions.1.changes.0.type":"asthetic","car.revisions.1.changes.0.desc":"Left tire cap","car.revisions.1.changes.1.type":"mechanic","car.revisions.1.changes.1.desc":"Engine pressure regulator","visits.0.date":"2015-01-01","visits.0.dealer":"DEAL-001","visits.1.date":"2015-03-01","visits.1.dealer":"DEAL-002"
function unflatten(data)
var result =
for (var i in data)
var keys = i.split('.')
keys.reduce(function(r, e, j)
return r[e] || (r[e] = isNaN(Number(keys[j + 1])) ? (keys.length - 1 == j ? data[i] : ) : [])
, result)
return result
console.log(unflatten(obj2))
【讨论】:
这个脚本的第 8 行很粗糙,很难弄清楚发生了什么。r[e] = isNan(...
有什么作用?三元的前面是布尔值,赋值会返回什么吗?
@ohsully isNaN(Number(keys[j + 1]))
在从字符串转换为数字时检查下一个键是否为数字。如果它返回 true,则意味着它不是数字,然后您评估另一个条件以查看其最后一个键是否并分配值或空对象。但是如果它返回 false 那么它是一个数字,然后它分配一个数组。
所以如果你有这样的密钥car.revisions.0.miles
前两部分(汽车和修订)不是数字,第三部分是数字,最后一部分不是数字。【参考方案2】:
尝试将问题分解为两个不同的挑战:
-
按路径设置值
循环遍历对象并逐个展开键
您可以从一个看起来像这样的setIn
函数开始:
function setIn(path, object, value)
let [key, ...keys] = path;
if (keys.length === 0)
object[key] = value;
else
let nextKey = keys[0];
object[key] = object[key] || isNaN(nextKey) ? : [];
setIn(keys, object[key], value);
return object;
然后将它与 unflatten
函数结合起来,该函数循环遍历每个键运行 setIn
的对象。
function unflatten(flattened)
let object = ;
for (let key in flattened)
let path = key.split('.');
setIn(path, object, flattened[key]);
return object;
当然,已经有一个npm package 可以做到这一点,而且使用_.set
from lodash 之类的函数也很容易实现自己的功能。
您不太可能遇到足够长的路径以致最终耗尽堆栈帧,但当然可以使用循环或 trampolines 不递归地实现 setIn
。
最后,如果您喜欢不可变数据并且您希望使用不修改数据结构的setIn
版本,那么您可以查看Zaphod 中的实现——一个javascript 库用于将本机数据结构视为不可变的。
【讨论】:
【参考方案3】:您可以遍历拆分的substrings
和一个临时对象,并检查键是否存在,并构建一个新属性并检查下一个属性是否为有限数,然后分配一个数组,否则分配一个对象。最后用最后一个子字符串将值赋给 temp 对象。
function unflatten(obj)
var result = , temp, substrings, property, i;
for (property in obj)
substrings = property.split('.');
temp = result;
for (i = 0; i < substrings.length - 1; i++)
if (!(substrings[i] in temp))
if (isFinite(substrings[i + 1])) // check if the next key is
temp[substrings[i]] = []; // an index of an array
else
temp[substrings[i]] = ; // or a key of an object
temp = temp[substrings[i]];
temp[substrings[substrings.length - 1]] = obj[property];
return result;
;
var obj2 = "firstName": "John", "lastName": "Green", "car.make": "Honda", "car.model": "Civic", "car.revisions.0.miles": 10150, "car.revisions.0.code": "REV01", "car.revisions.0.changes": "", "car.revisions.1.miles": 20021, "car.revisions.1.code": "REV02", "car.revisions.1.changes.0.type": "asthetic", "car.revisions.1.changes.0.desc": "Left tire cap", "car.revisions.1.changes.1.type": "mechanic", "car.revisions.1.changes.1.desc": "Engine pressure regulator", "visits.0.date": "2015-01-01", "visits.0.dealer": "DEAL-001", "visits.1.date": "2015-03-01", "visits.1.dealer": "DEAL-002" ;
console.log(unflatten(obj2));
.as-console-wrapper max-height: 100% !important; top: 0;
一个稍微紧凑的版本可能是这样的
function unflatten(object)
var result = ;
Object.keys(object).forEach(function (k)
setValue(result, k, object[k]);
);
return result;
function setValue(object, path, value)
var way = path.split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk)
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : );
, object)[last] = value;
var obj2 = "firstName": "John", "lastName": "Green", "car.make": "Honda", "car.model": "Civic", "car.revisions.0.miles": 10150, "car.revisions.0.code": "REV01", "car.revisions.0.changes": "", "car.revisions.1.miles": 20021, "car.revisions.1.code": "REV02", "car.revisions.1.changes.0.type": "asthetic", "car.revisions.1.changes.0.desc": "Left tire cap", "car.revisions.1.changes.1.type": "mechanic", "car.revisions.1.changes.1.desc": "Engine pressure regulator", "visits.0.date": "2015-01-01", "visits.0.dealer": "DEAL-001", "visits.1.date": "2015-03-01", "visits.1.dealer": "DEAL-002" ;
console.log(unflatten(obj2));
.as-console-wrapper max-height: 100% !important; top: 0;
【讨论】:
以上是关于如何将菊花链/点符号中的 JavaScript 对象展开为具有嵌套对象和数组的对象?的主要内容,如果未能解决你的问题,请参考以下文章
通过 Javascript 中的承诺链传递状态都有哪些模式? [复制]