【问题标题】:Transform JSON object to nested value, label, children将 JSON 对象转换为嵌套值、标签、子项
【发布时间】:2021-10-18 19:35:18
【问题描述】:

我有以下 JSON:

{
  "notebook": [{
      "categories": [{
        "key": "notebook",
        "value": "Notebook"
      }],
      "companies": [{
        "key": "apple",
        "value": "Apple"
      }],
      "brands": [{
        "key": "macbook",
        "value": "MacBook"
      }],
      "modelYears": [{
        "key": "2021",
        "value": "2021"
      }],
      "models": [{
        "key": "2021 Pro",
        "value": "2021 Pro"
      }]
    },
    {
      "categories": [{
        "key": "notebook",
        "value": "Notebook"
      }],
      "companies": [{
          "key": "ibm",
          "value": "IBM"
        },
        {
          "key": "lenovo",
          "value": "Lenovo"
        }
      ],
      "brands": [{
        "key": "thinkpad",
        "value": "Thinkpad"
      }],
      "modelYears": [{
        "key": "2019",
        "value": "2019"
      }],
      "models": [{
        "key": "e590",
        "value": "E590"
      }]
    },
    {
      "categories": [{
        "key": "notebook",
        "value": "Notebook"
      }],
      "companies": [{
          "key": "ibm",
          "value": "IBM"
        },
        {
          "key": "lenovo",
          "value": "Lenovo"
        }
      ],
      "brands": [{
        "key": "thinkpad",
        "value": "Thinkpad"
      }],
      "modelYears": [{
        "key": "2019",
        "value": "2019"
      }],
      "models": [{
        "key": "p1",
        "value": "P1"
      }]
    }
  ],
  "smartphone": [{
      "categories": [{
        "key": "smartphone",
        "value": "Smartphone"
      }],
      "companies": [{
        "key": "apple",
        "value": "Apple"
      }],
      "brands": [{
        "key": "iphone",
        "value": "IPHONE"
      }],
      "modelYears": [{
        "key": "2019",
        "value": "2019"
      }],
      "models": [{
        "key": "11pro",
        "value": "11 Pro"
      }]
    },
    {
      "categories": [{
        "key": "smartphone",
        "value": "Smartphone"
      }],
      "companies": [{
        "key": "apple",
        "value": "Apple"
      }],
      "brands": [{
        "key": "iphone",
        "value": "IPHONE"
      }],
      "modelYears": [{
        "key": "2020",
        "value": "2020"
      }],
      "models": [{
        "key": "12",
        "value": "12"
      }]
    }
  ]
}

我想要的结构是:

[
  {
    value: "notebook",
    label: "Notebook",
    children: [
      {
        value: "apple",
        label: "Apple",
        children: [
          {
            value: "macbook",
            label: "MacBook",
            children: [
              {
                value: "2021pro",
                label: "2021 Pro",
              },
              {
                value: "2019",
                label: "2019",
              },
            ],
          },
        ],
      },
      {
        value: "ibm",
        label: "IBM",
        children: [
          {
            value: "thinkpad",
            label: "Thinkpad",
            children: [
              {
                value: "e590",
                label: "E590",
              },
              {
                value: "p1",
                label: "P1",
              },
            ],
          },
        ],
      },
      {
        value: "lenovo",
        label: "Lenovo",
        children: [
          {
            value: "thinkpad",
            label: "Thinkpad",
            children: [
              {
                value: "e590",
                label: "E590",
              },
              {
                value: "p1",
                label: "P1",
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: "Smartphone",
    label: "Smartphone",
    children: [
      {
        value: "apple",
        label: "Apple",
        children: [
          {
            value: "iphone",
            label: "IPHONE",
            children: [
              {
                value: "11pro",
                label: "11 Pro",
              },
              {
                value: "12",
                label: "12",
              },
            ],
          },
        ],
      },
    ],
  },
];

如何做到这一点?

使用 Array.prototype.reduce / Array.prototype.map 寻找解决方案

【问题讨论】:

    标签: javascript mapping reduce


    【解决方案1】:

    这是我的方法。它使用内置数组函数,如reducefiltermap

    const out = Object.values(input).map(e => {
        const _get = key => ({ [key]: e.map(m => [...m[key]]).flat()
            .filter((m, i, a) => !a.slice(i + 1).find(c => c.key === m.key)) });
        const _join = (parent, children) => {
            const [pkey, ckey] = [Object.keys(parent)[0], Object.keys(children)[0]];
            return {
                [pkey]: parent[pkey].map(p => ({ value: p.key, label: p.value,
                        children: e.reduce((o, m) =>
                            o.concat(m[pkey].find(pp => pp.key === p.key) ?
                                children[ckey].filter(child => m[ckey]
                                    .find(mm => mm.key === (child.key || child.value)) &&
                                    !o.find(c => (c.key || c.value) === (child.key || child.value))) : []), [])
                    })
                )
            };
        };
        return { value: e[0].categories[0].key, label: e[0].categories[0].value,
            children: Object.values(_join(_get("companies"), _join(_get("brands"), _get("models"))))[0] };
    });
    

    它有两个功能:

    1. _get()
    2. _join()
    1. 当调用_get() 时,您将要搜索的键作为参数传递。它可以是companiesbrandsmodels 等。它返回一个数组,其中包含您作为参数传递的所有键值的出现。例如
        _get("companies")   
    

    返回

    {
        "companies": [
            {
                "key": "apple",
                "value": "Apple"
            },
            {
                "key": "ibm",
                "value": "IBM"
            },
            {
                "key": "lenovo",
                "value": "Lenovo"
            }
        ]
    }
    

    1. _join() 函数具有魔力。它需要一个父组和一个子组,并将它们转换为您想要的格式:

    这里是一个工作示例

    const input = {
      "notebook": [{
          "categories": [{
            "key": "notebook",
            "value": "Notebook"
          }],
          "companies": [{
            "key": "apple",
            "value": "Apple"
          }],
          "brands": [{
            "key": "macbook",
            "value": "MacBook"
          }],
          "modelYears": [{
            "key": "2021",
            "value": "2021"
          }],
          "models": [{
            "key": "2021 Pro",
            "value": "2021 Pro"
          }]
        },
        {
          "categories": [{
            "key": "notebook",
            "value": "Notebook"
          }],
          "companies": [{
              "key": "ibm",
              "value": "IBM"
            },
            {
              "key": "lenovo",
              "value": "Lenovo"
            }
          ],
          "brands": [{
            "key": "thinkpad",
            "value": "Thinkpad"
          }],
          "modelYears": [{
            "key": "2019",
            "value": "2019"
          }],
          "models": [{
            "key": "e590",
            "value": "E590"
          }]
        },
        {
          "categories": [{
            "key": "notebook",
            "value": "Notebook"
          }],
          "companies": [{
              "key": "ibm",
              "value": "IBM"
            },
            {
              "key": "lenovo",
              "value": "Lenovo"
            }
          ],
          "brands": [{
            "key": "thinkpad",
            "value": "Thinkpad"
          }],
          "modelYears": [{
            "key": "2019",
            "value": "2019"
          }],
          "models": [{
            "key": "p1",
            "value": "P1"
          }]
        }
      ],
      "smartphone": [{
          "categories": [{
            "key": "smartphone",
            "value": "Smartphone"
          }],
          "companies": [{
            "key": "apple",
            "value": "Apple"
          }],
          "brands": [{
            "key": "iphone",
            "value": "IPHONE"
          }],
          "modelYears": [{
            "key": "2019",
            "value": "2019"
          }],
          "models": [{
            "key": "11pro",
            "value": "11 Pro"
          }]
        },
        {
          "categories": [{
            "key": "smartphone",
            "value": "Smartphone"
          }],
          "companies": [{
            "key": "apple",
            "value": "Apple"
          }],
          "brands": [{
            "key": "iphone",
            "value": "IPHONE"
          }],
          "modelYears": [{
            "key": "2020",
            "value": "2020"
          }],
          "models": [{
            "key": "12",
            "value": "12"
          }]
        }
      ]
    }
    
    const out = Object.values(input).map(e => {
        const _get = key => ({ [key]: e.map(m => [...m[key]]).flat()
            .filter((m, i, a) => !a.slice(i + 1).find(c => c.key === m.key)) });
        const _join = (parent, children) => {
            const [pkey, ckey] = [Object.keys(parent)[0], Object.keys(children)[0]];
            return {
                [pkey]: parent[pkey].map(p => ({ value: p.key, label: p.value,
                        children: e.reduce((o, m) =>
                            o.concat(m[pkey].find(pp => pp.key === p.key) ?
                                children[ckey].filter(child => m[ckey]
                                    .find(mm => mm.key === (child.key || child.value)) &&
                                    !o.find(c => (c.key || c.value) === (child.key || child.value))) : []), [])
                    })
                )
            };
        };
        return { value: e[0].categories[0].key, label: e[0].categories[0].value,
            children: Object.values(_join(_get("companies"), _join(_get("brands"), _get("models"))))[0] };
    });
    
    console.log(out);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    考虑到堆栈溢出 sn-p 可能会省略一些重复的对象。自己试试就更好了

    【讨论】:

      【解决方案2】:

      const data = {
        "notebook": [{
            "categories": [{
              "key": "notebook",
              "value": "Notebook"
            }],
            "companies": [{
              "key": "apple",
              "value": "Apple"
            }],
            "brands": [{
              "key": "macbook",
              "value": "MacBook"
            }],
            "modelYears": [{
              "key": "2021",
              "value": "2021"
            }],
            "models": [{
              "key": "2021 Pro",
              "value": "2021 Pro"
            }]
          },
          {
            "categories": [{
              "key": "notebook",
              "value": "Notebook"
            }],
            "companies": [{
                "key": "ibm",
                "value": "IBM"
              },
              {
                "key": "lenovo",
                "value": "Lenovo"
              }
            ],
            "brands": [{
              "key": "thinkpad",
              "value": "Thinkpad"
            }],
            "modelYears": [{
              "key": "2019",
              "value": "2019"
            }],
            "models": [{
              "key": "e590",
              "value": "E590"
            }]
          },
          {
            "categories": [{
              "key": "notebook",
              "value": "Notebook"
            }],
            "companies": [{
                "key": "ibm",
                "value": "IBM"
              },
              {
                "key": "lenovo",
                "value": "Lenovo"
              }
            ],
            "brands": [{
              "key": "thinkpad",
              "value": "Thinkpad"
            }],
            "modelYears": [{
              "key": "2019",
              "value": "2019"
            }],
            "models": [{
              "key": "p1",
              "value": "P1"
            }]
          }
        ],
        "smartphone": [{
            "categories": [{
              "key": "smartphone",
              "value": "Smartphone"
            }],
            "companies": [{
              "key": "apple",
              "value": "Apple"
            }],
            "brands": [{
              "key": "iphone",
              "value": "IPHONE"
            }],
            "modelYears": [{
              "key": "2019",
              "value": "2019"
            }],
            "models": [{
              "key": "11pro",
              "value": "11 Pro"
            }]
          },
          {
            "categories": [{
              "key": "smartphone",
              "value": "Smartphone"
            }],
            "companies": [{
              "key": "apple",
              "value": "Apple"
            }],
            "brands": [{
              "key": "iphone",
              "value": "IPHONE"
            }],
            "modelYears": [{
              "key": "2020",
              "value": "2020"
            }],
            "models": [{
              "key": "12",
              "value": "12"
            }]
          }
        ]
      };
      
      const transforemd = Object.entries(data).map(([product, data]) => {
          const item = {
            children: []
          };
          
          data.forEach(value => {
            item.value = value.categories?.[0].key;
            item.label = value.categories?.[0].value;
           
            value.companies?.forEach(company => {
              let sub = {
                value: company.key,
                label: company.value,
                children: []
              };
              let toFind = item.children.find(element => element.value === company.key);
              if (toFind) 
                sub = toFind;
              else 
                item.children.push(sub);
                
              value.brands?.forEach(brand => {
                let entry = {
                  value: brand.key,
                  label: brand.value,
                  children: []
                };
                let toFind = sub.children.find(element => element.value === brand.key);
                
                if (toFind) 
                  entry = toFind;
                else 
                  sub.children.push(entry);
                
                value.models?.forEach(model => {
                  let product = {
                    value: model.key,
                    label: model.value,
                  };
                  entry.children.push(product);
                });
              });
            });
          });
          
          
          return item;
      });
      
      console.log(transforemd);

      【讨论】:

      • 绝对有帮助。谢谢 !这也可以通过使用 Array.prototype 来完成。功能 ?也许 map 或 reduce ?这些正常的 for 循环我真的不喜欢
      • @Gutelaunetyp 你不能使用 Array#map 因为关系不是一对一的。但我们绝对可以将所有 for of s 替换为 Array#forEach。更新了!
      • 我会使用reduce()
      • @AdamPatterson 你能描述一下我们如何使用reduce吗?
      【解决方案3】:

      我会这样做:

      • 首先我会稍微更改初始数据:
      mydata = [...mydata.notebook, ...mydata.smartphone];
      

      这与调用Array.flat() 相同(仅在逻辑上),我将从起始对象中删除notebooksmartphone 键,将所有嵌套对象合并到同一个数组中。其实你不需要,只是对category感兴趣。

      • 下一部分可能是创建一些实用方法,例如:
      function getOrCreateChild(children, entry) {
        let child = children.find(c => c.value === entry.key && c.label === entry.value);
        
        if (child === undefined) {
          child = {
            value: entry.key,
            label: entry.value,
            children: []
          }
          
          children.push(child);
        }
         
        return child;
      }
      

      只有当被视为对 (value, label) 的已访问键尚不存在时,您才希望将新子级附加到 children

      另外一个包含主要逻辑的是:

      const hierarchyOrder = ["categories", "companies", "brands", "models"];
      function fillNestedLevels(children, cur, hierarchyLevel) {
        if (hierarchyLevel === hierarchyOrder.length)
          return;
        
        const entryArray = cur[hierarchyOrder[hierarchyLevel]];
          
        for (const entry of entryArray) {
          let child = getOrCreateChild(children, entry);
          fillNestedLevels(child.children, cur, hierarchyLevel + 1);
        }
      }
      

      为初始数组中的每个对象(cur)调用此方法,其目的是用正确级别的条目填充children 数组,该级别由hierarchyOrder[hierarchyLevel] 确定。为了避免重复的孩子我们使用前面描述的方法,即getOrCreateChild

      最后,入口点是一个reduce 函数,它只使用累加器(以空数组开头)和一个数组元素调用上述方法。

      const result = mydata.reduce((acc, cur) => {
        fillNestedLevels(acc, cur, 0);
        return acc;
      }, [])
      

      大家一起:

      let mydata = {
        "notebook": [{
            "categories": [{
              "key": "notebook",
              "value": "Notebook"
            }],
            "companies": [{
              "key": "apple",
              "value": "Apple"
            }],
            "brands": [{
              "key": "macbook",
              "value": "MacBook"
            }],
            "modelYears": [{
              "key": "2021",
              "value": "2021"
            }],
            "models": [{
              "key": "2021 Pro",
              "value": "2021 Pro"
            }]
          },
          {
            "categories": [{
              "key": "notebook",
              "value": "Notebook"
            }],
            "companies": [{
                "key": "ibm",
                "value": "IBM"
              },
              {
                "key": "lenovo",
                "value": "Lenovo"
              }
            ],
            "brands": [{
              "key": "thinkpad",
              "value": "Thinkpad"
            }],
            "modelYears": [{
              "key": "2019",
              "value": "2019"
            }],
            "models": [{
              "key": "e590",
              "value": "E590"
            }]
          },
          {
            "categories": [{
              "key": "notebook",
              "value": "Notebook"
            }],
            "companies": [{
                "key": "ibm",
                "value": "IBM"
              },
              {
                "key": "lenovo",
                "value": "Lenovo"
              }
            ],
            "brands": [{
              "key": "thinkpad",
              "value": "Thinkpad"
            }],
            "modelYears": [{
              "key": "2019",
              "value": "2019"
            }],
            "models": [{
              "key": "p1",
              "value": "P1"
            }]
          }
        ],
        "smartphone": [{
            "categories": [{
              "key": "smartphone",
              "value": "Smartphone"
            }],
            "companies": [{
              "key": "apple",
              "value": "Apple"
            }],
            "brands": [{
              "key": "iphone",
              "value": "IPHONE"
            }],
            "modelYears": [{
              "key": "2019",
              "value": "2019"
            }],
            "models": [{
              "key": "11pro",
              "value": "11 Pro"
            }]
          },
          {
            "categories": [{
              "key": "smartphone",
              "value": "Smartphone"
            }],
            "companies": [{
              "key": "apple",
              "value": "Apple"
            }],
            "brands": [{
              "key": "iphone",
              "value": "IPHONE"
            }],
            "modelYears": [{
              "key": "2020",
              "value": "2020"
            }],
            "models": [{
              "key": "12",
              "value": "12"
            }]
          }
        ]
      }
      
      mydata = [...mydata.notebook, ...mydata.smartphone];
      const hierarchyOrder = ["categories", "companies", "brands", "models"];
      
      function getOrCreateChild(children, entry) {
        let child = children.find(c => c.value === entry.key && c.label === entry.value);
        
        if (child === undefined) {
          child = {
            value: entry.key,
            label: entry.value,
            children: []
          }
          
          children.push(child);
        }
         
        return child;
      }
      
      function fillNestedLevels(children, cur, hierarchyLevel) {
        if (hierarchyLevel === hierarchyOrder.length)
          return;
        
        const entryArray = cur[hierarchyOrder[hierarchyLevel]];
          
        for (const entry of entryArray) {
          let child = getOrCreateChild(children, entry);
          fillNestedLevels(child.children, cur, hierarchyLevel + 1);
        }
      }
      
      const result = mydata.reduce((acc, cur) => {
        fillNestedLevels(acc, cur, 0);
        return acc;
      }, []);
      
      console.log(result);

      一些最后的想法:

      • 它看起来有很多代码,但非常简单且易于维护。在这种情况下,我不鼓励您搜索对您和您的同事来说可能非常难以理解的“单线”解决方案。
      • 这也是一个非常灵活的解决方案。如果将来您想包含modelYears,您只需将其附加到hierarchyOrder
      • 为了保持一致性,即使是最嵌套的对象也具有children 属性。这当然是您可以控制的,但我想忽略它们不会有任何影响。

      【讨论】:

      • 谢谢。你能不能给我提供“复杂”的代码?
      • 你不应该要求别人写更糟糕的代码:) 关键是:如果我想重写它,性能不会改变,但可读性会降低。你为什么想要那个?
      • @Gutelaunetyp 感谢您的赏金,很抱歉,但公认的解决方案确实表现不佳。我不评论可读性,因为它是主观的,但如果你在生产环境中使用它,你会后悔的。
      猜你喜欢
      • 2019-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-02
      • 1970-01-01
      • 1970-01-01
      • 2019-09-01
      • 1970-01-01
      相关资源
      最近更新 更多