我想将嵌套对象数组转换为二维数组,我想将其用作 Google 表格中应用程序脚本批量更新的一部分。
我在应用程序脚本中使用对象数组作为输入,如下所示:
[
{
a: "val1",
b: {
x: "val2",
y: "val3",
z: "val4"
},
c: {
s: "val5",
t: "val6",
r: "val7"
},
},
{
a: "val8",
b: {
x: "val9",
z: "val10"
},
c: {
t: "val11",
r: "val12"
},
},
{
a: "val13",
b: {
y: "val14"
},
c: {
s: "val15",
t: "val16",
z: "val17"
}
}
]
所以并非所有嵌套对象都有所有键。
由于对象的数量相当大,我正在寻找一种有效的方法来处理和创建一个二维数组,其中第一列保存未嵌套的唯一键,子键分组在一起,而每个其他列保存保存每个对象的值(其中有一个键值对)。如下表:
关于如何快速简洁地做到这一点有什么想法吗?
蒂亚
因此,生成的对象是完整扩展的 id 及其值列表的映射,作为数组,槽位置映射到相应的列。
展示比解释更容易...
所以这里有一个
fillBag
函数,它将迭代传递的对象属性(递归地),同时填充传递的
bag
对象来保存最终结果:
//this function is fully commented in the live snippet
function fillBag(prefix, colIndex, obj, bag){
Object.entries(obj).forEach(
([key, value])=>{
const index = `${prefix}${key}`;
if(typeof(value) === 'string' || value instanceof String){
const values = (index in bag) ? bag[index] : bag[index] = [];
values.length = colIndex;
values.push(value);
}else{
fillBag(index, colIndex, value, bag);
}
});
}
我使用了与您相同的输入数组,但我必须进行一些更正,因为您有一些语法错误缺少关闭}
。这是最终返回的对象:
{
"a": [ "val1", "val8", "val13" ],
"bx": [ "val2", "val9", undefined],
"by": [ "val3", undefined, "val14" ],
"bz": [ "val4", "val10", undefined ]
"cs": [ "val5", undefined, "val15" ],
"ct": [ "val6", "val11", "val16" ],
"cr": [ "val7", "val12", undefined ],
"cz": [ undefined, undefined, "val17" ]
}
如果您需要数组的数组,而不是将唯一键映射到数组值的对象,您还可以在此基础上使用 Object.entries
:
Object.entries($bag) //...[ key, [values] ]
警告现在考虑到后面的列可能会堆积的事实,如果给定的索引不再显示,则该索引的值数组将不会包含最后一列后面的索引的未定义值属性名称最后一次出现的地方。为了纠正这种行为,在处理整个对象后最后一次访问生成的包,并且所有数组都根据最大数组的大小(总列数)调整大小。
function fillTrailingEmptyColumns(obj, cols){
Object.values(obj).forEach( values =>{
values.length = cols;
});
}
稀疏数组和设置 .length 属性进一步阅读稀疏数组和空槽可能会很有趣。
大多数人会感到非常惊讶,你实际上可以设置
Array.length
属性
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays
const data = [
{
a:"val1",
b: {
x:"val2",
y:"val3",
z:"val4"
},
c: {
s:"val5",
t:"val6",
r:"val7"
}
},
{
a:"val8",
b:{
x:"val9",
z:"val10"
},
c:{
t:"val11",
r:"val12"
}
},
{
a:"val13",
b:{
y:"val14"
},
c:{
s:"val15",
t:"val16",
z:"val17"
}
}
]
const bag = {};
//fills the bag with key->values
data.forEach( (obj, i) => fillBag('', i, obj, bag));
//this is an information I could grab from data.length as well
const cols = findMaxColumns(bag);
//fills the trailing missing columns in bag for each key
fillTrailingEmptyColumns(bag, cols);
console.log(bag);
//returns the maximum number of columns descrived in the obj
function findMaxColumns(obj){
return Object.values(obj).reduce(
(max, value)=>{
if(value.length > max)
return value.length;
else
return max;
},
0
);
}
function fillTrailingEmptyColumns(obj, cols){
Object.values(obj).forEach( values =>{
values.length = cols;
});
}
//fills the bag object with values coming from obj at the given prefix and colIndex
function fillBag(prefix, colIndex, obj, bag){
//for each entry in obj (an entry is key-value property pair)
Object.entries(obj).forEach(
([key, value])=>{
//current index based on prefix
const index = `${prefix}${key}`;
//if the current property value is a string
if(typeof(value) === 'string' || value instanceof String){
//if composed key exists in bag
if(index in bag){
let values = bag[index];
//change the size of values in the array based on the current column
values.length = colIndex;
//push the current value in the array of values for this key
values.push(value);
}
//else if composed key doesn't exist yet in bag
else{
//create a new array filled with nulls until now
let values = [];
//change the size of values in the array based on the current column
values.length = colIndex;
//push the current value in the array of values for this key
values.push(value);
//sets the key in the bag for its first time
bag[index] = values;
}
}
//else if the obj is not a string (thus a nested properties bag)
else{
//recursively call fillBag with the current composed index and passing the current value
fillBag(index, colIndex, value, bag);
}
});
}