Modify and Combine Array of Objects by ID

Viewed 54

I have the following array of objects:

[
    {
        "id": 1,
        "price": 22,
        "from": "00:00:00",
        "to": "02:00:00"
    },
    {
        "id": 1,
        "price": 23,
        "from": "02:00:00",
        "to": "04:00:00"
    },
    {
        "id": 2,
        "price": 10,
        "from": "04:00:00",
        "to": "1.00:00:00"
    }
]

I need to restructure it, so it combines the objects by ID, and creates a new nested array of objects:

[
    {
        "id": 1,
        "prices": [
            {
                "price": 22,
                "from": "00:00:00",
                "to": "02:00:00"
            },
            {
                "price": 23,
                "from": "02:00:00",
                "to": "04:00:00"
            },
        ]
    }
    {
        "id": 2,
        "prices": [
            {
                "price": 10,
                "from": "04:00:00",
                "to": "1.00:00:00"
            }
        ]
    }
]

Is there a simple way to do this? I'm getting lost in split, forEach and maps. Thanks.

6 Answers

One way is to use a Map to collect the prices per id. First associate an empty prices list for each id, then iterate the data to populate those arrays, and finally extract the Map values to get the result:

const data = [{"id": 1,"price": 22,"from": "00:00:00","to": "02:00:00"},{"id": 1,"price": 23,"from": "02:00:00","to": "04:00:00"},{"id": 2,"price": 10,"from": "04:00:00","to": "1.00:00:00"}];

const map = new Map(data.map(({id}) => [id, { id, prices: [] }]));
for (const {id, ...rest} of data) map.get(id).prices.push(rest);
const result = [...map.values()];

console.log(result);

Grouping by hash can save you from calling .find() or .findIndex()

const data=[{id:1,price:22,from:"00:00:00",to:"02:00:00"},{id:1,price:23,from:"02:00:00",to:"04:00:00"},{id:2,price:10,from:"04:00:00",to:"1.00:00:00"}];

const result = Object.values(data.reduce((acc, { id, ...rest }) => {
    acc[id] ??= { id, prices: [] };
    acc[id].prices.push(rest);
    return acc;
}, {}));

console.log(result);
.as-console-wrapper {max-height: 100% !important; top: 0}

Yes, there is an easy way using reduce().

const input = [
  {
    id: 1,
    price: 22,
    from: "00:00:00",
    to: "02:00:00",
  },
  {
    id: 1,
    price: 23,
    from: "02:00:00",
    to: "04:00:00",
  },
  {
    id: 2,
    price: 10,
    from: "04:00:00",
    to: "1.00:00:00",
  },
];

const output = input.reduce((nested, cur) => {
  const objWithoutId = (({id, ...o}) => o)(cur);
  if (!nested[cur.id]) {
    nested[cur.id] = {
      id: cur.id,
      prices: [objWithoutId]
    };
  }
  else nested[cur.id].prices.push(objWithoutId);
  return nested;
}, {});

console.log(Object.values(output));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Explanation

We loop over the input using a JavaScript object. For every object we check it's ID. When we don't have that ID stored in our object already we create a new object containing the id and a prices array as well as the current value (without the id property). If we have encountered the same id already we already just need to push the current value (without the id property) to the already existing array.

Since we only have one loop and the lookups take O(1) time this algorithm takes O(n) to give a valid result.

Using this one-liner

const objWithoutId = (({id, ...o}) => o)(cur);

we create a new object which contains all properties except id.

Lat but not least we just need to get the values of the created JavaScript object using Object.values(). Instead of a JavaScript object you could also use a Map to perform the equivalent algorithm with the same runtime properties.

You can use the Array.reduce method with Array.findIndex to convert your data.

const data = [{
    "id": 1,
    "price": 22,
    "from": "00:00:00",
    "to": "02:00:00"
  },
  {
    "id": 1,
    "price": 23,
    "from": "02:00:00",
    "to": "04:00:00"
  },
  {
    "id": 2,
    "price": 10,
    "from": "04:00:00",
    "to": "1.00:00:00"
  }
];

const transformedData = data.reduce((acc, item) => {
  const priceData = {
    price: item.price,
    to: item.to,
    from: item.from,
  };

  const index = acc.findIndex(({
    id
  }) => id === item.id);
  if (index === -1) {
    return [
      ...acc,
      {
        id: item.id,
        prices: [
          priceData
        ]
      },
    ];
  } else {
    acc[index].prices.push(priceData);
    return acc;
  }
}, []);

console.log(transformedData);

const items = [
    {
        "id": 1,
        "price": 22,
        "from": "00:00:00",
        "to": "02:00:00"
    },
    {
        "id": 1,
        "price": 23,
        "from": "02:00:00",
        "to": "04:00:00"
    },
    {
        "id": 2,
        "price": 10,
        "from": "04:00:00",
        "to": "1.00:00:00"
    }
]

const result = items
.map(i => i.id)
.filter((item, pos, self) => self.indexOf(item) == pos)
.map( i => ({
  id : i, 
  prices : items
            .filter(item => item.id === i)
            .map(({ price, from, to}) => ({price, from , to}) )
    }) )
console.log(result)

You can use the Object.entries, Array#reduce and Array#map methods as follows:

const input = [ { "id": 1, "price": 22, "from": "00:00:00", "to": "02:00:00" }, { "id": 1, "price": 23, "from": "02:00:00", "to": "04:00:00" }, { "id": 2, "price": 10, "from": "04:00:00", "to": "1.00:00:00" } ],

      output = Object.entries(input.reduce(
          (acc, {id,...rest}) =>
          ({...acc, [id]: [...(acc[id] || []), rest]}), {}
      ))
      .map(([id, prices]) => ({id,prices}));
      
console.log( output );

Related