【问题标题】:Group/sum property values inside an array of objects(json) using Angular 4 Pipes or Rxjs operators使用 Angular 4 Pipes 或 Rxjs 运算符对对象数组(json)中的属性值进行分组/求和
【发布时间】:2017-06-12 13:41:17
【问题描述】:

我的 http 调用的响应是一组公司对象,如下所示:

response = [{
    "name": "CompanyName1",
    "registrations": [
      {
        "country": "en",
        "register": "registerType1",
        "amount": 2
      },
      {
        "country": "wa",
        "register": "registerType2",
        "amount": 1
      },{
        "country": "en",
        "register": "registerType1",
        "amount": 3
      },
       {
        "country": "sc",
        "register": "registerType3",
        "amount": 2
      },
      {
        "country": "wa",
        "register": "registerType3",
        "amount": 2
      }
    ]
  },
  {
    "name": "CompanyName2",
    "registrations": [
      {
        "country": "wa",
        "register": "registerType1",
        "amount": 3
      },
      {
        "country": "sc",
        "register": "registerType3",
        "amount": 1
      }
    ]]

如果 registerType1 ||,我需要按注册和国家/地区将公司的注册分组/汇总到一个新的 propertyName (registerType_12) registerType2 否则只保留属性名称 registerTypeN。那是 companyName1 我需要得到这样的结果:

CompanyName1: [{country:en, registerType_12, amount:5},
              {country:wa, registerTyoe_12, amount:1,
              {country:sc, registerType3, amount:4}
              }]

【问题讨论】:

标签: typescript pipe rxjs lodash


【解决方案1】:

我做了一个 Plunkr 来演示如何做到这一点:https://plnkr.co/edit/uO48fEVJGy2nwm587PO8?p=preview

这里有一个解释

我将您的数据保存在一个变量中:

const { Observable } = Rx;

// FAKE DATA FROM BACKEND
const data = [
  {
    "name": "CompanyName1",
    "registrations": [
      {
        "country": "en",
        "register": "registerType1",
        "amount": 2
      },
      ...
    ]
  },
  {
    "name": "CompanyName2",
    "registrations": [
      {
        "country": "wa",
        "register": "registerType1",
        "amount": 3
      },
      ...
    ]
}];

我为 ResponseHttp.get 方法做了一个模拟,所以最后我们得到了一些看起来像 Angular 的代码:
(这些与您的问题没有直接关系)

// MOCK RESPONSE OBJECT
const response = () => {
  const r = {
    body: null
  };

  r.json = () => JSON.parse(r.body);

  return r;
};

// MOCK HTTP METHOD
const http = {
  get: (url) => {
    const res = response();
    res.body = JSON.stringify(data);

    return Observable.of(res).delay(1000)
  }
};

以下是您可以用干净的方式解决问题的方法:

http
  .get('some-url')
  .map(res => res.json())
  .map(groupByCompanyNameAndRegistration)
  .do(data => console.log(data))
  .subscribe();


显然,groupByCompanyNameAndRegistration 尚未制作。 但是我们有一个干净的Observable 链,很容易理解发生了什么。

代码的逻辑部分:

const flattenObj = (obj) =>
  Object.keys(obj).map(key => obj[key]);

const groupByCompanyNameAndRegistration = (data) => {
  return data.reduce((accComp, company) => {
    if (!accComp[company.name]) {
      accComp[company.name] = {};
    }

    const compObj = company.registrations.reduce((accReg, reg) => {
      if (!accReg[reg.country]) {
        accReg[reg.country] = {
          country: reg.country,
          amount: 0
        };
      }

      accReg[reg.country].amount += reg.amount;

      return accReg;
    }, {});

    accComp[company.name] = flattenObj(compObj);

    return accComp;
  }, {});
};

最后,控制台输出:

{
  "CompanyName1": [
    {
      "country": "en",
      "amount": 5
    },
    {
      "country": "wa",
      "amount": 3
    },
    {
      "country": "sc",
      "amount": 2
    }
  ],
  "CompanyName2": [
    {
      "country": "wa",
      "amount": 3
    },
    {
      "country": "sc",
      "amount": 1
    }
  ]
}

【讨论】:

  • 非常感谢!对于 RxJS 方法,这个解决方案也非常出色、干净和正确,但我意识到我必须在视图中保留原始响应,我决定创建一个 Angular Pipe 并将 data.registration 属性数组传递给 Pipe 所以我使用@skeptor 解决方案
  • 感谢您的客气话!没问题。如果您使用管道解决方案,请记住使用纯管道以避免一次又一次地计算,每次更改检测时都使用相同的值。也许尝试 console.log 在你的管道中记录一些东西,如果它被调用太多次,请注意 pure 或使用我的解决方案,一劳永逸;)
  • 我很确定我会在以后的类似问题中使用它,你的回答给了我很好的想法,谢谢!
【解决方案2】:

您不需要 typescript 或 rxjs。它可以用纯javascript来实现。

JSBin 链接:https://jsbin.com/yujocofesu/edit?js,console

response.reduce((finalObj, obj)=>{
     finalObj[obj.name] = 
         obj.registrations
             .map(reg => Object.assign(reg,
                  {register: ( 
                     reg.register ==='registerType1'||
                     reg.register==='registerType2') ?
                     'registerType_12': reg.register 
                   })
              )
              .reduce(mergeSimilar, []);
     return finalObj;
   },{});


function mergeSimilar(newList, item){
  let idx = newList.findIndex( 
      i=> i.country === item.country && i.register === item.register);
  if(idx>-1){
    newList[idx].amount += item.amount;
  }else{
    newList.push(item)
  }
  return newList;
}

这给出了响应

{
  CompanyName1: [{
    amount: 5,
    country: "en",
    register: "registerType_12"
  }, {
    amount: 1,
    country: "wa",
    register: "registerType_12"
  }, {
    amount: 2,
    country: "sc",
    register: "registerType3"
  }, {
    amount: 2,
    country: "wa",
    register: "registerType3"
  }],
  CompanyName2: [{
    amount: 3,
    country: "wa",
    register: "registerType_12"
  }, {
    amount: 1,
    country: "sc",
    register: "registerType3"
  }]
}

【讨论】:

  • 感谢@Skeptor,对不起,我使用了 Angular Pipe 我打算使用 javascript 解决方案,实际上您的解决方案非常适合 Pipe 方法!
  • 我很高兴在纯 js 中看到它,仅此而已:)
  • 我简化了问题,只是将 response.registrations 属性传递给了管道,所以我使用了你的算法的部分实现,但它对我来说仍然有点抽象;)我正在学习使用这个编程范式,再次感谢您的解决方案
猜你喜欢
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-31
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多