Pick specific properties from nested objects - javascript

I have javascript objects which looks similar to this:
{
id: 43,
name: 'ajajaj'
nestedObj1: {
id: 53,
name: 'ababab'
foo: 'xxxx'
nestedObj2: {
id: 90,
name: 'akakaka'
foo2: 'sdsddd'
surname: 'sdadasd'
nestedObj3: {
id: ..
name: ..
...
},
objectsArr: [
{id: .., name: ..., nestedOb4: {...} },
{id: .., name: ..., nestedOb4: {...} }
]
},
foo0: ...
}
name: 'blabla'
}
Each objects which I have, has property id and name. My goal is pick all properties in first level, so properties of object with id 43 I pick all, and from every nested objects, I just pick properties id and name, and another will be thrown away.
I am using in project lodash, so I found function pick, but I don't know how to use this function for nested objects. Also note that nested objects could be also in array. Exist some elegant way for that please? Thanks in advice.

pick function of lodash is able to fetch nested property like so:
_.pick(obj, [
'id',
'name',
'nestedObj1.id',
'nestedObj1.name',
'nestedObj1.nestedObj2.id',
'nestedObj1.nestedObj2.name',
'nestedObj1.nestedObj2.nestedObj3.id',
'nestedObj1.nestedObj2.nestedObj3.name',
'nestedObj1.nestedObj2.objectsArr[0].id',
'nestedObj1.nestedObj2.objectsArr[0].name',
'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.name',
'nestedObj1.nestedObj2.objectsArr[1].id',
'nestedObj1.nestedObj2.objectsArr[1].name',
'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.name',
])
The problem is, you must know the array length to pick all objects in them.
To mitigate this, you can use this function to iterate over arrays:
const _ = require('lodash')
const pickExtended = (object, paths) => {
return paths.reduce((result, path) => {
if (path.includes("[].")) {
const [collectionPath, itemPath] = path.split(/\[]\.(.+)/);
const collection = _.get(object, collectionPath);
if (!_.isArray(collection)) {
return result;
}
const partialResult = {};
_.set(
partialResult,
collectionPath,
_.map(collection, item => pickExtended(item, [itemPath]))
);
return _.merge(result, partialResult);
}
return _.merge(result, _.pick(object, [path]));
}, {});
};
You can then get the result you want with:
pickExtended(obj, [
'id',
'name',
'nestedObj1.id',
'nestedObj1.name',
'nestedObj1.nestedObj2.id',
'nestedObj1.nestedObj2.name',
'nestedObj1.nestedObj2.nestedObj3.id',
'nestedObj1.nestedObj2.nestedObj3.name',
'nestedObj1.nestedObj2.objectsArr[].id',
'nestedObj1.nestedObj2.objectsArr[].name',
'nestedObj1.nestedObj2.objectsArr[].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[].nestedObj4.name',
])
Here is the associated mocha test:
describe("pickExtended", () => {
const object = {
a: 1,
b: "2",
c: { d: 3, e: 4 },
f: [
{ a: 11, b: 12, c: [{ d: 21, e: 22 }, { d: 31, e: 32 }] },
{ a: 13, b: 14, c: [{ d: 23, e: 24 }, { d: 33, e: 34 }] }
],
g: [],
h: [{ a: 41 }, { a: 42 }, { a: 43 }]
};
it("should pick properties in nested collection", () => {
expect(
pickExtended(object, ["a", "c.d", "f[].c[].d", "g[].a", "b[].a", "h[]"])
).to.deep.equal({
a: 1,
c: { d: 3 },
f: [{ c: [{ d: 21 }, { d: 31 }] }, { c: [{ d: 23 }, { d: 33 }] }],
g: []
});
});
});
Keep in mind that no performance test was done on this. It might get slow for big object.

You can do this using recursion, as long as your objects do have an end to the levels of nesting. Otherwise, you run the risk of causing a stack limit error.
function pickProperties(obj) {
var retObj = {};
Object.keys(obj).forEach(function (key) {
if (key === 'id' || key === 'name') {
retObj[key] = obj[key];
} else if (_.isObject(obj[key])) {
retObj[key] = pickProperties(obj[key]);
} else if (_.isArray(obj[key])) {
retObj[key] = obj[key].map(function(arrayObj) {
return pickProperties(arrayObj);
});
}
});
return retObj;
}

Related

How do I filter the unique objects from an array if the filter key can have any type of value?

interface FormValues {
key: string;
value: any;
}
const array: FormValues[] = [
{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 'str' // string
},
{
key: 'C',
value: { a: 1, b: '2' } // object
},
{
key: 'C',
value: ['a','2'] // array
},
{
key: 'C',
value: ['a','2'] // array
}
{
key: 'B',
value: true // boolean
}
]
I want to filter the objects based on field value, which can have a value of any type.
I tried to do it like this; my solution is not working for nested object checks.
const key = 'value';
const arrayUniqueByKey = [...new Map(array.map(item => [item[key], item])).values()];
output :
[{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 'str' // string
},
{
key: 'C',
value: { a: 1, b: '2' } // object
},
{
key: 'C',
value: ['a','2'] // array
},
{
key: 'B',
value: true // boolean
}]
You need to decide what makes two distinct objects "equal". In JavaScript, all built-in comparisons of objects (which includes arrays) are by reference. That means ['a','2'] === ['a','2'] is false because two distinct array objects exist, despite having the same contents. See How to determine equality for two JavaScript objects? for more information.
I will take the approach that you would like two values to be considered equal if they serialize to the same value via a modified version of JSON.stringify() where the order of property keys are guaranteed to be the same (so {a: 1, b: 2} and {b: 2, a: 1} will be equal no matter how those are stringified). I use a version from this answer to do so:
function JSONstringifyOrder(obj: any, space?: number) {
var allKeys: string[] = [];
var seen: Record<string, null | undefined> = {};
JSON.stringify(obj, function (key, value) {
if (!(key in seen)) {
allKeys.push(key); seen[key] = null;
}
return value;
});
allKeys.sort();
return JSON.stringify(obj, allKeys, space);
}
And now I can use that to make the keys of your Map:
const arrayUniqueByKey = [...new Map(array.map(
item => [JSONstringifyOrder(item[key]), item]
)).values()];
And you can verify that it behaves as you'd like:
console.log(arrayUniqueByKey);
/* [{
"key": "A",
"value": 1
}, {
"key": "A",
"value": "str"
}, {
"key": "C",
"value": {
"a": 1,
"b": "2"
}
}, {
"key": "C",
"value": [
"a",
"2"
]
}, {
"key": "B",
"value": true
}] */
Playground link to code
This will combine any duplicate keys, creating a new property values to hold the array of combined values (from like keys).
const array = [{key: 'A', value: 1},{key: 'A', value: 'str'},{key: 'C', value: { a: 1, b: '2'}},{key: 'B',value: true}]
const arrayUniqueByKey = [array.reduce((b, a) => {
let f = b.findIndex(c => c.key === a.key)
if (f === -1) return [...b, a];
else {
b[f].values = [...[b[f].value], a.value];
return b
}
}, [])];
console.log(arrayUniqueByKey)
You can use Array.prototype.reduce() combined with JSON.stringify() and finaly get the result array of values with Object.values()
const array = [{key: 'A',value: 1,},{key: 'A',value: 1,},{key: 'A',value: 'str',},{key: 'C',value: { a: 1, b: '2' },},{key: 'C',value: ['a', '2'],},{key: 'C',value: ['a', '2'],},{key: 'B',value: true}]
const result = Object.values(array.reduce((a, c) => ((a[JSON.stringify(c)] = c), a), {}))
console.log(result)

How to flatten object of objects recursively into array

I have an object like this:
const myObj = {
a: {
b: {
c: 1,
d: 2
},
f: {
z: 4,
u: 6
}
}
}
into this:
const myObj = [
{
c: 1,
d: 2,
},
{
z: 4,
u: 6,
}
]
I found this: How to recursively transform an array of nested objects into array of flat objects? but the original is an array of objects, and mine is an object itself.
You can traverse the values of the objects until you reach the leaves (objects with no values that are other objects).
const myObj = {
a: {
b: {
c: 1,
d: 2
},
f: {
z: 4,
u: 6
}
}
};
const flatObj = o => Object.values(o).some(x => x === Object(x)) ?
Object.values(o).flatMap(flatObj) : [o];
console.log(flatObj(myObj))

cloning a new object from existing in javascript

I have a multi step javascript object like this:
const source = {
prev: [],
current: {
list: [{ a: 1, b: 2, c: 3 }],
data: [{ c: 1, b: 2 }]
},
id: 12,
next: []
};
and I would like to create a fresh copy of it.
I know if I use something like let copy = { ...source }; it's like a shallow copy. So if i change those arrays or objects in source they will also change in copy.
Is there anyway that by de-structuring, I get a fresh copy of all items? or should I do it manually for each and every level deep like this:
copy.prev = [...source.prev]
copy.current.list = [...source.current.list]
copy.current.data= [...source.current.data]
and so on
There's an old way to do it, but still works:
var cloned = JSON.parse(JSON.stringify(original));
You can use JSON.stringify
const source = {
prev: [],
current: {
list: [{ a: 1, b: 2, c: 3 }],
data: [{ c: 1, b: 2 }]
},
id: 12,
next: []
};
newObj = JSON.parse(JSON.stringify(source));
You can deconstruct like this:
const source = {
prev: [],
current: {
list: [{ a: 1, b: 2, c: 3 }],
data: [{ c: 1, b: 2 }]
},
id: 12,
next: []
};
const {
current: {list: listClone, data: dataClone}
} = source
console.log(listClone, dataClone)
With a simple object containing objects, arrays, and primitives, it's not that much code just to recursively clone everything.
Just return the primitives, map() the arrays, and reduce the object keys passing the values back in. Something like:
const source = {prev: [],current: {list: [{ a: 1, b: 2, c: 3 }],data: [{ c: 1, b: 2 }]},id: 12,next: []};
function clone(obj){
return (typeof obj == 'object')
? (Array.isArray(obj))
? obj.map(i => clone(i))
: Object.entries(obj).reduce((o, [key, value]) =>
Object.assign(o, {[key]: clone(value)}), {})
: obj
}
let a_clone = clone(source)
console.log(JSON.stringify(a_clone, null, 2))
// is the clone deep? should be false
console.log(a_clone.prev === source.prev)
console.log(a_clone.current.list === source.current.list)

Shorter way to convert object entries to array while flattening the contained arrays of objects? (improve my solution)

I have an object like this:
export default {
characters: {
hero: { h: 1 },
boundaries: { b: 1 },
zombies: [{ z: 1 }, { z: 2 }, { z: 3 }],
bullets: [{ b: 1 }, { b: 2 }, { b: 3 }],
},
};
I need to create an array from it that looks something like this, but the order would not be important:
[ { h: 1 }, { b: 1 }, { z: 1 }, { z: 2 }, { z: 3 }, { b: 1 }, { b: 2 }, { b: 3 } ]
and my current solution works but it seems like it could be improved to be more eloquent and use less lines of code.
import gameState from './modules/gameState.js';
let toRender = [];
Object.keys(gameState.characters).forEach(e => {
if (Array.isArray(gameState.characters[e])) {
toRender.push(...gameState.characters[e]);
} else {
toRender.push(gameState.characters[e]);
}
});
renderScreen(toRender);
function renderScreen(theArgs) {
theArgs.forEach(character => {
character.draw();
});
}
You could use reduce with concat on Object.values.
const data = {characters: {hero: { h: 1 },boundaries: { b: 1 },zombies: [{ z: 1 }, { z: 2 }, { z: 3 }],bullets: [{ b: 1 }, { b: 2 }, { b: 3 }],},};
const result = Object.values(data.characters).reduce((r, e) => r.concat(e), []);
console.log(result)
You can also just use spread syntax ... with concat on Object.values.
const data = {characters: {hero: { h: 1 },boundaries: { b: 1 },zombies: [{ z: 1 }, { z: 2 }, { z: 3 }],bullets: [{ b: 1 }, { b: 2 }, { b: 3 }],},};
const result = [].concat(...Object.values(data.characters))
console.log(result)
You could also use Object.values(), map() and Function.prototype.apply() to get the required result.
DEMO
const characters= {
hero: { h: 1 },
boundaries: { b: 1 },
zombies: [{ z: 1 }, { z: 2 }, { z: 3 }],
bullets: [{ b: 1 }, { b: 2 }, { b: 3 }],
};
console.log([].concat.apply([],Object.values(characters).map(v=>v)));
.as-console-wrapper {max-height: 100% !important;top: 0;}

Object array clone with subset of properties

What is the most efficient way in JavaScript to clone an array of uniform objects into one with a subset of properties for each object?
UPDATE
Would this be the most efficient way to do it or is there a better way? -
var source = [
{
id: 1,
name: 'one',
value: 12.34
},
{
id: 2,
name: 'two',
value: 17.05
}
];
// copy just 'id' and 'name', ignore 'value':
var dest = source.map(function (obj) {
return {
id: obj.id,
name: obj.name
};
});
using Object Destructuring and Property Shorthand
let array = [{ a: 5, b: 6, c: 7 }, { a: 8, b: 9, c: 10 }];
let cloned = array.map(({ a, c }) => ({ a, c }));
console.log(cloned); // [{ a: 5, c: 7 }, { a: 8, c: 10 }]
First define a function that clone an object and return a subset of properties,
Object.prototype.pick = function (props) {
return props.reduce((function (obj, property) {
obj[property] = this[property];
return obj;
}).bind(this), {});
}
Then define a function that clone an array and return the subsets of each object
function cloneArray (array, props) {
return array.map(function (obj) {
return obj.pick(props);
});
}
Now let's say you have this array :
var array = [
{ name : 'khalid', city : 'ifrane', age : 99 },
{ name : 'Ahmed', city : 'Meknes', age : 30 }
];
you need to call the function and pass the array of properties you need to get as result
cloneArray(array, ['name', 'city']);
The result will be :
[
{ name : 'khalid', city : 'ifrane' },
{ name : 'Ahmed', city : 'Meknes' }
]
Performance-wise, that would be the most efficient way to do it, yes.

Categories

Resources