如何将 daisy-chain/dot 符号中的 JavaScript 对象展开为具有嵌套对象和数组的对象?
How to unflatten a JavaScript object in a daisy-chain/dot notation into an object with nested objects and arrays?
我想像这样展开对象...
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;
};
为了嵌套对象和数组,我很快就开始不必要地重复代码。这绝对是需要递归的东西。有什么想法吗?
编辑:我也在 .
中问过相反的问题,扁平化
尝试将问题分解为两个不同的挑战:
- 通过路径设置值
- 遍历对象并逐个展开键
您可以从一个看起来像这样的 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
函数结合使用,该函数为每个键循环对象 运行ning setIn
。
function unflatten(flattened) {
let object = {};
for (let key in flattened) {
let path = key.split('.');
setIn(path, object, flattened[key]);
}
return object;
}
当然,已经有一个 npm package for doing this, and it'd also it'd be easy to implement your own using functions like _.set
from lodash。
您不太可能 运行 进入足够长的路径以致于最终 运行 退出堆栈帧,但是当然可以实现 setIn
没有递归,使用循环或 trampolines.
最后,如果不可变数据是你的事并且你想使用不修改你的数据结构的 setIn
版本,那么你可以看看 [=20] 中的实现=]—一个 JavaScript 库,用于将本机数据结构视为不可变的。
您可以遍历拆分的 substrings
和一个临时对象,检查密钥是否存在,然后构建一个新的 属性,检查下一个 属性 是否是有限数,然后分配一个数组,否则分配一个对象。最后用最后一个子字符串将值赋给临时对象。
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; }
您可以先使用 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))
我想像这样展开对象...
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;
};
为了嵌套对象和数组,我很快就开始不必要地重复代码。这绝对是需要递归的东西。有什么想法吗?
编辑:我也在
尝试将问题分解为两个不同的挑战:
- 通过路径设置值
- 遍历对象并逐个展开键
您可以从一个看起来像这样的 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
函数结合使用,该函数为每个键循环对象 运行ning setIn
。
function unflatten(flattened) {
let object = {};
for (let key in flattened) {
let path = key.split('.');
setIn(path, object, flattened[key]);
}
return object;
}
当然,已经有一个 npm package for doing this, and it'd also it'd be easy to implement your own using functions like _.set
from lodash。
您不太可能 运行 进入足够长的路径以致于最终 运行 退出堆栈帧,但是当然可以实现 setIn
没有递归,使用循环或 trampolines.
最后,如果不可变数据是你的事并且你想使用不修改你的数据结构的 setIn
版本,那么你可以看看 [=20] 中的实现=]—一个 JavaScript 库,用于将本机数据结构视为不可变的。
您可以遍历拆分的 substrings
和一个临时对象,检查密钥是否存在,然后构建一个新的 属性,检查下一个 属性 是否是有限数,然后分配一个数组,否则分配一个对象。最后用最后一个子字符串将值赋给临时对象。
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; }
您可以先使用 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))