0

I am getting data from API structured like this:

interface ProductModel {
    prod_id: string,
    brand: string,
    ...
}

interface OrderModel {
    order_id: string,
    prod_id: string,
    ...
}

const data = {
    products: ProductModel[],
    orders: OrderModel[]
}

What I want is to restructure the data to group the orders of a product and the product info in one object:

const expectedStructure = {
    prod_id: string,
    brand: string,
    ...,
    orders: OrderModel[]
}

I suppose that with a reduce it could be done easily, but I don't quite understand how it works. Could someone help me with this example?

3 Answers3

1

You could just use the spread operator which "flattens" the object's properties.

const expectedStructure = { ...products, orders }
Elio Rahi
  • 166
  • 8
1
const data = {
    products: ProductModel[],
    orders: OrderModel[]
}

const {products, orders} = data;

const expectedStructure = {
   ...products,
   orders,
}

checkout: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

checkout: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

Vinod Liyanage
  • 945
  • 4
  • 13
0

Reduce can be used but you dont really need it, simple .map with .filter and ... spread operator:

const products = [
  { prod_id: "1", brand: "brand1" },
  { prod_id: "2", brand: "brand1" },
  { prod_id: "3", brand: "brand2" },
  { prod_id: "4", brand: "brand2" },
  { prod_id: "5", brand: "brand3" }
];
const orders = [
  { order_id: "1", prod_id: "1" },
  { order_id: "2", prod_id: "2" },
  { order_id: "3", prod_id: "3" },
  { order_id: "4", prod_id: "3" },
  { order_id: "5", prod_id: "4" }
];

const data = {
  products: products,
  orders: orders
};

function groupData(data) {
  if (!data) return data;
  return data.products.map((p) => ({
    ...p,
    orders: data.orders.filter((x) => x.prod_id === p.prod_id)
  }));
}

console.log(groupData(data));

Adding a little optimization asked in comments using Map class. With that you will only need 1 loop over initial Orders array to build the Map object and then you will have a constant time of element retrieval:

const products = [
  { prod_id: "1", brand: "brand1" },
  { prod_id: "2", brand: "brand1" },
  { prod_id: "3", brand: "brand2" },
  { prod_id: "4", brand: "brand2" },
  { prod_id: "5", brand: "brand3" }
];
const orders = [
  { order_id: "1", prod_id: "1" },
  { order_id: "2", prod_id: "2" },
  { order_id: "3", prod_id: "3" },
  { order_id: "4", prod_id: "3" },
  { order_id: "5", prod_id: "4" }
];

const data = {
  products: products,
  orders: orders
};

function buildMap(data) {
  const res = new Map();
  data.orders.forEach((order) => {
    if (res.has(order.prod_id)) {
      res.get(order.prod_id).push(order);
    } else {
      res.set(order.prod_id, [order]);
    }
  });
  return res;
}

function groupData(data) {
  if (!data) return data;
  const ordersMap = buildMap(data);
  return data.products.map((p) => ({
    ...p,
    orders: ordersMap.get(p.prod_id) || []
  }));
}

console.log(groupData(data));
Sergey Sosunov
  • 4,124
  • 2
  • 11
  • 15
  • and if I know that the order can only be of one product, it would be better if after order filter, remove them from data? – Emili Bellot Pulido Aug 30 '22 at 09:18
  • @EmiliBellotPulido You can but GC (Garbage Collector) will do the work for you, for things that are not `global` you dont really need to worry about. It is if you are worried about your `data` object. if you are worried about `results` - you can just add `.filter(x => x.orders.length > 0)` after `.map` operator. But i could misunderstood you, sorry. – Sergey Sosunov Aug 30 '22 at 09:22
  • @EmiliBellotPulido Provide me some example and i will clarify – Sergey Sosunov Aug 30 '22 at 09:22
  • Your answer is correct, but I am thinking that foreach product I have to filter every time the entire order array and, maybe, as I know that the orders correspons only to one product, it would be better if I only filter the remaining orders. – Emili Bellot Pulido Aug 30 '22 at 09:29
  • @EmiliBellotPulido You have a good ideas but modifying array itself is also costly and it basically depends on how much items you have in the arrays. For something that is below 100 - i would not even bother with that. But if you are curious - someone already tried and measured your idea: https://medium.com/@justintulk/javascript-performance-array-slice-vs-array-filter-4573d726aacb – Sergey Sosunov Aug 30 '22 at 09:35
  • @EmiliBellotPulido But what can really help you - using `Map` class. You can build it having `key = prod_id` and `value = order`. You will have only 1 loop over your `orders` array to build this Map object and then for your products you will be just getting order from Map with prod_id key. – Sergey Sosunov Aug 30 '22 at 09:43
  • Yes, Map is perfect for this, but the problem is that I am working with react, and Map is more dificult to iterate... – Emili Bellot Pulido Aug 30 '22 at 11:29
  • @EmiliBellotPulido It is only used to prepare data, you can create a useMemo hook that will obtain your API result as a dependency and will return a proceeded records (products with orders array inside), but all the logic and Map usage will be inside of this useMemo function, no Maps outside. – Sergey Sosunov Aug 30 '22 at 11:47
  • Sorry, I have loosed in here – Emili Bellot Pulido Aug 30 '22 at 14:21