Whats the most elegant way to transform this JSON structure? [closed] - javascript

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
How to transform the following data with few lines of code?
From:
[
{filter:true, type:"one", name:"left"},
{filter:true, type:"two", name:"up"},
{filter:true, type:"one", name:"right"},
{filter:false, type:"three", name:"down"}
]
Into:
[
["left","right"],
["up"]
]
The transformation do the following:
keep items where filter is true
group items by type
map items on their name value
Performance does not matter, only cognitive complexity does.
Today, I use lodash filter/group then a double loop to map items in the sub-arrays.
I am curious to see it exists a way to do the same job without a double loop.

.filter(), destructure for filter and check for True
.reduce(), to create our Object; using type for the Key, and build an Array of name as the Value
Object.values() to return only the Values of that Object
const data = [
{filter:true, type:"one", name:"left"},
{filter:true, type:"two", name:"up"},
{filter:true, type:"one", name:"right"},
{filter:false, type:"three", name:"down"}
];
const newArray = Object.values(data
.filter(({filter}) => filter)
.reduce((acc, {type, name}) => {
acc[type] = acc[type] || [];
acc[type].push(name);
return acc;
}, {})
);
console.log(newArray);

You can use reduce to iterate the array, and group the names in an object by their type, and then convert the object back to an array with Object.values():
const arr = [{"filter":true,"type":"one","name":"left"},{"filter":true,"type":"two","name":"up"},{"filter":true,"type":"one","name":"right"},{"filter":false,"type":"three","name":"down"}]
const result = Object.values(
arr.reduce((r, o) => {
if(!o.filter) return r // ignore item if filter is false
if(!r[o.type]) r[o.type] = [] // init a property in the accumulator by the type, if none exists
r[o.type].push(o.name) // push the current name to the type property in the accumulator
return r
}, {})
)
console.log(result)
Another approach would be to filter out items with filter: false, and then reduce to a Map. Afterwards you can convert the Map back to an object by using Array.from() on the Map's .values() iterator:
const arr = [{"filter":true,"type":"one","name":"left"},{"filter":true,"type":"two","name":"up"},{"filter":true,"type":"one","name":"right"},{"filter":false,"type":"three","name":"down"}]
const result = Array.from(arr
.filter(o => o.filter) // remove items with filter: false
.reduce((r, o) =>
r.set(o.type, [...(r.get(o.type) || []), o.name]) // reduce to a Map
, new Map()
).values()) // get an array from the Map's values
console.log(result)

Sometimes simple is cleaner. Create an object where type is the key and then grab the values:
var arr = [{filter:true, type:"one", name:"left"}, {filter:true, type:"two", name:"up"}, {filter:true, type:"one", name:"right"}, {filter:false, type:"three", name:"down"}];
var obj = {};
arr.forEach((item) => {
if(item.filter){
obj[item.type] = obj[item.type] || [];
obj[item.type].push(item.name);
}
});
console.log(Object.values(obj));
Or, if you're willing to sacrifice readability for shortness:
var arr = [{filter:true, type:"one", name:"left"}, {filter:true, type:"two", name:"up"}, {filter:true, type:"one", name:"right"}, {filter:false, type:"three", name:"down"}];
var obj = {};
arr.forEach((item) => {if(item.filter) obj[item.type] ? obj[item.type].push(item.name) : obj[item.type] = [item.name];});
console.log(Object.values(obj));

You can do something like this
const arr = [
{ filter: true, type: "one", name: "left" },
{ filter: true, type: "two", name: "up" },
{ filter: true, type: "one", name: "right" },
{ filter: false, type: "three", name: "down" }
];
const groupItems = arr => {
const dict = {};
arr.forEach(item => {
const {type, name, filter} = item;
if (filter) {
dict[type] ? dict[type].push(name) : dict[type] = [name];
}
});
return Object.values(dict);
}
console.log(groupItems(arr));

An approach with a Map.
var data = [{ filter: true, type: "one", name: "left" }, { filter: true, type: "two", name: "up" }, { filter: true, type: "one", name: "right" }, { filter: false, type: "three", name: "down" }],
result = Array.from(data
.reduce(
(m, { filter, type, name }) => filter
? m.set(type, [...(m.get(type) || []), name])
: m,
new Map
)
.values()
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

const arr = [
{filter:true, type:"one", name:"left"},
{filter:true, type:"two", name:"up"},
{filter:true, type:"one", name:"right"},
{filter:false, type:"three", name:"down"}
]
const output = arr
.filter(item => item.filter)
.reduce((acc, curr) => {
const existing_item = acc.find(item => item.type === curr.type);
if (!existing_item) {
acc.push({ ...curr, names: [curr.name] });
} else {
existing_item.names.push(curr.name);
}
return acc;
}, [])
.map(obj => obj.names);
console.log({output});
// Alternate way
const output2 = Object.values(
arr
.filter(item => item.filter)
.reduce(
(acc, curr) => ({
...acc,
[curr.type]:
curr.type in acc ? [...acc[curr.type], curr.name] : [curr.name]
}),
{}
)
);
console.log({ output2 });

Related

JavaScript / ES6 -- Replace some keys in an array of objects, given key map

I have some (potentially very large) array of objects like this:
[
{
'before1' => val,
'same' => val,
'before2' => val
},
...
]
I need an efficient way to replace only some of the keys in the map (i.e. deleting keys won't work for me), and I have a map like this:
keyReplacements = {
'before1' => 'after1',
'same' => 'same',
'before2' => 'after2'
}
I know the same => same is not necessary in the map, but it's helpful to include as a full translation schema.
Given this key mapping, what's an efficient method to replace my given array of objects with the following result?
[
{
'after1' => val,
'same' => val,
'after2' => val
},
...
]
I've tried the following:
static replaceObjectKeys(objectToReplace, keyMap) {
objectToReplace.map(o =>
Object.keys(o).map((key) => ({ [keyMap[key] || key]: o[key] })
).reduce((objectToReplace, b) => Object.assign({}, objectToReplace, b)))
return objectToReplace
}
But it just returns me the same object with nothing replaced
const newObject = this.replaceObjectKeys(oldObject, keyMap)
console.log("new obj: ", newObject) // this is the same as oldObject
return newObject
Here's a solution using entries:
const arr = [
{
'before1': 1,
'same': 2,
'before2': 3
}, {
'before1': 4,
'same': 5,
}, {
'before1': 6,
'before2': 7
}, {
'same': 8,
'before2': 9
},
];
const keyReplacements = {
'before1': 'after1',
'same': 'same', // this is not necessary
'before2': 'after2'
};
const newArr = arr.map(obj =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [keyReplacements[k] || k, v]))
);
console.log(newArr);
Use ES6 map()
arrayObj = arrayObj.map(item => {
return {
value: item.key1,
key2: item.key2
};
});

How to convert object key value pair with array as values to multi objects of key value pair?

I have an object with key-value pair and its value as an array of elements.
{
status: ["new", "old"],
place: ["york", "blah"]
}
I'm trying to convert it into multiple array objects of key-value pair like below.
{
"newObj1": [
{ "status": "new" },
{ "status": "old" }],
"newObj2": [
{ "place": "york" },
{ "place": "blah" }]
}
Is there any way to achieve the above structure? I have tried couple of methods using array reduce methods but it doesn't give in the desired output.
let value= {
status: ["new", "old"],
place: ["york", "blah"]
}
Object.keys(value).map((key) => [key, value[key]]);
You can do something like this
const obj = {
status: ["new", "old"],
place: ["york", "blah"]
};
const result = {};
Object.keys(obj).forEach((key, index) => {
result[`newObj${index + 1}`] = obj[key].map(item => ({[key]: item}));
});
console.log(result);
Here's a solution that uses Array.reduce():
const value = {
status: ["new", "old"],
place: ["york", "blah"]
};
const result = Object.keys(value).reduce((acc, key, i) => {
acc["newObj" + (i + 1)] = value[key].map(k => ({ [key]: k }));
return acc;
}, {});
console.log(result);
Here is my way of accomplishing that.
let source = {
status: ["new", "old"],
place: ["york", "blah"]
};
let destination = {}; // make room for the destinoation object
Object.keys(source).forEach((key, index) => {
let obj = "newObj" + (index + 1); // assume all objects are named "newObj1,2,3,etc"
if (!destination[obj]) { // check if the object exists already
// if not, then crate an empty array first
destination[obj] = [];
}
// loop through all items in the source element array
source[key].forEach(value => {
// create an object from the array element
let subObj = {};
subObj[key] = value;
// push that object to the destination
destination[obj].push(subObj);
});
});
console.log(destination);
const data = {
status: ["new", "old"],
place: ["york", "blah"]
};
let result = Object.fromEntries( Object.entries(data).map( ([key, [first, second]], index) => {
return [ `newObj${index}`, [ { [key]: first }, { [key]: second } ] ];
} ) );
console.log(result);
Here's an idiomatic solution using .reduce inside .reduce:
Object.entries(data)
.reduce((result, [key, value], index) => !(result['newObj' + (index + 1)] = value
.reduce((arr, text) => !arr
.push({ [key]: text }) || arr, [])) || result, {});
Here's a live example:
const data = {
status: ['new', 'old'],
place: ['york', 'blah']
};
const result = Object.entries(data)
.reduce((result, [key, value], index) => !(result['newObj' + (index + 1)] = value
.reduce((arr, text) => !arr
.push({ [key]: text }) || arr, [])) || result, {});
console.log(result);
/*
{
newObj1: [
{ status: 'new' },
{ status: 'old' }
],
newObj2: [
{ place: 'york' },
{ place: 'blah' }
]
}
*/
For those who fail to understand map and reduce, here's a fairly naive solution but it will work:
newObjCounter = 1
orig = { status: [ 'new', 'old' ], place: [ 'york', 'blah' ] }
newObject = {}
//Initialise object with new keys with arrays as values
for(var key in orig){
newObject["newObj"+initialCounter] = []
initialCounter++
}
//Loop through keys of the original object and dynamically populate the new object
for(var key in orig){
index = "newObj"+objCounter
newObject[index].push({[key]:orig[key]})
objCounter++
}
console.log(newObject)

How to consolidate an array of multiple objects into single object?

So, I have an array like this:
[
{ tags__region: "Stockholm" },
{ tags__region: "Lund" },
{ tags__region: "Mora" },
{ tags__user: "Johan" },
{ tags__user: "Eva" }
]
and I want to turn that into an object like this:
{
tags__region: ["Stockholm", "Lund", "Mora"],
tags__user: ["Johan", "Eva"]
}
Is there a way with lodash?
Are vanilla Array/Object -methods simple enough?
Keep in mind the keys on my array are unknown, so they are not always the same.
Simple Javascript.
let arr = [{
tags__region: "Stockholm"
},
{
tags__region: "Lund"
},
{
tags__region: "Mora"
},
{
tags__user: "Johan"
},
{
tags__user: "Eva"
}
];
arr = arr.reduce((acc, val) => {
let key = Object.keys(val)[0];
let value = Object.values(val)[0];
acc[key] = acc[key] ? [...acc[key],value] : [value]
return acc;
}, {})
console.log(arr);
You can use Lodash's _.mergeWith() with array spread to combine all items in the array to a single object. If the same property exists in two object, the values will be collected to an array:
const arr = [{"tags__region":"Stockholm"},{"tags__region":"Lund"},{"tags__region":"Mora"},{"tags__user":"Johan"},{"tags__user":"Eva"}]
const result = _.mergeWith({}, ...arr, (objValue = [], srcValue) =>
[...objValue, srcValue]
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
With Lodash/fp you can generate a function (fn) using _.mergeAllWith(), and _.concat() that will do the same thing:
const fn = _.mergeAllWith(_.concat)
const arr = [{"tags__region":"Stockholm"},{"tags__region":"Lund"},{"tags__region":"Mora"},{"tags__user":"Johan"},{"tags__user":"Eva"}]
const result = fn(arr)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>

Javascript concat and prefix or rename props

I have two arrays that I am concatinating.
However each of these arrays has same property name I want to leave by adding prefix to each.
Array A(aData) looks like
[
{
id: 1,
title: `title`
code: '34x'
},
...
]
Array B(bData) looke like:
[
{
id: 1
prop: 3,
otherporp: `prop`
code: 'hi67'
},
...
]
In order to combine the arrays I am doing concat and reduce to get only matching id's
const data: any = aData.concat(bData).reduce((acc, x) => {
acc[x.id] = Object.assign(acc[x.id] || {}, x);
return acc;
}, {});
return Object.values(data);
But the issue is that my bData code props getting lost.
Is there any way I can rename the code from aData to say aCode and the code from bData to bCode ?
You can create a new array from both of your array with updated key value aCode and bCode instead of code key. Then concat both of these arrays and merge them on the id key.
const arrA = [{ id: 1, title: `title`, code: '34x' }],
arrB = [{ id: 1, prop: 3, otherporp: `prop`, code: 'hi67'}];
const newArrA = arrA.map(({code, ...rest}) => ({...rest, aCode : code}));
const newArrB = arrB.map(({code, ...rest}) => ({...rest, bCode : code}))
const merged = Object.values([].concat(newArrA, newArrB).reduce((r,o) => {
r[o.id] = r[o.id] || Object.assign({},o);
Object.assign(r[o.id], o);
return r;
}, {}));
console.log(merged);
var arrA = [{
id: 1,
title: `title`,
code: '34x'
}],
arrB = [{
id: 1,
prop: 3,
otherporp: `prop`,
code: 'hi67'
}];
let newArrA = arrA.map(({
code,
...rest
}) => ({ ...rest,
aCode: code
}));
const newArrB = arrB.map(({
code,
...rest
}) => ({ ...rest,
bCode: code
}));
result = newArrA.map(function(v) {
var ret;
$.each(newArrB, function(k, v2) {
if (v2.id === v.id) {
ret = $.extend({}, v2, v);
return false;
}
});
return ret;
});
console.log(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How to create nested arrays based on arrays of strings in javascript?

I am trying to create nested arrays with array of strings.
Each string object on the array is delimited by a '|' and that char its uses to create a nested array over an already existing array.
edit fix IE: current array
var arr = [
{ val : 'root|leaf|lead2|boo|foo|lee'},
{ val : 'root|leaf|lead3|boo|foo|lee'},
{ val : 'root|leaf2|boo'},
{ val : 'root|leaf2|foo'},
{ val : 'root|leaf2|leaf3|more'},
{ val : 'root|leaf2|leaf3|things'},
{ val : 'root|leaf2|leaf3|here'},
{ val : 'sibling|leaf|leaf2|boo'},
{ val : 'sibling|leaf|leaf2|foo'},
{ val : 'sibling|leaf|leaf2|lee'},
{ val : 'sibling|boo'},
{ val : 'sibling|foo'},
{ val : 'sibling|boo|leaf3'},
{ val : 'sibling|boo|leaf3|more'},
{ val : 'sibling|boo|leaf3|things'},
{ val : 'sibling|boo|leaf3|here'},
{ val : 'sibling|ops'},
];
var nested = [
root = [
leaf = [
leaf2 = [
'boo', 'foo', 'lee'
],
leaf3 = [
'boo', 'foo', 'lee'
]
],
leaf2 = [
'boo', 'foo', leaf3 = [
'more', 'things', 'here'
]
]
],
sibling = [
leaf = [
leaf = [
leaf2 = [
'boo', 'foo', 'lee'
]
]
],
'ops',
'boo', 'foo', leaf3 = [
'more', 'things', 'here'
]
]
];
You can find here a functional approach, by using .map() and .reduce() methods. The idea is to parse the path by splitting over the | character, and then build the object on the fly.
const arr = [
{cat : 'one|two|thre|boo'},
{cat : 'one|two|boo|boo|ouch'},
{cat : 'one|two|thre|boo|lee'},
{cat : 'one|hey|something|other'},
{cat : 'one|hey|keys|other'},
{cat : 'this|blaj|something|other'},
];
function create(array) {
const parse = elm => elm.cat.split('|');
const build = (keys, obj, acc) => {
keys.reduce((a, b) => {
if (!a[b]) a[b] = {};
return a[b];
}, obj);
Object.assign(acc, obj);
return acc;
};
const obj = {};
return array
.map(a => parse(a))
.reduce((acc, keys) => build(keys, obj, {}), {});
}
console.log(create(arr))
You can find the Working plunkr

Categories

Resources