0

I receive an array of entries from form using FormData(). It consists of information about the recipe. Like this:

const dataArr = [
  ['title', 'pizza'],
  ['image', 'url'],
  ['quantity-0', '1'],
  ['unit-0', 'kg'],
  ['description-0', 'Flour'],
  ['quantity-1', '2'],
  ['unit-1', 'tbsp'],
  ['description-1', 'Olive oil'],
  ... // more ingredients
];

which I need to reorganize in new object, like this:

const recipe = {
  title: 'pizza',
  image: 'url',
  ingredients: [
    { quantity: '1', unit: 'kg', ingredient: 'Flour' },
    { quantity: '2', unit: 'tbsp', ingredient: 'Olive oil' },
    ...
  ],
};

So, for ingredients array I need to create multiple objects from received data. I came up with needed result, but it's not clean. I would appreciate your help coming up with universal function, when number of ingredients is unknown.

My solution: Form receives 6 ingredients max, therefore:

const ingredients = [];

// 1. Create an array with length of 6 (index helps to get ingredient-related data looping over the array)
const arrayOf6 = new Array(6).fill({});

arrayOf6.forEach((_, i) => {

  // 2. In each iteration filter over all data to get an array for each ingredient
  const ingArr = dataArr.filter(entry => {
    return entry[0].startsWith(`unit-${i}`) ||
    entry[0].startsWith(`quantity-${i}`) ||
    entry[0].startsWith(`ingredient-${i}`);
  });

  // 3. Loop over each ingredient array and rename future properties
  ingArr.forEach(entry => {
    [key, value] = entry;

    if(key.includes('ingredient')) entry[0] = 'description';
    if(key.includes('quantity')) entry[0] = 'quantity';
    if(key.includes('unit')) entry[0] = 'unit';
  });

  // 4. Transform array to object and push into ingredients array
  const ingObj = Object.fromEntries(ingArr);
  ingredients.push(ingObj);
});

// To finalize new object
const dataObj = Object.fromEntries(dataArr);
const recipe = {
  title: dataObj.title,
  image: dataObj.image,
  ingredients,
};

3 Answers3

0

You don't need arrayOf6. You never use its elements for anything -- it seems like you're just using it as a replacement for a loop like for (let i = 0; i < 6; i++).

Just loop over dataArr and check whether the name has a number at the end. If it does, use that as an index into the ingredients array, otherwise use the name as the property of the ingredients object. Then you don't need to hard-code a limit to the number of ingredients.

const dataArr = [
  ['title', 'pizza'],
  ['image', 'url'],
  ['quantity-0', '1'],
  ['unit-0', 'kg'],
  ['description-0', 'Flour'],
  ['quantity-1', '2'],
  ['unit-1', 'tbsp'],
  ['description-1', 'Olive oil'],
  // more ingredients
];

const recipe = {
  ingredients: []
};

dataArr.forEach(([name, value]) => {
  let match = name.match(/^(\w+)-(\d+)$/);
  if (match) {
    let type = match[1];
    let index = match[2];
    if (!recipe.ingredients[index]) {
      recipe.ingredients[index] = {};
    }
    recipe.ingredients[index][type] = value;
  } else {
    recipe[name] = value;
  }
});

console.log(recipe);
Barmar
  • 741,623
  • 53
  • 500
  • 612
0

Separating the key-parsing logic helps me think about the concerns more clearly:

const orderedKeyRegExp = /^(.+)-(\d+)$/;

function parseKey (key) {
  const match = key.match(orderedKeyRegExp);
  // Return -1 for the index if the key pattern isn't part of a numbered sequence
  if (!match) return {index: -1, name: key};
  return {
    index: Number(match[2]),
    name: match[1],
  };
}

function transformRecipeEntries (entries) {
  const result = {};
  const ingredients = [];

  for (const [key, value] of entries) {
    const {index, name} = parseKey(key);
    if (index >= 0) (ingredients[index] ??= {})[name] = value;
    //               ^^^^^^^^^^^^^^^^^^^^^^^^^
    // Assign an empty object to the element of the array at the index
    // (if it doesn't already exist)
    else result[name] = value;
  }

  if (ingredients.length > 0) result.ingredients = ingredients;
  return result;
}

const entries = [
  ['title', 'pizza'],
  ['image', 'url'],
  ['quantity-0', '1'],
  ['unit-0', 'kg'],
  ['description-0', 'Flour'],
  ['quantity-1', '2'],
  ['unit-1', 'tbsp'],
  ['description-1', 'Olive oil'],
  // ...more ingredients
];

const result = transformRecipeEntries(entries);
console.log(result);
jsejcksn
  • 27,667
  • 4
  • 38
  • 62
0

You'll have to parse the values of the input array to extract the index. To build the result object, you could use reduce:

const dataArr = [['title', 'pizza'],['image', 'url'],['quantity-0', '1'],['unit-0', 'kg'],['description-0', 'Flour'],['quantity-1', '2'],['unit-1', 'tbsp'], ['description-1', 'Olive oil']];

const recipe = dataArr.reduce((recipe, [name, value]) => {
  const [, prop, index] = name.match(/^(\w+)-(\d+)$/) ?? [];
  if (prop) {
    (recipe.ingredients[index] ??= {})[prop] = value;
  } else {
    recipe[name] = value;
  }
  return recipe;
}, { ingredients: [] });

console.log(recipe);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • (Because I've seen several of your answers which demonstrate advanced JS knowledge,) I am almost certain that you are aware that unused destructured array element variables can be elided in your assignment (`const [, prop, index] = /*....*/` rather than using `_`). Do you use it anyway as a code style choice? If so, what do you use when there are multiple unusused in one statement (or later in the same block)? – jsejcksn Jul 27 '22 at 07:26
  • Yes, you're right. It would not work with multiple `_`. I think I took this habit from Python... So I'll go with just the comma here. Thanks! – trincot Jul 27 '22 at 07:55
  • Sure; thanks for sharing that bit about Python — I'm not as familiar with its syntax because I rarely use it: would you say that [this](https://stackoverflow.com/a/47061782/438273) is idiomatic? – jsejcksn Jul 27 '22 at 10:45
  • Opinions will differ, but yes, that looks like how it would be done. – trincot Jul 27 '22 at 10:58