将 JSON 对象转换为嵌套值、标签、子项

Transform JSON object to nested value, label, children

我有以下 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

的解决方案

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);

这是我会做的:

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

这与调用 Array.flat() 相同(仅在逻辑上),我从 object 开始删除 notebooksmartphone 键,合并所有嵌套的 objects 在同一个数组中。其实你不需要它们,你只是对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) 对)尚不存在时,您才想将新的 child 附加到 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);
  }
}

为初始数组中的每个 object (cur) 调用此方法,其目的是用确定的正确级别的条目填充 children 数组通过 hierarchyOrder[hierarchyLevel]。为了避免重复children我们使用前面介绍的方法,即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);


一些最后的想法:

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

这是我的方法。它使用内置数组函数,如 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 等。它 returns 一个数组,其中包含您作为参数传递的所有键值。例如
    _get("companies")   

returns

{
    "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; }

请注意 Stack overflow 代码段可能会遗漏一些重复的对象。自己试试就更好了