Group/sum property values inside an array of objec

2019-07-08 04:58发布

问题:

The response from my http call is an array of company objects like this:

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
      }
    ]]

I need to group/sum company's registrations by register and country into a new propertyName (registerType_12) if registerType1 || registerType2 otherwise just keep property name registerTypeN. that is for companyName1 I need to get a result like this:

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

回答1:

You don't need typescript or rxjs for this. It can be achieved with pure javascript.

JSBin link : 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;
}

This gives response as

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


回答2:

I made a Plunkr to demo how to do that: https://plnkr.co/edit/uO48fEVJGy2nwm587PO8?p=preview

Here's an explanation

I saved your data in a variable:

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
      },
      ...
    ]
}];

I made a mock for a Response and Http.get method so in the end we have something that really looks like Angular code:
(These are not directly relevant for your problem)

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

And here's how you could solve your problem in a clean way:

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


Oubviously, the groupByCompanyNameAndRegistration is not made yet. But we have a clean chain of Observable and it's really easy to understand what's going on.

The logic part of the code:

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;
  }, {});
};

And finally, the console output :

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