Lodash create object from array of arrays - javascript

I would like to use lodash to generete object from array of arrays. I use for it lodash.zipObject method and map. But it is not 1:1 what I would like to generate:
Input:
"rows": [
[
"stravi/aa",
"202001",
"59",
"51",
"2558.98",
"0.5358894453719162",
"1.9204668112983725",
"140",
"2.3466309084813943"
],
[
"stravi/ab",
"202003",
"3591",
"349",
"2246.09",
"0.41838214",
"3.57603358",
"50",
"4.82115474"
],
[
"stravi/ac",
"202007",
"3354",
"25",
"1975.76",
"0.74220667708",
"1.12321555541",
"11",
"0.9324532454"
]
]
dictionary: ['source', 'sessions', 'adClicks', 'adCost', 'CPC', 'CTR', 'goalCompletionsAll', 'goalConversionRateAll' ], [action.yearReportData]
output:
{
source: ['stravi/aa', 'stravi/ab', 'stravi/ac'],
sessions: ['202001', '202003', '202007']
...
}
I would like to use lodash, and I try by:
lodash.map(rows, arr =>
lodash.zipObject(['source', 'sessions', 'adClicks', 'adCost', 'CPC', 'CTR', 'goalCompletionsAll', 'goalConversionRateAll'], arr))
But is not correct... I received multiple object. I would like to have one object with all data. Like my example.

Unzip the rows to transpose the sub-arrays, and then use zip object:
const { flow, unzip, zipObject } = _
const fn = flow(
unzip,
arr => zipObject(['source', 'sessions', 'adClicks', 'adCost', 'CPC', 'CTR', 'goalCompletionsAll', 'goalConversionRateAll'], arr)
)
const rows = [["stravi/aa","202001","59","51","2558.98","0.5358894453719162","1.9204668112983725","140","2.3466309084813943"],["stravi/ab","202003","3591","349","2246.09","0.41838214","3.57603358","50","4.82115474"],["stravi/ac","202007","3354","25","1975.76","0.74220667708","1.12321555541","11","0.9324532454"]]
const result = fn(rows)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

why use third-party library, if may easy do it like so
let result = rows.reduce((obj, arr) => {
arr.forEach((item, i) => {
if (!obj[dictionary[i]]) {
obj[dictionary[i]] = [];
}
obj[dictionary[i]].push(item);
})
return obj;
}, {});

You need a reducer instead of map. Map just transform each elements that is why you get multiple elements.
I would use plain JS for this one. It will look like this:
const rows = [
[
'stravi/aa',
'202001',
'59',
'51',
'2558.98',
'0.5358894453719162',
'1.9204668112983725',
'140',
'2.3466309084813943',
],
[
'stravi/ab',
'202003',
'3591',
'349',
'2246.09',
'0.41838214',
'3.57603358',
'50',
'4.82115474',
],
[
'stravi/ac',
'202007',
'3354',
'25',
'1975.76',
'0.74220667708',
'1.12321555541',
'11',
'0.9324532454',
],
];
const keys = [
'source',
'sessions',
'adClicks',
'adCost',
'CPC',
'CTR',
'goalCompletionsAll',
'goalConversionRateAll',
'missingFieldName',
];
const initialAcc = keys.reduce((acc, cur) => {
acc[cur] = [];
return acc;
}, {});
const resultAcc = rows.reduce((acc, cur) => {
cur.forEach((value, index) => acc[keys[index]].push(value));
return acc;
}, initialAcc);
console.log(resultAcc);

Related

How to loop inside a map() loop?

The below script outputs
================================================
Log entry ID: 1
UUID: 2
Timestamp: 3
--------------------
Log entry ID: 4
UUID: 5
Timestamp: 6
which is what I want.
Right now description is hard coded, where I would like it to be built using arr instead.
My current thinking were to somehow generate the inner array in the map() function:
[
`Log entry ID: ${element['_id']}`,
`UUID: ${element['_source'].uuid}`,
`Timestamp: ${element['_source']['#timestamp']}\n`,
]
but because of the templating, this is not even a valid array with 3 elements, when looking at it on its own. So I am all out of ideas.
Question
Somehow I have to loop over the elements in arr before it is given to map(), I suppose.
Does anyone know how that could be done?
const dedent = require('dedent');
const arr = [
[ 'Log entry ID', '_id' ],
[ 'UUID', 'uuid' ],
[ 'Timestamp', '#timestamp' ],
]
;
const docs = [
{'_id': 1,'_source': {'uuid': 2,'#timestamp': 3}},
{'_id': 4,'_source': {'uuid': 5,'#timestamp': 6}},
];
const description = dedent`
================================================
${
docs.map((element) => [
`Log entry ID: ${element['_id']}`,
`UUID: ${element['_source'].uuid}`,
`Timestamp: ${element['_source']['#timestamp']}\n`,
].join('\n')).join('--------------------\n')
}
`;
console.log(description);
Update
I control arr so changing it to eg. is possible, or something else
const arr = [
[ 'Log entry ID', '_id' ],
[ 'UUID', {'_source': 'uuid'} ],
[ 'Timestamp', {'_source': '#timestamp'} ],
]
;
Since arr is in your control perhaps you can specify the path of the key itself.
const arr = [
['Log entry ID', '_id'],
['UUID', '_source.uuid'],
['Timestamp', '_source.#timestamp'],
['Description', '_source._desc']
];
const docs = [{
'_id': 1,
'_source': {
'uuid': 2,
'#timestamp': 3,
'_desc': 'test 1'
}
},
{
'_id': 4,
'_source': {
'uuid': 5,
'#timestamp': 6,
'_desc': 'test 2'
}
},
];
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(docs.map((element) => arr.map((label) => {
return `${label[0]}: ${getValue(element, label[1])}`
}).join('\n')).join('\n--------------------\n'))
Assuming that the keys of a docs array element are all unique, one could traverse the object looking for a matching key.
function findVal(object, key) {
var value;
Object.keys(object).some(function(k) {
if (k === key) {
value = object[k];
return true;
}
if (object[k] && typeof object[k] === 'object') {
value = findVal(object[k], key);
return value !== undefined;
}
});
return value;
}
docs.map((element) =>
arr.map(([item, key]) =>
`${item}: ${findVal(element, key)}`)
)
The FindVal is taken from here.
you could create a function to get data from arr and put in description
/* Simple Hello World in Node.js */
const arr = [
[ 'Log entry ID', '_id' ],
[ 'UUID', 'uuid' ],
[ 'Timestamp', '#timestamp' ],
]
;
const docs = [
{'_id': 1,'_source': {'uuid': 2,'#timestamp': 3}},
{'_id': 4,'_source': {'uuid': 5,'#timestamp': 6}},
];
const findInArr=function(value){
var t = ""
arr.forEach(item=>{
if (item.includes(value)) {t = item[0]}
})
return t
}
const description = dedent`
================================================
${
docs.map((element) => [
`${findInArr('_id')}: ${element['_id']}`,
`${findInArr('UUID')}: ${element['_source'].uuid}`,
`${findInArr('Timestamp')}: ${element['_source']['#timestamp']}\n`,
].join('\n')).join('--------------------\n')
}`;
console.log(description);

JavaScript modify Array of Objects and alter contained data

I am having difficulties formatting some data. Currently, I receive data in the following structure.
[
{
"q1":"5",
"q2":[
"13",
"12",
],
"q3":"test",
}
]
I essentially need to modify this or even create a new object, that takes the following structure.
[
{
id: 1, //q1
answers: [
{
answer: '5',
},
],
},
{
id: 2, //q2
answers: [
{
answer: '13',
},
{
answer: '12',
},
],
},
{
id: 3, //q3
answers: [
{
answer: 'test',
},
],
},
];
So the id in the above would be obtained by remove the q and getting the number in the first data object. It would then have an answers array that would have an object for each answer.
I have been attempting this but have gotten lost. I don't know if I should use loops, mapping, filters etc. To be honest, the furthest I have got so far is obtaining the keys
var modified = data.map(function(item) {
return Object.keys(item)
})
I have created a JSFiddle where I have been attempting to do this.
Is there any way I can achieve the data I am after?
Many thanks
Please use map function.
const data = {
"q1":"5",
"q2":[
"13",
"12",
],
"q3":"test",
};
const result = Object.keys(data).map(key => {
let item = {id: key.substring(1), answers: []};
if(typeof data[key] === "string")
item.answers.push({answer: data[key]});
else
item.answers = data[key].map(val => ({answer: val}));
return item;
});
console.log(result)
const inputData = [
{
"q1":"5",
"q2":[
"13",
"12",
],
"q3":"test",
}
]
function answerMapper(objVal, id){
return Array.isArray(objVal)
?
{ id, answers: objVal.map(answer => ({ answer }))}
:
{ id, answers: [{answer: objVal }] }
}
function formatObject(obj){
return Object.keys(obj).map((k, i) => answerMapper(obj[k], i+1));
}
const result = inputData.map(obj => formatObject(obj));
// remove flatMap if your inputData has more than one entry
console.log(result.flatMap(x => x));
map over the first element of the data with Object.entries, grab the key and value, create a new answers array and return a new object.
const data = [{
"q1": "5",
"q2": [
"13",
"12",
],
"q3": "test",
}];
const out = Object.entries(data[0]).map(obj => {
const [ key, value ] = obj;
const id = Number(key[1]);
// If the the value is an array
// return a new array of mapped data
// Otherwise return an array containing
// one object
const answers = Array.isArray(value)
? value.map(el => ({ answer: el }))
: [{ answer: value }];
// Return the new object
return { id, answers };
});
console.log(out);
lets create a pure function which accepts the object in the array like so
const processObject = obj => Object.keys(obj).map(id => {
const answer = obj[id];
const answers = Array.isArray(answer) ? answer : [answer]
const answerObjectArray = answers.map(ans => ({
answer: ans
}));
return {
id: +id.substring(1),
answers: answerObjectArray
}
});
const dataArray = [{
"q1": "5",
"q2": [
"13",
"12",
],
"q3": "test",
}];
const output = processObject(dataArray[0]);
console.log(output);

How to map a list of objects into an array of arrays

I have an array of objects that I need to reformat into a list of arrays in a specific format.
I need my list to be formatted like this
list: [
[ "B", "A" ],
[ "F", "E" ],
]
But the closest I have come is this
list: ["B A", "F E"]
using this code
const itemList = [
{"ProductName":"A",
"Sku":"B",},
{"ProductName":"E",
"Sku":"F",}
];
const newList = itemList.map(item => `${item.Sku} ${item.ProductName}`);
console.log(newList);
How would I map this correctly?
You can create array with the values inside map:
const itemList = [
{"ProductName":"A",
"Sku":"B",},
{"ProductName":"E",
"Sku":"F",}
];
const newList = itemList.map(item => [item.Sku, item.ProductName]);
console.log(newList);
You can also use destucure for each item and map it to array of these values:
const itemList = [
{
ProductName: 'A',
Sku: 'B'
},
{
ProductName: 'E',
Sku: 'F'
}
];
const newList = itemList.map(({ProductName, Sku}) => [
Sku,
ProductName
]);
console.log(newList);
To keep things simple, I would use Object.values as such:
const newList = [];
itemList.map(item => newList.push(Object.values(item)));

Concat all values inside a map

I've got a constant result like this :
result: Map<string, string[]>
When I do a console.log(result) the output is :
Map {
'toto' => [ 'a-1', 'a-2' ],
'tata' => [ 'b-1', 'b-2' ],
'titi' => [ 'c-1', 'c-2' ],
}
What I want to have, it's a constant globalResult with all values like this:
const globalResult = [ 'a-1', 'a-2','b-1','b-2','c-1','c-2' ]
How can I do this ?
Thanks
You can get map values into an array and then use flat() method on it like:
const myMap = new Map().set('toto', ['a-1', 'a-2']).set('tata', ['b-1', 'b-2'])
const arr = [...myMap.values()].flat()
console.log(arr)
You can use Array.from to covert map values into a flat array
const map = new Map();
map.set('a',11)
map.set('b',22)
map.set('c',33)
const array = Array.from(map.values())
console.log(array)
You could get the values of the properties and flat the arrays.
const
object = { toto: ['a-1', 'a-2'], tata: ['b-1', 'b-2'], titi: ['c-1', 'c-2'] },
array = Object.values(object).flat();
console.log(array);
Use forEach function.
const obj = {
'toto' : [ 'a-1', 'a-2' ],
'tata' : [ 'b-1', 'b-2' ],
'titi' : [ 'c-1', 'c-2' ],
}
const arr = [];
Object.values(obj).forEach(value=>arr.push(...value));
console.log(arr);

Lift a key’s values to top level whilst preserving rest of object using Ramda

I would like to build it into my compose function so that record’s values go to the top level of the object, whilst the rest of the keys stay in tact:
{
record: {
seasons: [
1
],
colors: [
2
]
},
tag_ids: [
2091
]
}
The result I am after:
{
seasons: [
1
],
colors: [
2
],
tag_ids: [
2091
]
}
Any of the keys may or may not exist.
I have always scratched my head with the ramda way to do it in a compose function. Currently I am looking at toPairs and doing some pretty long winded transforms with no luck.
This may be simpler in plain JS rather than Ramda:
const data = { record: { seasons: [1], colors: [2] }, tag_ids: [2091] }
const flattenRecord = ({record = {}, ...rest}) => ({...record, ...rest})
flattenRecord(data) //=> {"colors": [2], "seasons": [1], "tag_ids": [2091]}
If you would still like to utilise Ramda for the solution, consider looking into R.mergeLeft (or R.mergeRight) and R.omit.
You can use R.chain with R.merge and R.prop to flatten a key's content by merging it with the original object, and then you can omit the original key.
const { pipe, chain, merge, prop, omit } = R
const fn = key => pipe(
chain(merge, prop(key)), // flatten the key's content
omit([key]) // remove the key
)
const data = { record: { seasons: [1], colors: [2] }, tag_ids: [2091] }
const result = fn('record')(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
You can use the spread operator.
const startingWithAllProps = {
record: {
seasons: [
1
],
colors: [
2
]
},
tag_ids: [
2091
]
}
const startingWithoutRecord = {
tag_ids: [
2091
]
}
const startingWithoutTagIds = {
record: {
seasons: [
1
],
colors: [
2
]
}
}
const moveRecordUpOneLevel = (startingObject) => {
const temp = {
...startingObject.record,
tag_ids: startingObject.tag_ids
}
return JSON.parse(JSON.stringify(temp)) // To remove any undefined props
}
const afterTransformWithAllProps = moveRecordUpOneLevel(startingWithAllProps)
const afterTransformWithoutRecord = moveRecordUpOneLevel(startingWithoutRecord)
const afterTransformWithoutTagIds = moveRecordUpOneLevel(startingWithoutTagIds)
console.log('afterTransformWithAllProps', afterTransformWithAllProps)
console.log('afterTransformWithoutRecord', afterTransformWithoutRecord)
console.log('afterTransformWithoutTagIds', afterTransformWithoutTagIds)
This might help too!
const lift = key => R.converge(R.mergeRight, [
R.dissoc(key),
R.prop(key),
]);
const liftRecord = lift('record');
// ====
const data = {
record: {
seasons: [
1
],
colors: [
2
]
},
tag_ids: [
2091
]
};
console.log(
liftRecord(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js" integrity="sha256-buL0byPvI/XRDFscnSc/e0q+sLA65O9y+rbF+0O/4FE=" crossorigin="anonymous"></script>

Categories

Resources