【问题标题】:Reduce array of objects with deeply literals keys in JS在 JS 中使用深度文字键减少对象数组
【发布时间】:2021-03-24 09:33:13
【问题描述】:

我对使用 reduce 方法进行数组转换感到困惑。我不知道如何用文字键处理嵌套对象。

为了带来一些优势,我会发布一些我写的例子,它工作正常:

// consider flat array of objects
const names = [
{ name: "Jaden", sname: "Smith", age: 33 }, 
{ name: "Will", sname: "Smith", age: 12 },
{ name: "Jada", sname: "Smith", age: 29 }, 
{ name: "Jose", sname: "Varho", age: 21 },
{ name: "Josephina", sname: "Varho", age: 44 }, 
{ name: "Keanu ", sname: "Reeves", age: 44 }] ;

// suppose i need to transform that array to this shape:
/* {
    "Smith": {
        "Jaden": 33,
        "Will": 12,
        "Jada": 29
    },
    "Varho": {
        "Jose": 21,
        "Josephina": 44
    },
    "Reeves": {
        "Keanu ": 44
    }
}

*/

// this reducer do it's fine:
const shapeIt = (acc, item) => {
    console.log('acc:', JSON.stringify(acc));
    acc[item.sname] = { ...acc[item.sname], [item.name]: item.age }
    return acc
}

const transformShape= (arr) => {
    return arr.reduce((acc, item) => shapeIt(acc, item), {});
}

transformShape(names); //gives required shape

所以现在,让我们想象一下我有更复杂的数组,例如:

 const flatArray = [
{ "blockId": "first-block", "sectionId": "first-section", "fieldId": "_1", "value": "0" }, 
{ "blockId": "first-block", "sectionId": "first-section", "fieldId": "_2", "value": "1" }, 
{ "blockId": "first-block", "sectionId": "second-section", "fieldId": "_1", "value": "1" }, 
{ "blockId": "second-block", "sectionId": "first-section", "fieldId": "_1", "value": "1" }, 
{ "blockId": "second-block", "sectionId": "some-section", "fieldId": "_2", "value": "3" },
{ "blockId": "third-block", "sectionId": "other-section", "fieldId": "_1", "value": "3" }];

// and i strictly need to get this shape of object:

/* {
    "first-block": {
        "first-section": {
            "_1": "0",
            "_2": "1"
        },
        "second-section": {
            "_1": "1"
        }
    },
    "second-block": {
        "first-section": {
            "_1": "1"
        },
        "some-section": {
            "_2": "3"
        }
    },
    "third-block": {
        "other-section": {
            "_1": "3"
        }
    }
}
*/

此时我正在编写这种 reduce 函数。它有效,但它只给了我块中每个部分的最后一个 fieldId 键。如果一个块中的部分有多个fieldId - 它会丢失它。我在看累加器,发现只有具有不同 blockId、sectionId 的键被累加,但fieldId 的键没有不同。


const shapeComplex = (acc, item) => {
    console.log('acc:', JSON.stringify(acc));
    acc[item.blockId] = { ...acc[item.blockId], [item.sectionId]: { [item.fieldId]: item.value } }

    return acc
}

const transformComplex = (arr) => {
    console.log('initialArr: ', arr)
    return arr.reduce((acc, item) => shapeComplex(acc, item), {});
}

transformComplex(flatArray); 
// it gives me shape with only last idField in same section and block:

/*
{
    "first-block": {
        "first-section": {
            "_1": "0"
        },
        "second-section": {
            "_1": "1"
        }
    },
    "second-block": {
        "first-section": {
            "_1": "1"
        },
        "some-section": {
            "_2": "3"
        }
    },
    "third-block": {
        "other-section": {
            "_1": "3"
        }
    }
}
*/

请帮忙。

【问题讨论】:

    标签: javascript arrays dictionary object reduce


    【解决方案1】:

    您可以使用一组想要的键进行分组,然后将值或新对象用于下一级。

    const
        data = [{ blockId: "first-block", sectionId: "first-section", fieldId: "_1", value: "0" }, { blockId: "first-block", sectionId: "first-section", fieldId: "_2", value: "1" }, { blockId: "first-block", sectionId: "second-section", fieldId: "_1", value: "1" }, { blockId: "second-block", sectionId: "first-section", fieldId: "_1", value: "1" }, { blockId: "second-block", sectionId: "some-section", fieldId: "_2", value: "3" }, { blockId: "third-block", sectionId: "other-section", fieldId: "_1", value: "3" }],
        keys = ['blockId', 'sectionId', 'fieldId'],
        result = data.reduce((r, o) => {
            keys.reduce(
                (q, k, i, { length }) => q[o[k]] ??= i + 1 === length ? o.value : {},
                r
            );
            return r;
        }, {});
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    获取对象的所有值并将最后一项分配为嵌套最多的对象的值。

    这种方法依赖于每个对象中值的顺序。

    const
        data = [{ blockId: "first-block", sectionId: "first-section", fieldId: "_1", value: "0" }, { blockId: "first-block", sectionId: "first-section", fieldId: "_2", value: "1" }, { blockId: "first-block", sectionId: "second-section", fieldId: "_1", value: "1" }, { blockId: "second-block", sectionId: "first-section", fieldId: "_1", value: "1" }, { blockId: "second-block", sectionId: "some-section", fieldId: "_2", value: "3" }, { blockId: "third-block", sectionId: "other-section", fieldId: "_1", value: "3" }],
        result = data.reduce((r, o) => {
            const
                values = Object.values(o),
                value = values.pop();
            values.reduce(
                (q, k, i, { length }) => q[k] ??= i + 1 === length ? value : {},
                r
            );
            return r;
        }, {});
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

    • 这很有趣,遗憾的是我无法将“键”收集为数组,因为它是动态的,将来可能会发生变化。
    • 你怎么知道要分组?
    • @northernwind 如果它是动态的并且将来可以更改,那么最好为其设置一个数组值?
    • 我简化了我的问题。真正的逻辑更复杂,不幸的是,这对我来说不是这样。但我并没有说错。
    • 我需要读什么样的书才能记住这一点?
    【解决方案2】:

    您还需要复制最里面(部分)对象的属性(字段):

    const shapeComplex = (acc, item) => {
        console.log('acc:', JSON.stringify(acc));
        acc[item.blockId] = {
            ...acc[item.blockId],
            [item.sectionId]: {
                ...acc[item.blockId]?.[item.sectionId],
    //          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                [item.fieldId]: item.value
            }
        };
        return acc
    }
    

    不过,我建议要么始终使用在每个级别上散布的对象

    const shapeComplex = (acc, item) => {
        return {
            ...acc,
            [item.blockId]: {
                ...acc[item.blockId],
                [item.sectionId]: {
                    ...acc[item.blockId]?.[item.sectionId],
                    [item.fieldId]: item.value
                }
            }
        };
    }
    

    或使用突变:

    const shapeComplex = (acc, {blockId, sectionId, fieldId, value}) => {
        const block = acc[blockId] ?? (acc[blockId] = {});
        const section = block[sectionId] ?? (block[sectionId] = {});
        section[fieldId] = value;
        return acc;
    }
    

    正如@Nina 在此处的答案中所示,您可以将其推广到任意深度的作业。

    【讨论】:

    • hm...这似乎就像在我的情况下一样工作....我只得到最后一个 fieldId
    • @northernwind 哎呀,已修复
    • 仍然只得到最后一项......我花了几个小时,需要休息以进一步测试它
    • ups...第一个编辑的样本有效,但不是第二个
    • @northernwind 这就是一个人从匆忙修复中得到的……我只更新了第一个 sn-p bust 在第二个中错过了相同的修复。第三个还有另一个错字。现在测试了所有三个并修复了答案。
    【解决方案3】:

    您可以执行以下操作,

    const flatArray = [
    { "blockId": "first-block", "sectionId": "first-section", "fieldId": "_1", "value": "0" }, 
    { "blockId": "first-block", "sectionId": "first-section", "fieldId": "_2", "value": "1" }, 
    { "blockId": "first-block", "sectionId": "second-section", "fieldId": "_1", "value": "1" }, 
    { "blockId": "second-block", "sectionId": "first-section", "fieldId": "_1", "value": "1" }, 
    { "blockId": "second-block", "sectionId": "some-section", "fieldId": "_2", "value": "3" },
    { "blockId": "third-block", "sectionId": "other-section", "fieldId": "_1", "value": "3" }];
    
    
    const shapeComplex = (acc, item) => {
        
        if(acc[item.blockId]) {
            acc[item.blockId] = { ...acc[item.blockId], [item.sectionId]: {...acc[item.blockId][item.sectionId], [item.fieldId]: item.value } }
        } else {
            acc[item.blockId] = { ...acc[item.blockId], [item.sectionId]: {[item.fieldId]: item.value } }
        }
        return acc
    }
    
    const transformComplex = (arr) => {
        
        return arr.reduce((acc, item) => shapeComplex(acc, item), {});
    }
    
    console.log(transformComplex(flatArray));

    【讨论】:

    • 目前这只是我的一个工作代码。我不知道谁投反对票,我看不出有什么问题
    【解决方案4】:

    您正在从平面数据结构中创建一棵树。指定树层次结构(级别)和 用于创建叶节点数据元素的属性。

    例子:

        function treeFrom (arr, levels, name, value) {
          return arr.reduce( 
            (root,item) => 
            {          
              var node = root;
              for (let level of levels) /* descend to bottom tier */
              {
                levelvalue = item[level];
                if (! node[levelvalue]) node[levelvalue] = {};
                node = node[levelvalue];
              }
              node[item[name]] = item[value];
    
              return root;
            }
          , 
            {}
          )
        }
        
        console.log ( treeFrom(flatArray, ['blockId', 'sectionId'], 'fieldId', 'value') );
    

    【讨论】:

      猜你喜欢
      • 2019-07-26
      • 2011-03-06
      • 1970-01-01
      • 2017-10-02
      • 2018-10-20
      • 1970-01-01
      • 2019-05-22
      • 2016-12-01
      • 2018-05-30
      相关资源
      最近更新 更多