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,
};
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);
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);
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);
Related
This is a hard one to explain, but here goes. I need to clean an array of 'path' strings where if a path has sub properties it not include the top level property. but only the child properties
E.g
[
'firstName',
'address',
'address.local.addressLine1',
'address.local.addressLine2',
'address.local',
]
Should become:
[
'firstName',
'address.local.addressLine1',
'address.local.addressLine2',
'address.local',
]
I have a fairly verbose function kind of working so far, but looking to see if there is a more elegant/better solution than this:
function cleanCollisions(array) {
var output = [];
// return [...new Set(array)];
var map = array.reduce(function(set, field) {
if (!Boolean(field)) {
return set;
}
////////////////
var rootKey = field.split('.')[0];
if(!set[rootKey]) {
set[rootKey] =[];
}
var count = field.split('.').length -1;
if(count) {
set[rootKey].push(field);
}
return set;
}, {})
for(const key in map) {
value = map[key];
if(value.length) {
output.push(value);
} else {
output.push(key);
}
}
////////////////
return output.flat();
}
I'd first iterate over the array to extract the top property of all strings that have sub properties, then filter out all those top properties.
const input = [
'firstName',
'address',
'address.local.addressLine1',
'address.local.addressLine2',
'address.local',
];
const topLevelProps = new Set();
for (const str of input) {
const match = str.match(/^(.*?)\./);
if (match) {
topLevelProps.add(match[1]);
}
}
const output = input.filter(str => !topLevelProps.has(str));
console.log(output);
A variation of the answer by CertainPerformance but using filter and map instead of regex:
const paths = [
'firstName',
'address',
'address.local.addressLine1',
'address.local.addressLine2',
'address.local',
];
const roots = paths.filter(p => p.includes('.')).map(p => p.split('.')[0]);
const cleansed = paths.filter(p => p.includes('.') || !roots.includes(p));
console.log(cleansed);
So I have an array looks like this:
[
{ date: '2021-07-07' },
{ date: '2021-07-07' },
{ date: '2021-07-07' },
{ date: '2021-07-08' },
{ date: '2021-07-09' },
{ date: '2021-07-10' },
{ date: '2021-07-10' }
];
How can I split into 3 array (What I mean is 1 group for unique dates, and another group for duplicate, but if there more than 1 duplicate group, it should separate into another group)
It will looks like this after split
Array 1
[{"date": "2021-07-07"},{"date": "2021-07-07"},{"date": "2021-07-07"}]
Array 2
[{"date": "2021-07-08"},{"date": "2021-07-09"}]
Array 3
[{"date": "2021-07-10"},{"date": "2021-07-10"}]
Below is my code so far, but it only work if the duplicate on have 1
const findDuplicates = arr => {
let sorted_arr = arr.slice().sort();
let result = [];
for (let i = 0; i < sorted_arr.length - 1; i++) {
if (sorted_arr[i + 1].date == sorted_arr[i].date) {
result.push(sorted_arr[i]);
}
}
return result;
};
const filterSame = arr => {
let temp = findDuplicates(arr);
const result = arr.filter(date => date.date == temp[0].date);
return result;
};
const filterUnique = array => {
let result = array.filter(
(e, i) => array.findIndex(a => a['date'] === e['date']) === i
);
let temp = findDuplicates(array);
result = result.filter(function(obj) {
return obj.date !== temp[0].date;
});
return result;
};
You could create a map, keyed by dates, and with as value an empty array. Then populate those arrays. Finally extract the arrays that have more than one element, and add to that result a combined array of those single-element arrays:
function group(data) {
let map = new Map(data.map(o => [o.date, []]));
for (let o of data) map.get(o.date).push(o);
return [
...[...map.values()].filter(({length}) => length > 1),
[...map.values()].filter(({length}) => length == 1).flat()
];
}
let data = [{"date":"2021-07-07"},{"date":"2021-07-07"},{"date":"2021-07-07"},{"date":"2021-07-08"},{"date":"2021-07-09"},{"date":"2021-07-10"},{"date":"2021-07-10"}];
console.log(group(data));
Explanation
let map = new Map(data.map(o => [o.date, []]));
This creates a Map. The constructor is given an array of pairs. For the example data that array looks like this:
[
["2021-07-07", []],
["2021-07-07", []],
["2021-07-07", []],
["2021-07-08", []],
["2021-07-09", []],
["2021-07-10", []],
["2021-07-10", []]
]
The Map constructor will create the corresponding Map, which really removes duplicates. You can imagine it as follows (although it is not a plain object):
{
"2021-07-07": [],
"2021-07-08": [],
"2021-07-09": [],
"2021-07-10": []
}
Then the for loop will populate these (four) arrays, so that the Map will look like this:
{
"2021-07-07": [{date:"2021-07-07"},{date:"2021-07-07"},{date:"2021-07-07"}],
"2021-07-08": [{date:"2021-07-08"}],
"2021-07-09": [{date:"2021-07-09"}],
"2021-07-10": [{date:"2021-07-10"},{date:"2021-07-10"}]
}
In the return statement the Map values are converted to an array twice. Once to filter the entries that have more than 1 element:
[
[{date:"2021-07-07"},{date:"2021-07-07"},{date:"2021-07-07"}],
[{date:"2021-07-10"},{date:"2021-07-10"}]
]
...and a second time to get those that have 1 element:
[
[{date:"2021-07-08"}],
[{date:"2021-07-09"}],
]
The second array is flattened with flat():
[
{date:"2021-07-08"},
{date:"2021-07-09"},
]
The final result concatenates the first array (with duplicate dates) with the flattened array (with unique dates), using the spread syntax (...)
This could be done in a 2 step process
a typical group by operation based on the date properties
aggregating together all the groups which only have 1 result.
const input = [{"date":"2021-07-07"},{"date":"2021-07-07"},{"date":"2021-07-07"},{"date":"2021-07-08"},{"date":"2021-07-09"},{"date":"2021-07-10"},{"date":"2021-07-10"}]
const grouped = input.reduce ( (acc,i) => {
if(!acc[i.date]) acc[i.date] = []
acc[i.date].push(i);
return acc;
},{});
const final = Object.values(Object.entries(grouped).reduce( (acc,[key,values]) => {
if(values.length>1) {
acc[key] = values;
}
else{
if(!acc.others) acc.others = [];
acc.others.push(values[0]);
}
return acc
},{}))
console.log(final);
Note that if you added, for example, 2021-07-11 to your original array, this would get lumped in with all the other "unique" elements. This may or may not be what you expected, but was not clear from the question.
Another option is to sort the array before grouping. If the current date being looped isn't the same as its neighbors, then it doesn't have duplicates.
const input = [{"date":"2021-07-07"},{"date":"2021-07-07"},{"date":"2021-07-07"},{"date":"2021-07-08"},{"date":"2021-07-09"},{"date":"2021-07-10"},{"date":"2021-07-10"}]
input.sort((a,b) => a.date.localeCompare(b.date))
const grouped = input.reduce((acc, o, i, arr) => {
const key = o.date === arr[i-1]?.date || o.date === arr[i+1]?.date
? o.date
: 'lonely'
acc[key] ||= []
acc[key].push(o);
return acc;
}, {});
console.log(Object.values(grouped));
I have an array with the following values:
['persona1', 'Persona2', 'Persona3', 'Persona4']
And I have another array with the names of each person:
['JUAN', 'CARLOS', 'PEDRO','MATEO']
I need to generate a JSON object like the following:
{ persona1: 'JUAN', persona2: 'CARLOS', persona3: 'PEDRO', persona4: 'MATEO' }
Each value in the first array becomes the key for the corresponding value in the second array.
How can I do this in Javascript?
Loop over the array and generate the object dynamically.
let arr1 = [ 'foo', 'bar' ]
let arr2 = [ 'baz', 'qux' ]
let obj = {}
for (let i = 0; i < arr1.length; i++) obj[arr1[i]] = arr2[i];
console.log(obj);
You could use reduce here
const arr1 = ["persona1", "Persona2", "Persona3", "Persona4"];
const arr2 = ["JUAN", "CARLOS", "PEDRO", "MATEO"];
const result = arr1.reduce((acc, curr, i) => {
acc[curr] = arr2[i];
return acc;
}, {});
console.log(result);
You can create an empty object, loop over the first array, and take each item in the array as the key, and take each item of the second array as the value.
Then you can you the JSON.stringify() function to convert that object to JSON string.
const arr1 = ['persona1', 'Persona2', 'Persona3', 'Persona4']
const arr2 = ['JUAN', 'CARLOS', 'PEDRO', 'MATEO']
const output = {}
arr1.forEach((item, i) => output[item] = arr2[i])
console.log(JSON.stringify(output))
const personKeyArr = ['persona1', 'Persona2', 'Persona3', 'Persona4']
const personValArr = ['JUAN', 'CARLOS', 'PEDRO','MATEO']
const retValue = {}
personKeyArr.forEach((x,i)=>retValue[x] = [personValArr[i]])
What you want to do is a combination of zipping two arrays together and then converting the resulting pairs into the key/value entries in an object. As #Phil mentioned in their comment, the lodash library has a function to do this called zipObject, but if you don't want to load that entire library, it's not hard to create your own with reduce. Here's one version (found at https://lowrey.me/lodash-zipobject-in-es6-javascript/):
const zipObject = (props, values) => {
return props.reduce((prev, prop, i) => {
return Object.assign(prev, { [prop]: values[i] });
}, {});
};
Running it on your data:
keys = ['persona1', 'Persona2', 'Persona3', 'Persona4']
nombres = ['JUAN', 'CARLOS', 'PEDRO','MATEO']
zipObject(keys, nombres)
//=>
{
persona1: 'JUAN',
Persona2: 'CARLOS',
Persona3: 'PEDRO',
Persona4: 'MATEO'
}
My array comes like this
var data=[{PRODUCT : P1}, {PRODUCT: P2}]
I wantt to convert this into [P1, P2].
Sometimes my array comes like this
var data=[{ITEM: I1, QUANTITY:1}, {ITEM: I2, QUANTITY:2}]
I wantt to convert this into [I1, I2].
so can we make a common function, where I just want to extract particular value of array and make a new array.
p.s. Thank you in advance
I tried to write the logic like this:
data.map((d, index) => { var result= [];
result.includes(d[0]); })
but it,s not dynamic
You could define a function which will always get the first value of the first object key, this should satisfy your needs based on the above
var data1 = [{
ITEM: 'I1',
QUANTITY: 1
}, {
ITEM: 'I2',
QUANTITY: 2
}]
var data2 = [{
PRODUCT: 'P1'
}, {
PRODUCT: ' P2'
}]
function getArrayOfValues(list) {
return list.reduce((acc, x) => {
const firstValue = Object.values(x)[0];
acc.push(firstValue)
return acc;
}, [])
}
const result1 = getArrayOfValues(data1)
console.log(result1)
const result2 = getArrayOfValues(data2)
console.log(result2)
function getProductOrItem(list) {
return list.reduce((accumulator, obj) => {
if (obj.PRODUCT) {
accumulator.push(obj.PRODUCT);
} else if (obj.ITEM) {
accumulator.push(obj.ITEM);
}
return accumulator;
}, [])
}
you can iterate through your array with map() method and inside it extract the value of a first entity of an object in your array and simply get a new array with all values:
const data1 =[{PRODUCT : 'P1'}, {PRODUCT: 'P2'}]
const data2 = [{ITEM: 'I1', QUANTITY: 1}, {ITEM: 'I2', QUANTITY: 2 }]
const transformValuesOfArray = (arrayToTransform) =>
arrayToTransform.map(value => {
const firstObjectValue = Object.values(value)[0]
return firstObjectValue
})
console.log(transformValuesOfArray(data1))
console.log(transformValuesOfArray(data2))
I am working on a data format transformation which showed on the title, but I don't know how to figure out. I have tried to write the blow code to add the variable name for second dimensional array:
const data = [
{ id: 1, var1: 'val1', var2: 'val2', varX: ['time1', 'time2', 'time3'] },
{ id: 2, var1: 'val2', var2: 'val3', varX: ['time4', 'time5', 'time6'] },
];
const test = data.map((o) => o.varX);
for (i = 0; i < test.length; i++) {
const test2 = test[i].reduce((res, cur, idx) => {
res[`varX${idx}`] = cur;
return res;
}, {});
console.log(test2);
}
but what I expected result should be:
[{id:1, var1:val1, var2:val2, varX1:time1, varX2:time2, varX3:time3},{id:2, var1:val2, var2:val3, varX1:time4, varX2:time5, varX3:time6}]
Could anyone can guide me how to convert the data?
The issue with your code is that you're extracgint the properties that are not varX from the data by mapping only the values that have a varX. Then you do a good job at reducing them, but then you would have to merge the "left over" properties into the new object, it sounds a bit cumbersome to me. Instead, you could do something like the following:
const data = [
{ id: 1, var1: "val1", var2: "val2", varX: ["time1", "time2", "time3"] },
{ id: 2, var1: "val2", var2: "val3", varX: ["time4", "time5", "time6"] },
];
const test = data.map((o) => {
return Object.entries(o).reduce((p, [k, v]) => {
if (k === "varX") {
for (let index = 0; index < v.length; index++) {
p[`varX${index + 1}`] = v[index];
}
} else {
p[k] = v;
}
return p;
}, {});
});
console.log(test);
First, you map the data objects, then for each object you reduce their key/value pairs and check if the property is varX, if so, then you iterate through the array and assign to the new object the varX${index + 1} since you want the first property to be varX1, otherwise you just keep the same key/value pair.
You need create objects in reducemethod instead array, after replace varX to this new object by second map() method
const data = [{'id':1, 'var1':'val1', 'var2':'val2', 'varX':['time1', 'time2', 'time3']},
{'id':2, 'var1':'val2', 'var2':'val3', 'varX':['time4', 'time5', 'time6']}]
const test = data.map(item => item.varX);
const test2 = {}
for (i = 0; i < test.length; i++) {
test2[i] = test[i].reduce((accum, item, index) => {
return { ...accum, [`varX${index}`]: item};
}, {});
}
const dataX = data.map((item, index) => {
delete item.varX
return { ...item, ...test2[index] }
});
console.log(dataX)
Because of the first map in test, you are operating only on values of varX. You can just add another operation to merge the original data[i] objects with your new reduced varX objects.
const data = [
{ id: 1, var1: 'val1', var2: 'val2', varX: ['time1', 'time2', 'time3'] },
{ id: 2, var1: 'val2', var2: 'val3', varX: ['time4', 'time5', 'time6'] },
];
const test = data.map((o) => o.varX);
for (i = 0; i < test.length; i++) {
const test2 = test[i].reduce((res, cur, idx) => {
res[`varX${idx}`] = cur;
return res;
}, {});
// exclude varX and merge your new varX${i} back into data[i]
const {varX, ...test3} = Object.assign(data[i], test2);
console.log(test3);
}
Transform in-place code. I've deliberately avoided reduce, because the syntax becomes less readable and it adds performance overhead. The downside is that this code mutates the original data (transforms the data in-place). Can be easily mitigated by creating copies of objects val while mapping.
[1,2,3,4,5,6].forEach( i => eval(`time${i} = "time${i}";val${i} = "val${i}"`) );
const data = [{id:1, var1:val1, var2:val2, varX:[time1, time2, time3]}, {id:2, var1:val2, var2:val3, varX:[time4, time5, time6]}]
data.forEach((val, i)=>{
val.varX.forEach( (X,i) => val[`varX${i}`]=X );
delete val.varX;
console.log(val);
return val;
});
console.log(data);
I've created a version below using reduce and spread syntax. You can look at the spread syntax as simply an operation to copy properties to the new object being generated. I've used destructuring syntax to isolate varX from the rest of the properties and put them in varX and noVarX.
This also has the advantage of deep copying except for the final outside referenced objects in data.
[1,2,3,4,5,6].forEach( i => eval(`time${i} = "time${i}";val${i} = "val${i}"`) );
const data = [{id:1, var1:val1, var2:val2, varX:[time1, time2, time3]}, {id:2, var1:val2, var2:val3, varX:[time4, time5, time6]}]
const expanded = data.map(val => {
const {varX, ...noVarX} = val;
return {
...noVarX,
...varX.reduce( (res, cur, idx) =>
({ ...res, [`varX${idx}`]: cur }), {})
};
});
console.log(expanded);