lodash convert array of objects to single array of keys and multiple array of values - javascript

I need to transmit some data, that has too many key-value pairs.
As the keys are similar, I dont want to transmit them with each object.
Consider I have the following data:
[
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
];
And I need the final output as
[ [x,y],[[11,12],[21,22],[31,32],[41,42]] ] OR
[ [x,y],[11,12],[21,22],[31,32],[41,42] ]
On the other end, I should be able to convert back to its original form.
It would be great if it can handle an additional key in some of the objects
I think I have seen lodash or underscore function for something close/similar to this, but I'm not able to find it right now.
NOTE: I don't know what the keys will be

Lodash v4.17.1
modify original
var modifiedOriginal = _.chain(original)
.map(_.keys)
.flatten()
.uniq()
.thru(function(header){
return _.concat(
[header],
_.map(original, function(item) {
return _.chain(item)
.defaults(_.zipObject(
header,
_.times(_.size(header), _.constant(undefined))
))
.pick(header)
.values()
.value()
})
);
})
.value();
modified back to original (keys order is not
guarantee)
var backToOriginal = _.map(_.tail(modified), function(item) {
return _.chain(_.head(modified))
.zipObject(item)
.transform(function(result, val, key) {
if (!_.isUndefined(val)) {
result[key] = val;
}
})
.value();
});
JSFiddle code https://jsfiddle.net/wa8kaL5g/1/

Using Array#reduce
var arr = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
var keys = Object.keys(arr[0]);
var op = arr.reduce(function(a, b) {
var arr = keys.reduce(function(x, y) {
return x.concat([b[y]]);
}, [])
return a.concat([arr]);
}, [keys]); //If all the objects are having identical keys!
console.log(JSON.stringify(op));

A little more verbose way of doing it:
[Edit: added the function to convert it back]
function convert(arr) {
var retArr = [ [/* keys (retArr[0]) */], [/* values (retArr[1]) */] ]
arr.forEach(function(obj){
// create new array for new sets of values
retArr[1].push([])
// put all of the keys in the correct array
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// does the key exist in the array yet?
if (retArr[0].indexOf(key) === -1) {
retArr[0].push(key)
}
// get last index of retArr[1] and push on the values
retArr[1][retArr[1].length - 1].push(obj[key])
}
}
})
return retArr
}
function reConvert(arr) {
var retArr = []
var keys = arr[0]
arr[1].forEach(function(itemArr){
var obj = {}
itemArr.forEach(function(item, i){
obj[keys[i]] = item
})
retArr.push(obj)
})
return retArr
}
var objArr = [
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
]
var arrFromObj = convert(objArr)
var objFromArr = reConvert(arrFromObj)
console.log(arrFromObj)
console.log(objFromArr)

A solution using Underscore.
First work out what the keys are:
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
Then map across the data to pick out the value for each key:
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
and back again:
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
Lodash's version of object is zipObject and omit using a predicate is omitBy:
var thereAndBackAgain = _.map(result[1], item => _.omitBy(_.zipObject(result[0], item), _.isUndefined));
var data = [
{
x:11,
y:12,
aa: 9
},{
x:21,
y:22
},{
x:31,
y:32,
z: 0
},{
x:41,
y:42
}
];
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
console.log(result)
console.log(thereAndBackAgain)
<script src="
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

In ES6 you can do it by reducing it with Object.values(), and Object.keys(). You can restore it using a combination of Array.prototype.map() and Array.prototype.reduce():
const convertStructure = (data) => data.reduce((s, item) => {
s[1].push(Object.values(item));
return s;
}, [Object.keys(data[0]), []]); // all objects should be the same, so we can take the keys from the 1st object
const restoreStructure = ([keys, data]) => data.map((item) => item.reduce((o, v, i) => {
o[keys[i]] = v;
return o;
}, {}));
const data = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
const convertedStructure = convertStructure(data);
console.log('convertedStructure:\n', convertedStructure);
const restoredStructure = restoreStructure(convertedStructure);
console.log('restoredStructure:\n', restoredStructure);

Related

Rebuilding/Parsing plain JavaScript object

Let's say I have an object containing objects that have 30 key-value pairs each:
const data = {
"foo": {
"3/16/21": 'buzz',
"3/17/21": 'fizz',
...
"4/13/21": 'lorem',
"4/14/21": 'ipsum'
},
"bar": {
"3/16/21": 'sit',
"3/17/21": 'amet',
...
"4/13/21": 'dummy',
"4/14/21": 'text'
},
};
My goal is to rebuild this object into something like this:
myData = [
{date: "3/16/21", foo: 'buzz', bar : 'sit'}
{date: "3/17/21", foo: 'fizz', bar : 'amet'} ,
...
{date: "4/13/21", foo: 'lorem', bar : 'dummy'}
{date: "4/14/21", foo: 'ipsum', bar : 'text'}
];
The function below works like charm but I feel like there is a 10x better way to do it. I would love to see your suggestions on how I could improve it.
const processAPIdata = (data) => {
if (data) {
var myData = [];
for (var key in data) {
if (!data.hasOwnProperty(key)) continue;
var obj = data[key];
for (var prop in obj) {
if (!obj.hasOwnProperty(prop)) continue;
if (myData.length < 30) {
myData.push({ date: prop });
}
let pos = myData.map(function (e) { return e.date; }).indexOf(prop);
myData[pos][key] = obj[prop];
}
}
}
return myData;
};
I'd group into an object indexed by date. When iterating, create the object for that date if it doesn't exist yet, with { date } (where date is the inner property being iterated over), and assign a new property from the outer key (for the new key) and the inner value (for the new value):
const data = {
"foo": {
"3/16/21": 'buzz',
"3/17/21": 'fizz',
"4/13/21": 'lorem',
"4/14/21": 'ipsum'
},
"bar": {
"3/16/21": 'sit',
"3/17/21": 'amet',
"4/13/21": 'dummy',
"4/14/21": 'text'
},
};
const newDataByDate = {};
for (const [key, obj] of Object.entries(data)) {
for (const [date, val] of Object.entries(obj)) {
newDataByDate[date] ??= { date };
newDataByDate[date][key] = val;
}
}
console.log(Object.values(newDataByDate));
You can complete by doing this simple way.
const data = {"foo":{"3/16/21":'buzz',"3/17/21":'fizz',"4/13/21":'lorem',"4/14/21":'ipsum'},"bar":{"3/16/21":'sit',"3/17/21":'amet',"4/13/21":'dummy',"4/14/21":'text'},};
const result = Object.entries(data).reduce((acc, [key, values]) => {
for(const [date, v] of Object.entries(values)){
acc[date] = acc[date] || {date}
acc[date][[key]] = v;
}
return acc;
}, {});
console.log(Object.values(result));
We can achieve this using Object.entries, Array.reduce & Object.values like below
const data = {"foo":{"3/16/21":'buzz',"3/17/21":'fizz',"4/13/21":'lorem',"4/14/21":'ipsum'},"bar":{"3/16/21":'sit',"3/17/21":'amet',"4/13/21":'dummy',"4/14/21":'text'}};
const formatData = (data) => {
//Convert the object to array of arrays with value at first index being the keys and value at second index being values
const result = Object.entries(data).reduce((acc, [key, val]) => {
//Since we have object as value, we need to again convert to array of arrays in order to get the date and the corresponding value
Object.entries(val).forEach(([date, innerVal]) => {
//update the accumulator with new key-value
acc[date] = {
...(acc[date] || {date}),
[key]: innerVal
}
})
return acc;
}, {});
//Return the values of the accumulator
return Object.values(result);
}
console.log(formatData(data));
.as-console-wrapper {
max-height: 100% !important;
}

Data transformation horizontal to vertical javascript

I need help in transforming data in a particular way to plot a graph. The data which I get from API is a different format. Please guide me on how to transform it
const demo = [
{
label: 'ABC',
vMini: 28,
vMaxi: 56,
dMini: 2,
dMaxi: 50,
},
{
label: 'BCD',
vMini: 2,
vMaxi: 56,
dMini: 3,
dMaxi: 50,
},
];
end result which i want is
[
{
section: "vMini",
"ABC": 28,
"BCD": 2,
},
{
section: "vMaxi",
"ABC": 56,
"BCD": 56
}
{
section: "dMini",
"ABC": 2,
"BCD": 3,
},
{
section: "dMaxi",
"ABC": 50,
"BCD": 50
}
]
I have started working on it and got confused with second loop.
for (let i = 0; i < demo.length; i += 1) {
for (let j in demo[i]) {
if (j === 'label') {
}
}
}
This one is a bit tricky with the way the data is structured, but you should be able to do this with array.reduce, like so:
const demo = [{label:"ABC",vMini:28,vMaxi:56,dMini:2,dMaxi:50},{label:"BCD",vMini:2,vMaxi:56,dMini:3,dMaxi:50}];
// get array of keys, and create a new object for each one except label
// ["label", "vMini", "vMaxi", "dMini", "dMaxi"]
let results = Object.keys(demo[0]).reduce((res, key) => {
if (key === "label") { return res; }
else {
// for each item in demo, create a key for the label and grab the key's value
let newObj = demo.reduce((_res, obj) => {
_res[obj.label] = obj[key];
return _res;
}, {section: key})
// push the new object into the results array
res.push(newObj);
}
return res;
}, [])
console.log(results);
Using reduce() and Map()
const demo = [{label:"ABC",vMini:28,vMaxi:56,dMini:2,dMaxi:50},{label:"BCD",vMini:2,vMaxi:56,dMini:3,dMaxi:50}];
const resMap = demo.reduce((a, v) => {
let label = v.label
for (let k in v) {
if (k == 'label') continue
a.has(k) || a.set(k, { section: k })
let o = a.get(k)
o[label] = v[k]
}
return a
}, new Map())
const resArr = [...resMap.values()]
console.log(resArr)

Combine two objects in an array into a single object if the object properties are the same and convert the unique properties into an array

Input -
[
{color:'red', shape:'circle', size:'medium'},
{color:'red', shape:'circle', size:'small'}
]
Output -
[
{color:'red', shape:'circle', size:['medium','small']}
]
How can this be achieved in Javascript?
If you just want to group based on color and shape, you could use reduce. Create an accumulator object with each unique combination of those 2 properties separated by a | as key. And the object you want in the output as their value. Then use Object.values() to get those objects as an array.
const input = [
{color:'red', shape:'circle', size :'medium'},
{color:'red', shape:'circle', size:'small'},
{color:'blue', shape:'square', size:'small'}
];
const merged = input.reduce((acc, { color, shape, size }) => {
const key = color + "|" + shape;
acc[key] = acc[key] || { color, shape, size: [] };
acc[key].size.push(size);
return acc
}, {})
console.log(Object.values(merged))
This is what the merged/accumulator looks like:
{
"red|circle": {
"color": "red",
"shape": "circle",
"size": [
"medium",
"small"
]
},
"blue|square": {
"color": "blue",
"shape": "square",
"size": [
"small"
]
}
}
You can make it dynamic by creating an array of keys you'd want to group by:
const input = [
{ color: 'red', shape: 'circle', size: 'medium' },
{ color: 'red', shape: 'circle', size: 'small' },
{ color: 'blue', shape: 'square', size: 'small' }
];
const groupKeys = ['color', 'shape'];
const merged = input.reduce((acc, o) => {
const key = groupKeys.map(k => o[k]).join("|");
if (!acc[key]) {
acc[key] = groupKeys.reduce((r, k) => ({ ...r, [k]: o[k] }), {});
acc[key].size = []
}
acc[key].size.push(o.size)
return acc
}, {})
console.log(Object.values(merged))
You can create a general function which takes an array of objects and array of keys to match as its parameters.
You can do that in following steps:
First use reduce() on the array of objects and set accumulator to empty array []
Get the other props(unique props) by using filter() on Object.keys()
Then in each iteration find the element of the accumulator array whose all the given props matches with the current object.
If the element is found then use forEach() other keys and push the values to to corresponding array.
If element is not found then set each key to an empty array.
At last return the result of reduce()
const arr = [
{color:'red', shape:'circle', size:'medium'},
{color:'red', shape:'circle', size:'small'}
]
function groupByProps(arr,props){
const res = arr.reduce((ac,a) => {
let ind = ac.findIndex(b => props.every(k => a[k] === b[k]));
let others = Object.keys(a).filter(x => !props.includes(x));
if(ind === -1){
ac.push({...a});
others.forEach(x => ac[ac.length - 1][x] = []);
ind = ac.length - 1
}
others.forEach(x => ac[ind][x].push(a[x]));
return ac;
},[])
return res;
}
const res = groupByProps(arr,['color','shape'])
console.log(res)
Here is a simple groupBy function which accepts an array of objects and array of props to group on:
let data = [
{color:'red', shape:'circle', size:'medium'},
{color:'red', shape:'circle', size:'small'}
]
let groupBy = (arr, props) => Object.values(arr.reduce((r,c) => {
let key = props.reduce((a,k) => `${a}${c[k]}`, '')
let otherKeys = Object.keys(c).filter(k => !props.includes(k))
r[key] = r[key] || {...c, ...otherKeys.reduce((a,k) => (a[k] = [], a),{})}
otherKeys.forEach(k => r[key][k].push(c[k]))
return r
}, {}))
console.log(groupBy(data, ['color','shape']))
The idea is to use Array.reduce and basically create a string key of the passed in props. For the other fields create an array and keep pushing values there on each iteration.

How to flatten array in JS?

I have a data that is like following:
const data = [{
ratings: [ { rating: 5 } ],
counts: [ { count: 100 } ],
}];
And I want to flatten it in a sense that I want to get rid of arrays and have only objects, and end result to be:
const data = {
ratings: { rating: 5 },
counts: { count: 100 },
};
I tried to do something like this, but it is wrong and I believe I'm kind of over complicating it.
const flatten = data => {
return data.reduce((r, { ...children }) => {
Object.assign(children, r);
if (children) Object.assign(flatten(...Object.values(children)), r);
return r;
}, {})
}
Any ideas?
You could create recursive function with reduce method to turn all arrays to objects assuming you have just objects in those arrays.
const data = [{ratings: [ { rating: 5 } ],counts: [ { count: 100 } ]}];
function flatten(arr) {
return arr.reduce((r, e) => {
const obj = Object.assign({}, e);
for (let p in obj) {
if (Array.isArray(obj[p])) {
obj[p] = flatten(obj[p])
}
}
return Object.assign(r, obj)
}, {})
}
console.log(flatten(data))
If by any chance the data is result from JSON.parse :
var json = JSON.stringify( [{ratings:[{rating: 5}], counts:[{count: 100}]}] )
var result = JSON.parse(json, (k, v) => v[0] || v)
console.log( result )
Please check:
var data = [{ratings: [ { rating: 5 } ], counts: [ { count: 100 } ]}];
var flatten = function(data) {
if (Array.isArray(data)) {
data = data[0];
for (var key in data) data[key] = flatten(data[key]);
}
return data;
}
console.log(flatten(data));
Please check # CodePen
https://codepen.io/animatedcreativity/pen/842e17d2b9f83bc415513f937fc29be8

Get Maximum value from JSON object using javascript?

I have JSON like
var JObject = [
{
a:"A1",
b:100,
c:800
},
{
a:"B1",
b:300,
c:400
}
];
I need maximum value from this JSON...it has to return 800 if it return key and column index
Since this is tagged in d3.
I will give a d3 answer.
Working code below
var kary = [
{
a:"A1",
b:100,
c:800
},
{
a:"B1",
b:1300,
c:400
},
{
a:"D1",
b:300,
c:400
}
];
var max = d3.max(kary, function(d){ return d3.max(d3.values(d).filter(function(d1){ return !isNaN(d1)}))});
console.log(max)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps!
You can do something like this
var JObject = [{
a: "A1",
b: 100,
c: 800
}, {
a: "B1",
b: 300,
c: 400
}];
var res = Math.max.apply(null,JObject.map(function(v) {
// iterate over array element and getting max value from result array
var r = [];
for (var val in v) {
// iterate over object inside array
if (v.hasOwnProperty(val)) {
var num = parseInt(v[val], 10);
// parsing the value for integer output
r.push(isNaN(num) ? 0 : num);
// pushing value to array, in case of `Nan` pushing it as 0
}
}
return Math.max.apply(null,r);
// getting max value from object values
}));
console.log(res);
You could make this more or less generic - and probably shorten it into a single reduce statement.
var data = [
{a:"A1",b:100,c:800},
{a:"B1",b:300,c:400}
];
data
.reduce(function(acc, x, i) {
if (x.b > x.c) {
return acc.concat({key: 'b', value: x.b });
}
return acc.concat({key: 'c', value: x.c });
}, [])
.reduce(function(acc, x, i) {
if (x.value > acc.value) {
return {
key: x.key,
value: x.value,
index: i
};
}
return acc;
}, {key: '', value: Number.MIN_VALUE, index: -1});
If you are using a library like lodash or underscore you can simply do:
_.max(_.map(JObject, function(a){
return _.max(_.values(a));
}));
You can also solve it with reduce:
_.reduce(JObject, function(val, a){
return _.max(_.values(a)) > val ? _.max(_.values(a)) : val;
},0);

Categories