I've this object:
const dataset = {
"2019": {
"a": 1,
"b": 2,
"c": 3,
"d": 4
},
"2020": {
"a": 2,
"b": 4,
"c": 6,
"d": 8
},
"2021": {
"a": 10,
"b": 11,
"c": 12,
"d": 13
}
}
I would like to obtain these two objects:
const obj1 = {
"2019": {
"a": 1,
"c": 3,
},
"2020": {
"a": 2,
"c": 6,
},
"2021": {
"a": 10,
"c": 12,
}
}
const obj2 = {
"2019": {
"b": 2,
"d": 4
},
"2020": {
"b": 4,
"d": 8
},
"2021": {
"b": 11,
"d": 13
}
}
So "split" the object in two objects based on some keys of the inner objects.
Here is what I tried:
function pickKeys(dataObj, keys) {
return Object.entries(dataObj).map(([d, obj]) => {
return { [d]: _.pick(obj, keys) }
})
}
const obj1 = pickKeys(dataset, ['a', 'c'])
The result is:
const obj1 = [
{ '2019': { a: 1, c: 3 } },
{ '2020': { a: 2, c: 6 } },
{ '2021': { a: 10, c: 12 } }
]
So almost there but it's not perfect. Which is the better way to do that?
You do this using combination of map, reduce methods and one for...in loop that will turn array of keys into array of objects. Then you can use array destructuring to get two separate objects.
const dataset = {"2019":{"a":1,"b":2,"c":3,"d":4},"2020":{"a":2,"b":4,"c":6,"d":8},"2021":{"a":10,"b":11,"c":12,"d":13}}
const [a, b] = [['a', 'c'], ['b', 'd']]
.map(keys => keys.reduce((r, key) => {
for (let year in dataset) {
if (!r[year]) r[year] = {}
r[year][key] = dataset[year][key]
}
return r;
}, {}))
console.log(a)
console.log(b)
The problem is that map returns an array with replaced elements, while you want an object.
Since you are already using Lodash you could use mapValues to transform the values of an object and return an object instead of an array.
function pickKeys(dataObj, keys) {
return _.mapValues(dataObj, obj => _.pick(obj, keys));
}
function pickKeys(dataObj, keys) {
return _.mapValues(dataObj, obj => _.pick(obj, keys));
}
const dataset = {
"2019": {
"a": 1,
"b": 2,
"c": 3,
"d": 4
},
"2020": {
"a": 2,
"b": 4,
"c": 6,
"d": 8
},
"2021": {
"a": 10,
"b": 11,
"c": 12,
"d": 13
}
}
console.log(pickKeys(dataset, ["a", "c"]));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
You could map the wanted keys by using the entries of the given object.
const
dataset = { 2019: { a: 1, b: 2, c: 3, d: 4 }, 2020: { a: 2, b: 4, c: 6, d: 8 }, 2021: { a: 10, b: 11, c: 12, d: 13 } },
groups = [['a', 'c'], ['b', 'd']],
[result1, result2] = Object
.entries(dataset)
.reduce((r, [k, o]) =>
groups.map((group, i) =>
group.reduce(
(q, g) => ({ ...q, [k]: { ...q[k], [g]: o[g] } }),
r[i] || {}
)
),
[]
);
console.log(result1);
console.log(result2);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The next provided example code is reduce based, generic but configurable in its usage ...
function createAndCollectSubdata(collector, dataEntry) {
const { keyLists, subdataList } = collector;
const [ dataKey, dataValue ] = dataEntry;
keyLists.forEach((keyList, idx) => {
const data = subdataList[idx] || (subdataList[idx] = {});
const subdata = data[dataKey] || (data[dataKey] = {}) ;
keyList.forEach(key => subdata[key] = dataValue[key]);
});
return collector;
}
const dataset = {
"2019": {
"a": 1,
"b": 2,
"c": 3,
"d": 4
},
"2020": {
"a": 2,
"b": 4,
"c": 6,
"d": 8
},
"2021": {
"a": 10,
"b": 11,
"c": 12,
"d": 13
}
};
const [
acSubdata,
bdSubdata
] = Object.entries(dataset).reduce(createAndCollectSubdata, {
keyLists: [["a", "c"], ["b", "d"]],
subdataList: []
}).subdataList;
console.log('acSubdata :', acSubdata);
console.log('bdSubdata :', bdSubdata);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Related
I have an array of objects.
I want to merge the objects into a single array by same key. At that time, I also want to include other value in the array together.
It doesn't matter whether the merged array is an array or an object.
Current array:
[
{
"datetime": "2022-01-10",
"a": 0.5,
"b": 80.6,
"c": 1002.2
},
{
"datetime": "2022-01-11",
"a": 0.7,
"b": 80.4,
"c": 1002.4
},
{
"datetime": "2022-01-12",
"a": 0.4,
"b": 80.2,
"c": 1002.3
}
]
Expected result:
[
[
["2022-01-10", 0.5], ["2022-01-11", 0.7], ["2022-01-12", 0.4]
],
[
["2022-01-10", 80.6], ["2022-01-11", 80.4], ["2022-01-12", 1002.4]
],
[
["2022-01-10", 1002.2], ["2022-01-11", 1002.4], ["2022-01-12", 1002.3]
]
]
or
{
"a": [
["2022-01-10", 0.5], ["2022-01-11", 0.7], ["2022-01-12", 0.4]
],
"b": [
["2022-01-10", 80.6], ["2022-01-11", 80.4], ["2022-01-12", 1002.4]
],
"c": [
["2022-01-10", 1002.2], ["2022-01-11", 1002.4], ["2022-01-12", 1002.3]
]
}
I use forEach() and it works.
But I want to know if there are other ways.
const foo = [[], [], []];
json.forEach((item) => {
const [a, b, c] = foo;
a.push([item.datetime, item.a]);
b.push([item.datetime, item.b]);
c.push([item.datetime, item.c]);
});
As for generic solutions (only the datetime property of each item needs to be known, thus the solutions are agnostic to all other properties) to both of the OP's use cases,
the array of arrays where the nested array items are tuples of distinct datetime values and values of same item keys
and the object of arrays where the arrays are the same but get referred to by keys which are distinct from datetime,
one easily could use the same reduce based approach only that
for the latter result (object based key specific array of arrays) one passes an object as initial value and creates and aggregates the key specific nested arrays upon the currently processed key of each item's rest-property data-entries,
whereas for the former result (array of arrays) the initial value is an array where one needs to create and/or access each key specific inner array by the current index of each of the rest-data's values.
const sampleData = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
console.log(
sampleData
.reduce((result, { datetime, ...rest }) => {
Object
.values(rest)
.forEach((value, idx) =>
(result[idx] ??= []).push([datetime, value])
);
return result;
}, [])
);
console.log(
sampleData
.reduce((result, { datetime, ...rest }) => {
Object
.entries(rest)
.forEach(([key, value]) =>
(result[key] ??= []).push([datetime, value])
);
return result;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
In case the key (insertion) order of the original array items can not be guarantied amongst all items, one should use the key-specific second solution, where one would pass the created object to Object.values in order to get an array of arrays ...
const sampleData = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
console.log(
Object
.values(
sampleData
.reduce((result, { datetime, ...rest }) => {
Object
.entries(rest)
.forEach(([key, value]) =>
(result[key] ??= []).push([datetime, value])
);
return result;
}, {})
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
You can use reduce function where the initial value will be an empty object.
after that, you can check if that object contains that particular key or not and push the data accordingly
a = [
{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
},
{
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
},
{
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
},
];
const solution = (key) => {
return a.reduce((acc, { [key]: keyValue, ...next }) => {
Object.entries(next).forEach(([dataKey, dataValue]) => {
(acc[dataKey] ??= []).push([keyValue, dataValue]);
});
return acc;
}, {});
};
console.log('Solution 1: ',Object.values(solution("datetime")));
console.log('Solution 2: ',solution("datetime"));
you can use javascript reduce to optimize this:
const data = temp.reduce((acc,{a,b,c,datetime})=>{
const [first,second,third] = acc;
first.push([datetime, a]);
second.push([datetime, b]);
third.push([datetime, c]);
return acc;
},[[],[],[]])
const Json = [
{
"datetime": "2022-01-10",
"a": 0.5,
"b": 80.6,
"c": 1002.2
},
{
"datetime": "2022-01-11",
"a": 0.7,
"b": 80.4,
"c": 1002.4
},
{
"datetime": "2022-01-12",
"a": 0.4,
"b": 80.2,
"c": 1002.3
}
]
const func = (arr, i) => {
let result = [];
const constants = ["a", "b", "c"];
for (let index = 0; index < arr.length; index++) {
result.push([arr[index].datetime, arr[index][`${constants[i]}`]]);
}
return result;
}
const result = Json.map((d, i) => {
return func(Json, i);
});
console.log(result)
See this it uses map and then returns an array just like your ist
const data = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
const res = data.map((m ,_) => [
[m.datetime,m.a],
[m.datetime,m.b],
[m.datetime,m.c],
]);
console.log({ res });
Or 2nd method because of comments
const data = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
const res = ["a","b","c"].map(key => data.map(obj => [
obj.datetime,
obj[key],
]));
console.log({ res });
I want to be able to sort the nested object below from the lowest number to the high number...
{
"a": 50,
"b": {
"c": {
"d": 69,
"e": 420,
"f": 21,
"g": {
"h": 5,
"i": 3,
}
}
},
"j": 1,
"k": 1000
}
... so that it can look like this:
{
"j": 1, // min in OBJECT
"b": {
"c": { // min in B
"g": { // min in C
"i": 3, // min in G
"h": 5
},
"f": 21,
"d": 69,
"e": 420
}
},
"a": 50,
"k": 1000
}
Note that the object is sorted by the lowest number inside objects, meaning the object's direct children's values are [1, 3, 50, 1000].
Quick and dirty using Object.entries, Array forEach, Array reduce and Array sort
getpaths creates an array in the form
[
["a.b.c", 2],
["d.e", 1],
....
]
not really happy with the code for getpaths - if someone has better code, please let me know
Which can then be sorted easily using Array sort
createPath takes a root object, a path and a value and creates the "path" in the object
so, given {}, "a.b.c", 2
would result in
{
a: {
b: {
c: 2
}
}
}
Since (non numeric) object keys are "sorted" in insertion order [ref], inserting the values in value order produces the object as required
let obj = { a: 50, b: { c: { d: 69, e: 420, f: 21, g: { h: 5, i: 3, }, }, }, j: 1, k: 1000 };
const getpaths = (obj) => {
const paths = [];
const fn = (obj, path = "") => {
Object.entries(obj).forEach(([key, value]) => {
if (typeof value === "object") {
return fn(value, `${path}.${key}`);
}
paths.push([`${path}.${key}`.slice(1), value]);
});
};
fn(obj);
return paths;
};
const createPath = (obj, [path, value]) => {
path.split(".").reduce(
(acc, key, index, array) => acc[key] ||= array[index + 1] ? {} : value, obj);
return obj;
};
const result = getpaths(obj)
.sort(([, a], [, b]) => a - b)
.reduce((acc, item) => createPath(acc, item), {});
console.log(JSON.stringify(result, null, 4));
Suppose I have an object:
let array = [
{a: 1, b: 5, c: 9},
{a: 2, b: 6, c: 10},
{a: 3, b: 7, c: 11},
{a: 4, b: 8, c: 12}
];
then I have a dictionary:
const columns = [
{ key: 'a', value: 'a' },
{ key: 'b', value: 'b' },
]
I want to filter out properties that are not defined in columns.
I have tried
array.map((x) => ({"a": x.a, "b": x.b}))
Is there a way to use the data defined in columns instead of manually typing all the properties?
Desired output:
[
{
"a": 1,
"b": 5
},
{
"a": 2,
"b": 6
},
{
"a": 3,
"b": 7
},
{
"a": 4,
"b": 8
}
]
You could map entries and get the new objects.
let
array = [{ a: 1, b: 5, c: 9 }, { a: 2, b: 6, c: 10 }, { a: 3, b: 7, c: 11 }, { a: 4, b: 8, c: 12 }],
columns = [{ key: 'a', value: 'a' }, { key: 'b', value: 'b' }],
keys = columns.map(({ key }) => key),
result = array.map(o => Object.fromEntries(keys.map(k => [k, o[k]])));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use this.
This uses just an array to hold the desired columns because I don't get why you would use a dictionary with key and value being the same.
let array = [
{ a: 1, b: 5, c: 9 },
{ a: 2, b: 6, c: 10 },
{ a: 3, b: 7, c: 11 },
{ a: 4, b: 8, c: 12 },
];
const desiredColumns = ["a", "b"];
const transformed = array.map(item => {
const obj = {};
desiredColumns.forEach(col => {
if(col in item){
obj[col] = item[col];
}
})
return obj;
})
console.log(array);
console.log(transformed)
Another, slightly less direct way using map() and reduce():
Create an array with all the keys we'll keep
Reduce the array to get the desired result
Add current key + value if key keep array
const array = [{a: 1, b: 5, c: 9}, {a: 2, b: 6, c: 10}, {a: 3, b: 7, c: 11}, {a: 4, b: 8, c: 12} ];
const columns = [{ key: 'a', value: 'a' }, { key: 'b', value: 'b' }, ];
const toKeep = columns.map(({ key }) => key).flat();
const result = array.map(a =>
Object.keys(a)
.reduce((prev, cur) => (toKeep.includes(cur)) ? { ...prev, [cur]: a[cur] } : prev, {})
);
console.log(result);
Result:
[
{
"a": 1,
"b": 5
},
{
"a": 2,
"b": 6
},
{
"a": 3,
"b": 7
},
{
"a": 4,
"b": 8
}
]
there is a way for merge array items by first item
_.merge({a: 1, c: [{y: 8}, {z: 9}]}, {b: 0, c: [{x: 5}]})
Result:
{
"a": 1,
"c": [
{
"y": 8,
"x": 5
},
{
"z": 9
}
],
"b": 0
}
what i want:
{
"a": 1,
"c": [
{
"y": 8,
"x": 5
},
{
"z": 9,
"x": 5 // <-------------------------
}
],
"b": 0
}
I want merge a source object by another used like a model. In case of array the model define only the first item of the collection and the source object should reflect the first model item into all the collection items.
I wouldn't actually do it, as it might be very confusing (especially when you've got an array with more than 1 item).
You can use _.mergeWith(), and manually iterate and merge array items.
const mrg = (o1, o2) => _.mergeWith(
o1, o2,
(a, b) => _.isArray(a) && _.isArray(b) ?
a.map((item, i) => mrg(item, b[Math.min(i, b.length - 1)]))
:
undefined
)
const result = mrg({a: 1, c: [{y: 8}, {z: 9}]}, {b: 0, c: [{x: 5}]})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.14/lodash.js"></script>
Use _.mergeWith
function customizer(objValue, srcValue) {
if (_.isArray(objValue) && _.every(objValue, _.isObject) &&
_.isArray(srcValue) && _.every(srcValue, _.isObject) )
{
let newObj = Object.assign(...srcValue)
_.forEach(objValue, function(obj, index) {
objValue[index] = Object.assign(obj, newObj)
});
}
}
let a = {a: 1, c: [{y: 8}, {z: 9}]}
let b = {b: 0, c: [{x: 5}]}
let res = _.mergeWith(a, b, customizer)
Result:
{
"a": 1,
"c": [
{
"y": 8,
"x": 5
},
{
"z": 9,
"x": 5
}
],
"b": 0
}
In Javascript, is there a way to filter the JSON file based on the values in the array?
For example, with the following array:
["c", "f"]
and the JSON object file:
[{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6
},{
"a": 2,
"b": 4,
"c": 6,
"d": 8,
"e": 10,
"f": 12
}]
I would like to generate the following result:
[{
"c": 3,
"f": 6
},{
"c": 6,
"f": 12
}]
You could map the values of the given keys for a new object.
var keys = ["c", "f"],
data = [{ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }, { a: 2, b: 4, c: 6, d: 8, e: 10, f: 12 }],
filtered = data.map(o => Object.assign(...keys.map(k => ({ [k]: o[k] }))));
console.log(filtered);
You can use map() and reduce() for this.
var keys = ["c", "f"];
var arr = [{"a":1,"b":2,"c":3,"d":4,"e":5,"f":6},{"a":2,"b":4,"c":6,"d":8,"e":10,"f":12}];
var result = arr.map(o => {
return keys.reduce((r, k) => (o[k] && (r[k] = o[k]), r), {})
})
console.log(result)