首页 > 解决方案 > 按对象数组javascript中的多个键分组

问题描述

我有一个嵌套的对象数组,如下所示。

[
    {
        "region": null,
        "country": null,
        "territory": "Worldwide",
        "territoryCode": "ALL",
        "t2": null,
        "t3": null,
        "t4": null,
        "t5": null,
        "t6": null,
        "t7": null,
        "localLanguage": {
            "territoryId": 1,
            "localLanguageName": "N/A",
            "localLanguageCode": null
        }
    },
    {
        "region": "Africa",
        "country": "Madagascar",
        "territory": null,
        "territoryCode": "MG",
        "t2": "AFR",
        "t3": "MG",
        "t4": null,
        "t5": null,
        "t6": null,
        "t7": null,
        "localLanguage": {
            "territoryId": 30,
            "localLanguageName": "Malagasy, French",
            "localLanguageCode": "MLG, FRE"
        }
    },
    {
        "region": "Africa",
        "country": null,
        "territory": null,
        "territoryCode": "AFR",
        "t2": "AFR",
        "t3": null,
        "t4": null,
        "t5": null,
        "t6": null,
        "t7": null,
        "localLanguage": {
            "territoryId": 2,
            "localLanguageName": "N/A",
            "localLanguageCode": null
        }
    },
    {
        "region": "Africa",
        "country": "Morocco (incl. Western Sahara)",
        "territory": null,
        "territoryCode": "MA",
        "t2": "AFR",
        "t3": "MA",
        "t4": null,
        "t5": null,
        "t6": null,
        "t7": null,
        "localLanguage": {
            "territoryId": 35,
            "localLanguageName": "Arabic, French",
            "localLanguageCode": "ARA, FRE"
        }
    },
    {
        "region": "Africa",
        "country": "Morocco (incl. Western Sahara)",
        "territory": "Morocco (excl. Western Sahara)",
        "territoryCode": "MAXEH",
        "t2": "AFR",
        "t3": "MA",
        "t4": "MAXEH",
        "t5": null,
        "t6": null,
        "t7": null,
        "localLanguage": {
            "territoryId": 36,
            "localLanguageName": "Arabic, French",
            "localLanguageCode": "ARA, FRE"
        }
    },
    {
        "region": "Africa",
        "country": "Morocco (incl. Western Sahara)",
        "territory": "Western Sahara",
        "territoryCode": "EH",
        "t2": "AFR",
        "t3": "MA",
        "t4": "EH",
        "t5": null,
        "t6": null,
        "t7": null,
        "localLanguage": {
            "territoryId": 37,
            "localLanguageName": "Arabic, French",
            "localLanguageCode": "ARA, FRE"
        }
    }
]

我希望根据独特的地区、国家、t2-t7 组合对我的整个数据对象进行分组,并得到这样的输出

[{
  "region": "Africa",
  "country": [{
      "desc": "Madagascar",
      "t2": [{
        "id": "AFR",
        "localLanguageName": "Malagasy, French",
        "localLanguageCode": "MLG, FRE"
        "t3": [{
          "id": "MG"
        }]
      }]
    },
    {
      "desc": "Morocco (incl. Western Sahara)",
      "subTerritory": [{
        "t2": "AFR",
        "t3": [{
          "id": "MA",
          "localLanguageName": "Arabic, French",
          "localLanguageCode": "ARA, FRE"
          "t4": [{
              "id": "MAXEH",
              "localLanguageName": "Arabic, French",
              "localLanguageCode": "ARA, FRE"
              "t5": [{
                "id": ""
                  .
                  .
                  .
              }]
            },
            {
              "id": "EH",
              "localLanguageName": "Arabic, French",
              "localLanguageCode": "ARA, FRE"
              "t5": [{
                "id": ""
                  .
                  .
                  .
              }]
            }]
        }]
      }]
    }]
}]

我正在寻找对数据进行分组的最有效方法。使用 hashmap 更好吗?还是 Javascript 中的 map/reduce 方法?

我已经尝试过以下方法。它显然不完整,但经过几次迭代后我被卡住了。

    const result = Object.values(data.reduce((key, curr) => {
        const { region, country, t2, t3, t4, t5, t6, t7 } = curr;
        if (!key[country]) {
            let obj = {};
            obj.region = region;
            obj.country = country;
            obj.t2 = [{
                id: t2,
                t3: [{
                    id: t3,
                    t4: {
                        id: t4,
                        t5: t5
                    }
                }]
            }];
            key[country] = obj;
        } else {
            key[country].t2 = key[country].t2 || [];
            const foundCountry = key[country].t2.find(x => x.desc === t2);
            if (!foundCountry) {
                key[country].t2.push({
                    id: t2,
                    t3: [{
                        id: t3,
                        t4: {
                            id: t4,
                            t5: t5
                        }
                    }]
                });
            } else {
                const tx = foundCountry.find(x => x.id === t3);
                if (!tx) {
                    foundCountry.push({
                        id: t3,
                        t4: {
                            id: t4,
                            t5: t5
                        }
                    });
                } else {
                    tx.id = t3;
                    tx.t4 = t4;
                }
            }
        }
        return key;
    }, {}));
    console.log(util.inspect(result, false, null, true))
    return result;

标签: javascriptarraysobject

解决方案


我将从一个非常通用的开始,groupBy它支持基于键数组嵌套多个组:

// Takes an array of keys and array of objects and returns a nested grouping
const groupByKeys = ([k, ...ks], xs) => k
  ? mapObj(
      ys => groupByKeys(ks, ys),
      groupByKey(k, xs)
    )
  : xs;

// Groups an array based by a key
const groupByKey = (k, xs) => xs
  .reduce(
    (gs, x) => Object.assign(gs, { [x[k]]: (gs[k] || []).concat([x]) }),
    {}
  );

// Utility to map a function over the values of an object
const mapObj = (f, obj) => Object.fromEntries(
  Object.entries(obj).map(([k, v]) => [ k, f(v) ])
);

注意:您可能会在 Ramda 或 lodash 等前端库中找到这些实用方法的维护良好、测试更好且性能更高的版本。

您现在可以使用以下方法对数据进行分组:

const groups = groupByKeys(
  ["region", "country", "t2", "t3", "t4", "t5", "t6", "t7"],
  your_data
);

下一步将是转换为所需格式并处理所有null路径的更难的部分。

为了使这更容易和更高效,我在每个分组层上包含了一个所有元素的数组:

const groupByKeys = ([k, ...ks], xs) => k
  ? Object.assign(mapObj(
      ys => groupByKeys(ks, ys),
      groupByKey(k, xs)
    ), { [AllKey]: xs })
  : xs;

我们现在可以使用 , 和一些解构来遍历我们的Object.values分组Array.prototype.map

const transform = groups => Object
  .values(groups)
  .map(
    ({ [AllKey]: allElementsInThisLayer, ...childGrouping }) => { /* ... */ }
  );

现在剩下要做的就是定义转换每一层的逻辑。老实说,这就是我不太了解您想要的结果的地方。我实现了前几层,但也许你自己可以做得更好,因为你有结构化数据可以使用:

// Transformers for all layers of grouping
const Region = ({ [AllKey]: [ { region } ], ...countries }) => ({ 
  region,
  country: Object.values(countries).map(Country).filter(Country.notEmpty)
});

Region.notEmpty = ({ region }) => region !== null;

const Country = ({ [AllKey]: [ { country } ], ...t2}) => ({
  desc: country,
  t2: Object.values(t2).map(T2)
});

Country.notEmpty = ({ desc }) => desc !== null;

const T2 = ({ [AllKey]: [ t2 ], ...t3 }) => ({
  id: t2.t2,
  localLanguageName: t2.localLanguage.localLanguageName,
  localLanguageCode: t2.localLanguage.localLanguageCode,
  t3: Object.values(t3).map(T3)
})

const T3 = ({ [AllKey]: [ { t3 } ], ...t4 }) => ({
  id: t3 // Etc.
})

这是一个可运行的片段。// Etc.在评论处继续工作

const AllKey = Symbol();

// Utils
const mapObj = (f, obj) => Object.fromEntries(
  Object.entries(obj).map(([k, v]) => [ k, f(v) ])
)

const groupByKey = (k, xs) => xs
  .map(x => [x[k], x])
  .reduce(
    (gs, [k, v]) => Object.assign(gs, { [k]: (gs[k] || []).concat([v]) }),
    {}
  );

const groupByKeys = ([k, ...ks], xs) => k
  ? Object.assign(mapObj(
      ys => groupByKeys(ks, ys),
      groupByKey(k, xs)
    ), { [AllKey]: xs })
  : xs;

// App
const keys = ["region", "country", "t2", "t3", "t4", "t5", "t6", "t7"]
const groups = groupByKeys(keys, getData());

// Transformers for all layers of grouping
const Region = ({ [AllKey]: [ { region } ], ...countries }) => ({ 
  region,
  country: Object.values(countries).map(Country).filter(Country.notEmpty)
});

Region.notEmpty = ({ region }) => region !== null;

const Country = ({ [AllKey]: [ { country } ], ...t2}) => ({
  desc: country,
  t2: Object.values(t2).map(T2)
});

Country.notEmpty = ({ desc }) => desc !== null;

const T2 = ({ [AllKey]: [ t2 ], ...t3 }) => ({
  id: t2.t2,
  localLanguageName: t2.localLanguage.localLanguageName,
  localLanguageCode: t2.localLanguage.localLanguageCode,
  t3: Object.values(t3).map(T3)
})

const T3 = ({ [AllKey]: [ { t3 } ], ...t4 }) => ({
  id: t3 // Etc.
})

const transform = groups => Object
  .values(groups)
  .map(Region)
  .filter(Region.notEmpty);

console.log(JSON.stringify(transform(groups), null, 2));

function getData() {
  return [{
    "region": null,
    "country": null,
    "territory": "Worldwide",
    "territoryCode": "ALL",
    "t2": null,
    "t3": null,
    "t4": null,
    "t5": null,
    "t6": null,
    "t7": null,
    "localLanguage": {
      "territoryId": 1,
      "localLanguageName": "N/A",
      "localLanguageCode": null
    }
  }, {
    "region": "Africa",
    "country": "Madagascar",
    "territory": null,
    "territoryCode": "MG",
    "t2": "AFR",
    "t3": "MG",
    "t4": null,
    "t5": null,
    "t6": null,
    "t7": null,
    "localLanguage": {
      "territoryId": 30,
      "localLanguageName": "Malagasy, French",
      "localLanguageCode": "MLG, FRE"
    }
  }, {
    "region": "Africa",
    "country": null,
    "territory": null,
    "territoryCode": "AFR",
    "t2": "AFR",
    "t3": null,
    "t4": null,
    "t5": null,
    "t6": null,
    "t7": null,
    "localLanguage": {
      "territoryId": 2,
      "localLanguageName": "N/A",
      "localLanguageCode": null
    }
  }, {
    "region": "Africa",
    "country": "Morocco (incl. Western Sahara)",
    "territory": null,
    "territoryCode": "MA",
    "t2": "AFR",
    "t3": "MA",
    "t4": null,
    "t5": null,
    "t6": null,
    "t7": null,
    "localLanguage": {
      "territoryId": 35,
      "localLanguageName": "Arabic, French",
      "localLanguageCode": "ARA, FRE"
    }
  }, {
    "region": "Africa",
    "country": "Morocco (incl. Western Sahara)",
    "territory": "Morocco (excl. Western Sahara)",
    "territoryCode": "MAXEH",
    "t2": "AFR",
    "t3": "MA",
    "t4": "MAXEH",
    "t5": null,
    "t6": null,
    "t7": null,
    "localLanguage": {
      "territoryId": 36,
      "localLanguageName": "Arabic, French",
      "localLanguageCode": "ARA, FRE"
    }
  }, {
    "region": "Africa",
    "country": "Morocco (incl. Western Sahara)",
    "territory": "Western Sahara",
    "territoryCode": "EH",
    "t2": "AFR",
    "t3": "MA",
    "t4": "EH",
    "t5": null,
    "t6": null,
    "t7": null,
    "localLanguage": {
      "territoryId": 37,
      "localLanguageName": "Arabic, French",
      "localLanguageCode": "ARA, FRE"
    }
  }]
};
.as-console-wrapper { min-height: 100% }


推荐阅读