-1

I want to create new array of object from apiResponse which will contain all the areaCode for particular RouteId and InDirection.

apiResponse = [{
  RouteId: "1",
  InDirection: "1",
  AreaCode: "41108",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "1",
  InDirection: "1",
  AreaCode: "41109",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "1",
  InDirection: "1",
  AreaCode: "41110",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "1",
  InDirection: "1",
  AreaCode: "41111",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "1",
  InDirection: "2",
  AreaCode: "41108",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "1",
  InDirection: "2",
  AreaCode: "41109",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "1",
  InDirection: "2",
  AreaCode: "411011",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "2",
  InDirection: "1",
  AreaCode: "41112",
  LastModified: "2011-06-15 00:00:00.000",
}, {
  RouteId: "2",
  InDirection: "1",
  AreaCode: "41114",
  LastModified: "2011-06-15 00:00:00.000",
}];

For e.g. for RouteId = 1 and InDirection = 1, result should be

result = [{
  RouteId: "1",
  InDirection: "1",
  AreaCode: ["41108", "41109", "41110", "41111"],
}];

and the final result for the given apiResponse should be

finalResult = [{
  RouteId: "1",
  InDirection: "1",
  AreaCode: ["41108", "41109", "41110", "41111"],
}, {
  RouteId: "1",
  InDirection: "2",
  AreaCode: ["41108", "41109", "411011"],
}, {
  RouteId: "2",
  InDirection: "1",
  AreaCode: ["41112", "41114"],
}];

I tried to extract InDirection but couldn't create desired array

apiResponse
  .filter(x => x.RouteId === '1' && x.InDirection === '1')
  .map(x => x.AreaCode);

Can someone help me with this?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Bonzo
  • 427
  • 2
  • 10
  • 28

4 Answers4

0

The OP's task is a classic data grouping and aggregation (or merge) task.

The result, the OP is looking for, therefore gets achieved best within a single reduce cycle and not via filtering and mapping.

Whilst reducing, one aggregates an intermediate object (here called index) which got passed as initial value as the reduce method's 2nd parameter. With each iteration one either does create and assign a new grouped item, or one just does access it under/via a unique key which is made of both of the currently processed response data item's key values referred by RouteId and InDirection. The create/assign or access task gets achieved within a single statement which uses the nullish coalescing assignment operator / ??=.

And since the OP is looking for an array of newly created grouped items, one exclusively is interested in the intermediate object's values, which is the operation that envelopes the reduce task.

const groupedAndMergedData = Object
  .values(
    responseData
      .reduce((index, { RouteId, InDirection, AreaCode }) => {

        // create a unique grouping key.
        const groupKey = `route${ RouteId }_direction${ InDirection }`;

        // create and assign or just access a grouped item.
        const groupedItem = index[groupKey] ??= {
          RouteId, InDirection, AreaCode: []
        };
        // push the currently processed response data item's area code.
        groupedItem.AreaCode.push(AreaCode);

        return index;
      }, {})
  );

console.log({ groupedAndMergedData });
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  const responseData = [{
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41108",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41109",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41110",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41111",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "41108",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "41109",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "411011",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "2",
    InDirection: "1",
    AreaCode: "41112",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "2",
    InDirection: "1",
    AreaCode: "41114",
    LastModified: "2011-06-15 00:00:00.000",
  }];
</script>

Edit

Having commented Mayank Kumar's approach/implementation ...

"Since having chosen a Set based approach, wasn't it obvious while iterating the apiResponse array and aggregating unique keys on set1, that one already there should have created and/or accessed the new item, and also have aggregated all AreaCode values(, thus better having chosen a Map)? Does it make any sense, for each iteration step of a mapping task to filter again and again any correctly matching value from the already before processed apiResponse array? ...And then map the resulting array again?"

... his Set based approach applied to a Map based variant then could have looked like this ...

// create a `Map` instead of an `Object` based lookup table.
const lookup = new Map();

apiResponse
  .forEach(({ RouteId, InDirection, AreaCode }) => {
    const key = `${RouteId}_${InDirection} `;

    let item = lookup.get(key);
    if (!item) {
      item = { RouteId, InDirection, AreaCode: [] };

      // aggregate the `Map` based lookup.
      lookup.set(key, item);
    }
    // aggregate/merge the individual/spoecific `AreaCode` value(s).
    item.AreaCode.push(AreaCode);
  });

// make `lookup` an array of key-value pairs and `map` each `value` only.
const finalResult = [...lookup]
  .map(([key, value]) => value);

console.log({ finalResult });
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  const apiResponse = [{
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41108",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41109",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41110",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41111",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "41108",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "41109",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "411011",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "2",
    InDirection: "1",
    AreaCode: "41112",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "2",
    InDirection: "1",
    AreaCode: "41114",
    LastModified: "2011-06-15 00:00:00.000",
  }];
</script>

Edit 2 ... since the OP changed the requirements

"Is it possible to sort it based on number of entries in AreaCodes? But I only want one object [for] every RouteId. Every RouteId can have two types of InDirection ...[either]... 1 or 2. So in the above result [I] would like to ...[not have included { RouteId: "1", InDirection: "2", AreaCode: ["41108", "41109", "411011"] }]..."

The next provided code is a slightly altered variant of the first provided reduce based solution.

This time the reduce task does create a RouteId-value based index of InDirection-value based indirection items/objects.

Thus, the values of the reduced index are indices itself which feature the items of interest, each grouped by an InDirection-value based key. Therefore, in order to meet the OP's new requirements, one has to additionally map the index values. Each iteration step provides another, RouteId based, index which gets referred as routeGroup. It is an object with InDirection-value based entries where each value is an object which features an AreaCode array. And since the mapping task is supposed to only return the object with the most AreaCode items one just needs to sort the InDirection based values array by each values AreaCode array-length in an descending order and return the sorted result's first item.

const groupedAndMergedData = Object
  .values(
    responseData
      .reduce((index, { RouteId, InDirection, AreaCode }) => {

        // create and assign or just access specifically grouped items.
        const routeGroup = index[RouteId] ??= {};

        const indirectionItem = routeGroup[InDirection] ??= {
          RouteId, InDirection, AreaCode: []
        };

        // push the currently processed response data item's area code.
        indirectionItem.AreaCode.push(AreaCode);

        return index;
      }, {})
  )
  .map(routeGroup =>
    Object
      .values(routeGroup)
      .sort((a, b) => b.AreaCode.length - a.AreaCode.length)[0]
  );

console.log({ groupedAndMergedData });
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  const responseData = [{
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41108",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41109",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41110",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "1",
    AreaCode: "41111",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "41108",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "41109",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "1",
    InDirection: "2",
    AreaCode: "411011",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "2",
    InDirection: "1",
    AreaCode: "41112",
    LastModified: "2011-06-15 00:00:00.000",
  }, {
    RouteId: "2",
    InDirection: "1",
    AreaCode: "41114",
    LastModified: "2011-06-15 00:00:00.000",
  }];
</script>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • Is it possible to sort it based on number of entries in `AreaCodes`? But I only want one object every `RouteId`. Every `RouteId` can have two types of `InDirection = 1 or 2`. So in the above result would like to removed `{ "RouteId": "1", "InDirection": "2", "AreaCode": ["41108", "41109", "411011"], }` since it less entires on `AreaCode` as compared to `InDirection= 1`. – Bonzo Jul 10 '23 at 13:12
  • @Bonzo ... It is possible, but of cause you are aware that you just changed the requirements. The above current question's `finalResult` still does exactly match the outcomes of any of the above answer's implemented approaches/solutions. One does either change requirements in time by editing the original question or one does open a new question which targets exclusively the requirement part which has changed (is new). – Peter Seliger Jul 10 '23 at 13:18
  • I just posted new question. You can have a look :) – Bonzo Jul 10 '23 at 13:40
  • @Bonzo ... and I just provided another solution which takes the changed requirements into account. – Peter Seliger Jul 10 '23 at 14:09
  • @Bonzo ... [The new question of yours now changes or omits the underlying basic data structure](https://stackoverflow.com/questions/76654328/modify-array-of-objects-based-on-condition-and-sort-the-final-result). This does not make any sense. The newly provided data structure, which seems to resemble the final result you have been originally ask for needs an entirely different approach in order to achieve the new goal of yours, thus making the processing of your originally provided data structure more _"resource hungry"_ and much more complicated, thus less understandable and less maintainable. – Peter Seliger Jul 10 '23 at 15:35
  • @Bonzo ... The new question should have ask for **the most straightforward solution** which takes as input the originally provide `apiResponse` data and has as output the data structure of the already here changed requirements. – Peter Seliger Jul 10 '23 at 15:37
  • Since the above answer does provide well explained solutions to the OP's even changing requirements (also each fully covering the requirements) a comment which points to the answer's technical faults/failures is much appreciated. Otherwise neither the audience nor the author of the answer know what can be taken from the -1 vote. The latter in addition also is left without a chance of improving the answer. – Peter Seliger Jul 10 '23 at 17:29
-1

Use reduce to get the final result.

const transformedData = apiResponse.reduce((acc, val) => {
  const existingItem = acc.find(
    item => item.RouteId === val.RouteId && item.InDirection === val.InDirection
  );
  if (existingItem) {
    existingItem.AreaCode.push(val.AreaCode);
  } else {
    acc.push({ ...val, AreaCode: [val.AreaCode] });
  }
  return acc;
}, []);

Mizan Rifat
  • 54
  • 2
  • 8
  • Do you think it is possible to have final result where array will have object where `AreaCode` have most entries. for eg. In the above finalResult, for `RouteId = 1` and `InDirection = 1`, it has more entires for AreaCode i.e. 4 as compared to RouteId = 1 and InDirection = 2. So final result will have `finalResult = [{ "RouteId": "1", "InDirection": "1", "AreaCode": ["41108", "41109", "41110", "41111"], }, { "RouteId": "2", "InDirection": "1", "AreaCode": ["41112", "41114"], }]` – Bonzo Jul 10 '23 at 10:59
  • I didn't ger your question properly. Do you want to sort the array? – Mizan Rifat Jul 10 '23 at 11:02
  • Yes, sort it based on most entries in `AreaCode`. But I only want one object every RouteId. Every `RouteId` can have two types of `InDirection = 1 or 2`. So in the above result I removed `{ "RouteId": "1", "InDirection": "2", "AreaCode": ["41108", "41109", "411011"], }` since it less entires on `AreaCode` as compared to `InDirection= 1`. – Bonzo Jul 10 '23 at 11:13
  • if you have only two types of InDirection you can use following code ``` const filteredItems = transformedData.filter(item => { const otherItem = transformedData.find( i => i.RouteId === item.RouteId && i.InDirection !== item.InDirection ); if (otherItem) { return item.AreaCode.length > otherItem.AreaCode.length; } else { return true; } }) ); ``` – Mizan Rifat Jul 10 '23 at 12:00
  • @MizanRifat ... looking up an `existingItem` for each iteration step via `find` (which itself is an iteration based task) will be quite expensive for vast amounts of response data items of varying `RouteId` and `InDirection` values. – Peter Seliger Jul 10 '23 at 12:25
  • @MizanRifat I think in the above solution in case of equal entires of `AreaCode` it will not consider any and it is also not giving sorted `finalResult` array – Bonzo Jul 10 '23 at 12:53
  • @Bonzo this is the second step. You have to apply the first function's return value to the second function – Mizan Rifat Jul 10 '23 at 14:55
  • Yes, I did that and it was missing case of equal entires of AreaCode and data is not sorted – Bonzo Jul 10 '23 at 14:58
-1
 const res = apiResponse.map((e) => {
  const f = apiResponse.filter((f) => f.RouteId === e.RouteId);
  var r = {};
  f.forEach((e) => {
    r = {
      RouteId: e.RouteId,
      InDirection: e.InDirection,
      AreaCode: r.AreaCode?[...r.AreaCode, e.AreaCode]:[e.AreaCode],
    };
  });

  return r;
});
JOo
  • 17
  • 5
-1

Let's go by a very readable and simple approach.

// get all unique combinations of RouteId and InDirection
const set1 = new Set();
apiResponse.forEach(({RouteId, InDirection}) => set1.add(`${RouteId}, ${InDirection}`));

// map over set and create required array
const finalResult = [...set1].map(s => {
  const [RouteId, InDirection] = s.split(', ');
  return {
    RouteId, 
    InDirection,
    AreaCodes: apiResponse.filter(r => r.RouteId === RouteId && r.InDirection === InDirection).map(r => r.AreaCode)
  }
})
Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122
  • Is it possible to sort it based on number of entries in `AreaCodes`? But I only want one object every `RouteId`. Every `RouteId` can have two types of `InDirection = 1 or 2`. So in the above result would like to removed `{ "RouteId": "1", "InDirection": "2", "AreaCode": ["41108", "41109", "411011"], }` since it less entires on `AreaCode` as compared to `InDirection= 1`. – Bonzo Jul 10 '23 at 11:33
  • 1
    Since having chosen a `Set` based approach, wasn't it obvious while iterating the `apiResponse` array and aggregating unique keys on `set1`, that one already there should have created and/or accessed the new item, and also have aggregated all `AreaCode` values(, thus better having chosen a `Map`)? Does it make any sense, for each iteration step of a mapping task to `filter` again and again any correctly matching value from the already before processed `apiResponse` array? ...And then `map` the resulting array again? – Peter Seliger Jul 10 '23 at 12:43