在JavaScript中,对对象数组按多个键进行分组

5

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

[
    {
        "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": ""
                  .
                  .
                  .
              }]
            }]
        }]
      }]
    }]
}]

我正在寻找最有效的方式来对数据进行分组。使用哈希表是否更好?还是在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;

1
在您的原始数据中,连接到 Elasticsearch 是非常容易的!关于如何编写转换,您尝试过哪些方法出了问题呢? - Doug
@Doug,尝试使用以下代码: var theatres = [], city = []; for (var key in data) { var output = {}, city_data = {}; city_data.desc= data[key].city; output.id = data[key].theatreId; output.desc = data[key].theatreDescription; theatres.push(output); city.push(city_data) } 但是希望能够使用默认方法来完成相同的操作。 - stackUser67
为什么country有不同的对象结构,一个带有t2,另一个带有subTerritory?这是什么原因? - Nina Scholz
请添加一个具有现实意义的数据结果。Translated text: 请添加一个具有现实意义的数据结果。 - Nina Scholz
@stackUser67,您能否指定要在输入JSON中按组进行的确切键?根据我对您发布的输出JSON的理解,您正在一级别上按'region'进行分组,并且所有属于同一地区的国家都位于由键“country”引用的数组中。您计划如何在最终输出JSON中填充'subTerritory'? - Soumojit Ghosh
6个回答

2

本答案提供了两种解决方案:

  • 针对每个级别进行处理的层级中心方法。
  • 使用给定的键数组和统一的结果集的快速方法。

使用短路实现每个级别的自定义函数

此方法采用不同的方式来处理每个级别,并允许在某个节点结束迭代。

result = data.reduce((r, o) => {
    let p = r;
    groups.every(fn => p = fn(o, p));
    return r;
}, []);

上面的代码遍历给定的数据集并使用一个分组函数数组,该数组返回真值或假值。如果返回一个假值,则迭代在这个级别停止。
({ region }, target) => {                                   // 1
    if (!region) return;                                    // 2
    let temp = target.find(q => q.region === region);       // 3
    if (!temp) target.push(temp = { region, country: [] }); // 4
    return temp.country;                                    // 5
}

分组函数至少包含四到五个部分。

  1. 函数签名包含源对象或通过析构的子集和目标。目标可以是数组或对象。

  2. 检查分组值是否存在,如果不存在,则返回。这也会结束所有后续组的迭代。此点是可选的。

  3. 搜索以找到所需组的正确对象。

  4. 检查组是否存在,如果不存在,则创建具有所需属性的新组。

  5. 函数的返回值为数组或对象,取决于下一个分组函数。


这种方法的主要优点是能够处理具有自定义结构的不同级别,并且可以省略空/未给定的分组值。

主要缺点是每个级别都需要一个函数。

const
    groups = [
        ({ region }, target) => {
            if (!region) return;
            let temp = target.find(q => q.region === region);
            if (!temp) target.push(temp = { region, country: [] });
            return temp.country;
        },
        ({ country: desc }, target) => {
            if (!desc) return;
            let temp = target.find(q => q.desc === desc);
            if (!temp) target.push(temp = { desc, t2: [] });
            return temp.t2;
        },
        ({ t2: id, localLanguage: { localLanguageName, localLanguageCode } }, target) => {
            if (!id) return;
            let temp = target.find(q => q.id === id);
            if (!temp) target.push(temp = { id, localLanguageName, localLanguageCode, t3: [] });
            return temp.t3;
        },
        ({ t3: id }, target) => {
            if (!id) return;
            let temp = target.find(q => q.id === id);
            if (!temp) target.push(temp = { id, t4: [] });
            return temp.t4;
        },
        ({ t4: id }, target) => {
            if (!id) return;
            let temp = target.find(q => q.id === id);
            if (!temp) target.push(temp = { id, t5: [] });
            return temp.t5;
        }
    ],
    data = [{ 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" } }],
    result = data.reduce((r, o) => {
        let p = r;
        groups.every(fn => p = fn(o, p));
        return r;
    }, []);
   console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

快速统一的方法,每个级别具有相同的结构

{ id, children: [] }

这是每个层级的结果结构。 id 反映了实际的组值,而 children 包含相同结构或最终对象的其他对象。
此方法针对每个层级使用一个对象和一个名为 _ 的结果。
它从筛选具有值的顶级组开始,并通过从(最终)对象中提取 level 属性来减少组。
如果实际层级的组不存在,则创建一个带有数组的新属性。此对象包含一个名为 _ 的单个属性,其中包含一个空数组。此数组与后面可见对象中此层级的子项共享相同的对象引用。
在减少结束时,将推送未访问属性的最终对象。
最后返回下划线属性,因为它包含所有嵌套组。
这种方法的主要优点是通过给定的键立即添加所需的分组。
主要缺点是得到统一的结果,没有为某些层级提供自定义选项。它不会过滤不需要的对象。

const
    keys = ['region', 'country', 't2', 't3', 't4', 't5', 't6', 't7'],
    data = [{ 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" } }],
    result = data
        .reduce((t, o) => {
            const groups = keys.filter((flag => k => flag = flag && o[k])(true));
            groups
                .reduce(function (r, k) {
                    let id;
                    ({ [k]: id, ...o } = o);
                    if (!r[id]) {
                        r[id] = { _: [] };
                        r._.push({ id, children: r[id]._ });
                    }
                    return r[id];
                }, t)._.push(o);
            return t;
        }, { _: [] })
        ._;
 
   console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

很难理解给定数据集的哪一部分,如果有任何部分应该被覆盖到规范解决方案中,以及目标操作是什么。因此,本答案分为两个部分,但如果给出提示,任何人都会从中受益。


1
你需要使用Array.prototype.reduce根据state属性对数组对象进行分组,然后检查城市是否存在,如果存在,则用最新值覆盖,否则将其推入city数组中,同样地,你需要检查电影院,在最后你需要返回累加器以供下一次迭代使用。

const data1 = [{
    city: 'LAKE GENEVA',
    state: 'WISCONSIN',
    theatreId: '000080',
    theatreDescription: 'GENEVA 4'
  },
  {
    city: 'BURLINGTON',
    state: 'WISCONSIN',
    theatreId: 'c05364',
    theatreDescription: 'PLAZA THEATRE 4'
  }
];

const data2 = [{
  city: 'MIAMI',
  state: 'FLORIDA',
  theatreId: 'c05170',
  theatreDescription: 'DOLPHIN 24'
}, {
  city: 'MIAMI',
  state: 'FLORIDA',
  theatreId: '000306',
  theatreDescription: 'CMX BRICKELL CITY CENTRE 10'
}];

const reduceCityTheaters = (arr) => Object.values(arr.reduce((acc, curr) => {
  // Deconstruct needed properties
  const { state, city, theatreId, theatreDescription } = curr;
  
  // Check if state not present
  if (!acc[state]) {
    let obj = {};
    obj.state = state;
    obj.city = [{
      desc: city,
      theatres: [{
        id: theatreId,
        desc: theatreDescription
      }]
    }];

    acc[state] = obj;
  } else { // Check if state is present
    acc[state].city = acc[state].city || [];
    const foundCity = acc[state].city.find(x => x.desc === city);

    // Check if city exists or not if not push it
    if (!foundCity) {
      acc[state].city.push({
        desc: city,
        theatres: [{
          id: theatreId,
          desc: theatreDescription
        }]
      });
    } else {
      const foundTheater = foundCity.theatres.find(x => x.id === theatreId);

      // Check if theatre exists or not if not push it
      if (!foundTheater) {
        foundCity.theatres.push({
          id: theatreId,
          desc: theatreDescription
        });
      } else {
        foundTheater.id = theatreId;
        foundTheater.desc = theatreDescription;
      }
    }
  }

  return acc;
}, {}));


const res1 = reduceCityTheaters(data1);
const res2 = reduceCityTheaters(data2);

console.log('1', res1);
console.log('2', res2);


但是它适用于下面的对象吗?我不这么认为。[ { city: '迈阿密', state: '佛罗里达州', theatreId: 'c05170', theatreDescription: '海豚24' }, { city: '迈阿密', state: '佛罗里达州', theatreId: '000306', theatreDescription: 'CMX BRICKELL CITY CENTRE 10' } ] - stackUser67

1
我会从一个非常通用的 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.valuesArray.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% }


0

试一下这个

var data = [
  {
    city: 'LAKE GENEVA',
    state: 'WISCONSIN',
    theatreId: '000080',
    theatreDescription: 'GENEVA 4'
  },
  {
    city: 'BURLINGTON',
    state: 'WISCONSIN',
    theatreId: 'c05364',
    theatreDescription: 'PLAZA THEATRE 4'
  }
]

let group = data.reduce((r, a) => {
 r[a.state] = [...r[a.state] || [], a];
 return r;
}, {});

let nowObj ={};
for (let [key, value] of Object.entries(group)) {
  let groupNew = value.reduce((r, a) => {
      r[a.city] = [...r[a.city] || [], a];
      return r;
  }, {});
  nowObj[key] =groupNew;
}
let mainObj=[];
for (let [key, value] of Object.entries(nowObj)) {
  let obj ={};
  let city =[];
  for (let [key2, value2] of Object.entries(value)) {
    let cityObj ={};
    cityObj['desc'] =key2;
    cityObj['theatres'] =value2.map(item => {
      const container = {};
      container['id'] = item.theatreId;
      container['des'] = item.theatreDescription;
      return container;
    });
    city.push(cityObj);
  }
  obj['state'] =key;
  obj['city'] =city;
  mainObj.push(obj);
}

console.log(mainObj);


0

我会使用构造函数或类。我更喜欢构造函数,因为它们可以用较少的工作来维护私有变量(在示例中未显示)。来看看吧!

function Theaterminal(dataArray){
  this.states = [];
  this.getPlace = (state, city)=>{
    let sx = new RegExp('^'+state+'$', 'i'), cx;
    if(city)cx = new RegExp('^'+city+'$', 'i');
    for(let s of this.states){
      if(s.state.match(sx)){
        if(city){
          for(let c of s.cities){
            if(c.city.match(cx)){
              return c;
            }
          }
          return false;
        }
        else{
          return s;
        }
      }
    }
    return false;
  }
  this.parse = data=>{
    let s = this.states, w, q, t, c, b;
    s.splice(0);
    data.forEach(o=>{
      w = o.state; q = this.getPlace(w); t = {id:o.theatreId, desc:o.theatreDescription}; c = {city:o.city, theaters:[t]};
      if(q === false){
        s.push({state:w, cities:[c]});
      }
      else{
        b = this.getPlace(w, o.city);
        if(b === false){
          q.cities.push(c);
        }
        else{
          b.theaters.push(t);
        }
      }
    });
    return s;
  }
  this.parse(dataArray);
}
const data = [
  {
    city: 'LAKE GENEVA',
    state: 'WISCONSIN',
    theatreId: '000080',
    theatreDescription: 'GENEVA 4'
  },
  {
    city: 'BURLINGTON',
    state: 'WISCONSIN',
    theatreId: 'c05364',
    theatreDescription: 'PLAZA THEATRE 4'
  },
  {
    city: 'LAKE GENEVA',
    state: 'WISCONSIN',
    theatreId: 'fakeId',
    theatreDescription: 'fake theater'
  },
  {
    city: 'SEATTLE',
    state: 'WASHINGTON',
    theatreId: 'example id',
    theatreDescription: 'why bother'
  }
];
const terminal = new Theaterminal(data);
console.log(terminal.states);
console.log('-----------------------check this out--------------------');
console.log(terminal.getPlace('wisconsin', 'lake geneva'));

请注意,如果您将一个 dataArray 传递给 TheaterminalInstance.parse(here)TheaterminalInstance.states 也会改变,但其对象引用关联不会丢失。而 dataArray 不会被修改。

0
这个问题需要一个递归解决方案。我已经为这个问题制定了一个通用的解决方案,但这意味着忽略了不同级别表达方式之间的一些差异。
function nestedGrouper(
  data = sampleData,
  groupKeys = ["region", "country", "t2", "t3", "t4", "t5", "t6"]
) {
  function populate(group, remainingKeys, item) {
    if (remainingKeys.length === 1) return;
    const [thisKey, nextKey] = remainingKeys;

    const member =
      group.find((a) => a.id === item[thisKey]) ||
      (group.push(
        Object.assign({
          id: item[thisKey]
        },
        thisKey !== 'region' && thisKey !== 'country' && {
          localLanguageName: item.localLanguage.localLanguageName,
          localLanguageCode: item.localLanguage.localLanguageCode,
        })
      ),
      group[group.length - 1]);

    const subGroup = member[nextKey] || [];

    if (item[nextKey]) {
      populate(subGroup, remainingKeys.slice(1), item);
    }

    // no key for empty array
    if (subGroup.length > 0) {
      member[nextKey] = subGroup;
    }
  }

  return data.reduce(
    (result, item) => (populate(result, groupKeys, item), result),
    []
  );
}

您所期望的输出中,各级别之间的主要差异在于标识类别的属性名称...在顶层的情况下,它可能是'id'、'desc'或'region'。这会使事情变得不太优雅,但显然是可行的。

以下是一些可运行的代码:

const sampleData = [
  {
    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",
    },
  },
];

function nestedGrouper(
  data = sampleData,
  groupKeys = ["region", "country", "t2", "t3", "t4", "t5", "t6"]
) {
  function populate(group, remainingKeys, item) {
    if (remainingKeys.length === 1) return;
    const [thisKey, nextKey] = remainingKeys;

    const member =
      group.find((a) => a.id === item[thisKey]) ||
      (group.push(
        Object.assign({
          id: item[thisKey]
        },
        thisKey !== 'region' && thisKey !== 'country' && {
          localLanguageName: item.localLanguage.localLanguageName,
          localLanguageCode: item.localLanguage.localLanguageCode,
        })
      ),
      group[group.length - 1]);

    const subGroup = member[nextKey] || [];

    if (item[nextKey]) {
      populate(subGroup, remainingKeys.slice(1), item);
    }

    // no key for empty array
    if (subGroup.length > 0) {
      member[nextKey] = subGroup;
    }
  }

  return data.reduce(
    (result, item) => (populate(result, groupKeys, item), result),
    []
  );
}

console.log(JSON.stringify(nestedGrouper(), null, 4));
.as-console-wrapper { max-height: 100% !important; top: 0; }

事实上,我“硬编码”了一个方面,即localLanguageNamelocalLanguageCode... 我应该注意到我们依赖于输入数据对这些属性保持一致,因为如果不同对象的语言具有不同的t3值但相同的t2值,那么在t2级别上就无法决定这些属性应该是什么。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接