【问题标题】:Transform an array of objects structure转换对象结构数组
【发布时间】:2020-04-20 04:35:36
【问题描述】:

目前我正在使用一个 api,它返回一个看起来像的对象数组

[{match_id: "255232",
country_id: "41",
country_name: "England",
league_id: "152",
league_name: "National League",
match_date: "2020-01-01",
match_status: "",
match_time: "16:00"},
{match_id: "255232",
    country_id: "41",
    country_name: "Italy",
    league_id: "152",
    league_name: "Serie a",
    match_date: "2020-01-01",
    match_status: "",
    match_time: "16:00"},
{match_id: "255232",
        country_id: "41",
        country_name: "Italy",
        league_id: "153",
        league_name: "Serie b",
        match_date: "2020-01-01",
        match_status: "",
        match_time: "16:00"},...
    ...

]

我想以一种最终看起来像这样的方式对其进行改造:

const transformed = [
{
    country_name: "England",
    entries: [
        {league_name: "National League",
        events: [{},{}]}
    ]

},
{country_name: "Italy",
entries: [
    {
        league_name: "Serie a",
        events: [{},{}]
    },
    {
        league_name: "Serie b",
        events: [{},{}]
    }...
]]

我尝试使用 .reduce,但没有得到预期的输出,我最终得到的只是一个还原的结构。基本上我需要的是首先按国家名称和联赛名称进行分类。

显然数据是动态的,国家/联赛的名称经常变化。

【问题讨论】:

  • 我会尝试使用 map 来遍历列表并返回一个您想要的格式的新列表。
  • 一些答案​​建议对结果数据使用键控对象。我认为这个建议很好,并建议您考虑数据的结果“形状”(每个@tex)对最终输出的重要性。

标签: javascript arrays node.js object


【解决方案1】:

我提供了两种解决方案 - 一种返回以国家和联赛名称为键的对象(这是我的偏好/建议),另一种扩展第一个解决方案以返回您请求的形状。

第一个 sn-p 将您的输入转换为一个以国家和联赛名称为键的对象:

const data = [{match_id: "255232",country_id: "41",country_name: "England",league_id: "152",league_name: "National League",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255233",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255234",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"}]

const transformed = data.reduce(
  (acc, { country_name, league_name, ...match }) => {
    acc[country_name] = acc[country_name] || {}
    acc[country_name][league_name] = acc[country_name][league_name] || []
    acc[country_name][league_name].push(match)
    return acc
  },
  {}
)

console.log(transformed)

第二个 sn-p 扩展了第一个,返回您请求的形状,最初:

const data = [{match_id: "255232",country_id: "41",country_name: "England",league_id: "152",league_name: "National League",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255233",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255234",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"}]

const tmp = data.reduce(
  (acc, { country_name, league_name, ...match }) => {
    acc[country_name] = acc[country_name] || {}
    acc[country_name][league_name] = acc[country_name][league_name] || []
    acc[country_name][league_name].push(match)
    return acc
  },
  {}
)

const transformed = Object.entries(tmp).map(
  ([country_name, leagues]) => ({
      country_name,
      entries: Object.entries(leagues).map(
        ([league_name, events]) => ({ league_name, events })
      )
  })
)

console.log(transformed)

与数组相比,我更喜欢键控对象有几个原因:

  1. keyed 对象中的嵌套更少,字符串化时嵌套会更小。
  2. 从对象(例如transformed.England["National League"])获取国家和/或国家+联赛的所有条目更容易。
  3. 生成对象的工作量更少。
  4. 每个国家/地区一个条目意味着“正确”的数据结构是Map(在大多数情况下都正确,但不一定在所有情况下都正确)。该对象比数组更接近 Map

我在redux 中成功使用(并教授)了类似的技术。我不仅存储 API 返回的对象数组,还存储基于对象 ID 索引的数组的对象。在许多情况下,使用此对象比使用原始数组要容易得多:

const arr = [{ id: 'a', foo: 'bar' }, { id: 'b', foo: 'baz' }]
const obj = { a: { foo: 'bar' }, b: { foo: 'baz' } }

这里有一个快速的 sn-p 演示如何从对象转到所需的 React 输出,如果结果证明该对象是一个有用的中间数据结构:

const { Fragment } = React

const data = [{match_id: "255232",country_id: "41",country_name: "England",league_id: "152",league_name: "National League",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255233",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255234",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"}]

const transformed = data.reduce(
  (acc, { country_name, league_name, ...match }) => {
    acc[country_name] = acc[country_name] || {}
    acc[country_name][league_name] = acc[country_name][league_name] || []
    acc[country_name][league_name].push(match)
    return acc
  },
  {}
)

const League = ({ league, matches }) => (
  <Fragment>
    <h2>{league}</h2>
    {matches.map(({ match_id }) => (<p>{match_id}</p>))}
  </Fragment>
)

ReactDOM.render(
  Object.entries(transformed).map(([country, leagues]) => (
    <Fragment>
      <h1>{country}</h1>
      {Object.entries(leagues).map(([league, matches]) => (
        <League league={league} matches={matches} />
      ))}
    </Fragment>
  )),
  document.body
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>

【讨论】:

  • 这看起来不错,但为什么你更喜欢对象而不是数组结构?
  • @greatTeacherOnizuka 谢谢!我在答案中添加了一些关于为什么我更喜欢对象而不是数组的注释。如果您有任何其他问题,请告诉我,我会尽力回答。
  • 假设您不知道键的值,您如何打印对象的所有条目?我最终得到了像Object.keys(transformed).map( key =&gt; { console.log(`${key} : ${transformed[key]}`) Object.keys(transformed[key]).forEach(k=&gt;{ {(Object.values(transformed[key]).map(el =&gt; el.map(console.log(el.match_id)} }); } ) 这样看起来不正确的东西。 PS 我正在使用 React,所以如果你愿意,你可以用 JSX 编写
  • 我正在尝试建立一个看起来像

    England

    PREMIER LEAGUE

    {MATCH_ID}

    ...的结构...跨度>
  • @greatTeacherOnizuka 添加了一个 React sn-p 来展示如何从中间对象数据结构生成所需的输出。
【解决方案2】:

这似乎是 for 循环的用例,它创建或 pushes 将每个“结果”放入相应的国家/地区索引,然后使用类似的逻辑按联赛组织每个国家/地区的比赛。下面的代码非常冗长——如果你使用像 Lodash 这样的实用程序库,你可以通过 groupBy 之类的东西更简洁地实现其中的一些功能(尽管这会产生一个对象而不是数组)。

const results = [/* ...your original data... */];
const resultsByCountry = [];

// The first loop just indexes the matches by Country:
for (let r = 0; r < results.length; r++) {
  let result = results[r];
  let existingCountryIndex = resultsByCountry.findIndex((country) => country.country_name == result.country_name);

  // Temporarily, we're just stashing the matches flat, here. We'll take another pass to group them into leagues, once we know they're all sorted into the right countries:
  if (existingCountryIndex > -1) {
    resultsByCountry[existingCountryIndex].entries.push(result);
  } else {
    resultsByCountry.push({
      country_name: result.country_name,
      entries: [result]
    });
  }
}


// The second loop organizes the matches into leagues, by a very similar means:
for (let c = 0; c < resultsByCountry.length; c++) {
  const country = resultsByCountry[c]
  let matches = country.entries;
  let matchesByLeague = [];

  for (let m = 0; m < matches.length; m++) {
    let match = matches[m];
    let existingLeagueIndex = matchesByLeague.findIndex((league) => league.league_name == match.league_name);

    if (existingLeagueIndex > -1) {
      matchesByLeague[existingLeagueIndex].events.push(match);
    } else {
      matchesByLeague.push({
        league_name: match.league_name,
        events: [match]
      });
    }
  }

  // Overwrite the old `entries` key with the grouped array:
  country.entries = matchesByLeague;
}

console.log(resultsByCountry);

再一次,这很粗糙。可能有一种方法可以用mapreduce 来实现它,但是由于数据结构不寻常,并且专门询问了关于从数组转换为数组(不是对象)的问题,我将其提供为一个简单 + 明确的答案。

【讨论】:

    【解决方案3】:

    您可以存储用于构建嵌套组/值对的所有信息,并为最终对象的所有键获取一个数组,并通过减少组数组来减少数组。

    var data = [{ match_id: "255232", country_id: "41", country_name: "England", league_id: "152", league_name: "National League", match_date: "2020-01-01", match_status: "", match_time: "16:00" }, { match_id: "255232", country_id: "41", country_name: "Italy", league_id: "152", league_name: "Serie a", match_date: "2020-01-01", match_status: "", match_time: "16:00" }, { match_id: "255232", country_id: "41", country_name: "Italy", league_id: "153", league_name: "Serie b", match_date: "2020-01-01", match_status: "", match_time: "16:00" }],
        groups = [['country_name', 'entries'], ['league_name', 'events']],
        values = ['match_date', 'match_status', 'match_time'],
        result = data.reduce((r, o) => {
            groups
                .reduce((group, [key, values]) => {
                    var temp = group.find(q => q[key] === o[key]);
                    if (!temp) group.push(temp = { [key]: o[key], [values]: [] });
                    return temp[values];
                }, r)
                .push(Object.assign(...values.map(k => ({ [k]: o[k] }))));
            return r;
        }, []);
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

      【解决方案4】:
      const map1 = {};
      const list = matches.map(match => {
        if (!map1[match.country_name]) {
          map1[match.country_name] = {};
        }
        map1[match.country_name]["entries"] =
          map1[match.country_name]["entries"] &&
          map1[match.country_name]["entries"].length > 0
            ? map1[match.country_name]["entries"].concat(match)
            : [match];
      });
      
      const result = [];
      Object.keys(map1).forEach(country => {
        const m = {};
        m["country_name"] = country;
        m["entries"] = [];
        const entries = map1[country]["entries"];
      
        entries.forEach(entry => {
          m["entries"].push({
            league_name: entry.league_name,
            events: entry
          });
        });
      
        result.push(m);
      });
      // result has the required info.
      

      【讨论】:

        猜你喜欢
        • 2021-10-03
        • 2016-04-19
        • 1970-01-01
        • 1970-01-01
        • 2018-03-10
        • 2022-01-09
        • 2021-07-15
        相关资源
        最近更新 更多