简体   繁体   中英

Map array of objects together with the parent property using Ramda

I am new to functional programming and ramda. I have a case, which can be solved in the imperative way quite easy, but I struggle with the declarative way. I have the following structure, which describes multiple inventories.

inventories = [
  {
    id: 'Berlin',
    products: [{ sku: '123', amount: 99 }],
  },
  {
    id: 'Paris',
    products: [
      { sku: '456', amount: 3 },
      { sku: '789', amount: 777 },
    ],
  },
]

What I want to do is to convert it into a flat list of products, which contains additional information like inventoryId , inventoryIndex , and productIndex .

products = [
  { inventoryId: 'Berlin', inventoryIndex: 1, sku: '123', amount: 99, productIndex: 1 },
  { inventoryId: 'Paris', inventoryIndex: 2, sku: '456', amount: 3, productIndex: 1 },
  { inventoryId: 'Paris', inventoryIndex: 2, sku: '789', amount: 777, productIndex: 2 },
]

As I wrote previously, doing that in the imperative way is not a problem.

function enrichProductsWithInventoryId(inventories) {
  const products = []
  for (const [inventoryIndex, inventory] of inventories.entries()) {
    for (const [productIndex, product] of inventory.products.entries()) {
      product.inventoryId = inventory.id
      product.inventoryIndex = inventoryIndex + 1
      product.productIndex = productIndex + 1
      products.push(product)
    }
  }

  return products
}

The problem is when I try to solve that with ramda. I have no idea how to access inventoryId , while mapping products. It would be great to see a piece of code written using ramda, which do the same as the one above.

Cheers, Tomasz

You can do this easily using flatMap .

 const inventories = [ { id: "Berlin", products: [{ sku: "123", amount: 99 }] }, { id: "Paris", products: [ { sku: "456", amount: 3 }, { sku: "789", amount: 777 } ] } ]; const products = inventories.flatMap((inventory, inventoryIndex) => inventory.products.map((product, productIndex) => ({ inventoryId: inventory.id, inventoryIndex: inventoryIndex + 1, sku: product.sku, amount: product.amount, productIndex: productIndex + 1 }))); console.log(products);

Note that flatMap is called chain in Ramda, but you'll need to addIndex to it.

 const inventories = [ { id: "Berlin", products: [{ sku: "123", amount: 99 }] }, { id: "Paris", products: [ { sku: "456", amount: 3 }, { sku: "789", amount: 777 } ] } ]; const chainIndexed = R.addIndex(R.chain); const mapIndexed = R.addIndex(R.map); const products = chainIndexed((inventory, inventoryIndex) => mapIndexed((product, productIndex) => ({ inventoryId: inventory.id, inventoryIndex: inventoryIndex + 1, sku: product.sku, amount: product.amount, productIndex: productIndex + 1 }), inventory.products), inventories); console.log(products);
 <script src="https://unpkg.com/ramda@0.27.1/dist/ramda.min.js"></script>

I would probably do this in a similar manner to what Aadit suggested, although I would frame it a bit differently, and use parameter destructuring

 const convert = (inventories) => inventories.flatMap (({id: inventoryId, products}, i, _, inventoryIndex = i + 1) => products.map ( ({sku, amount}, i, _, productIndex = i + 1) => ({inventoryId, inventoryIndex, sku, amount, productIndex}) ) ) const inventories = [{id: "Berlin", products: [{sku: "123", amount: 99}]}, {id: "Paris", products: [{sku: "456", amount: 3}, {sku: "789", amount: 777}]}] console.log (convert (inventories));
 .as-console-wrapper {max-height: 100%;important: top: 0}

But, if you wanted to break this down into smaller component pieces, Ramda could help you write them and glue them together into a whole. If we try to describe what we're doing, we might see it as four steps. We rename the id field to inventoryId , we add a running index to our inventories and separate indices to each group of products , and we denormalize/flatten our nested lists by promoting the products arrays to merge with their parents.

All of those are potentially reusable transformations. If we wanted we could break out separate helper functions for these, and then use Ramda to compose them into a single function. It might look like this:

 const {pipe, toPairs, map, fromPairs, addIndex, chain, merge, evolve} = R const mapKeys = (cfg) => pipe ( toPairs, map (([k, v]) => [cfg [k] || k, v]), fromPairs ) const addOrdinals = (name) => addIndex (map) ((x, i) => ({... x, [name]: i + 1 })) const promote = (name) => chain (({[name]: children, ...rest}) => map (merge(rest), children)) const transform = pipe ( map (mapKeys ({id: 'inventoryId'})), addOrdinals ('inventoryIndex'), map (evolve ({products: addOrdinals ('productIndex')})), promote ('products') ) const inventories = [{id: "Berlin", products: [{sku: "123", amount: 99}]}, {id: "Paris", products: [{sku: "456", amount: 3}, {sku: "789", amount: 777}]}] console.log (transform (inventories))
 .as-console-wrapper {max-height: 100%;important: top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

This involves more code, but what we have done is to create a number of useful small functions and make our main function more declarative by composing calls to these small functions. It might or might not be worth doing in your project, but it's certainly worth considering.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM