How to expand single level nested hashes on the fly? - javascript

I have an object where a few of the keys are nested single level hashes. In this example only b is nested.
const j = {
a: 'A',
b: {
bb: 'BB',
bbb: 'BBB',
},
c: 'C'
};
Question
What I am looking for is a way to loop over the object and if a key is a nested object, then print its keys instead.
a
bb
bbb
c
Does anyone know how to do that?

You can do this recursively:
function printKeys(obj) {
for (const [key, val] of Object.entries(obj)) {
if (typeof val === "object") {
printKeys(val);
} else {
console.log(key);
}
}
}
If you only have one level of nesting at most, #blex's answer is probably the better one.

You could do it with Object.entries and flatMap:
const j = { a: 'A', b: { bb: 'BB', bbb: 'BBB' }, c: 'C' };
function getOneLevelKeys(obj) {
return Object.entries(obj)
.flatMap(([key, value]) => typeof value === "object" ? Object.keys(value) : key);
}
console.log( getOneLevelKeys(j) );

You can use a recursive flatMap with Object.keys.
const j = {
a: 'A',
b: {
bb: 'BB',
bbb: 'BBB',
},
c: 'C'
};
const getKeys = o => Object.keys(o).flatMap(x => o[x] === Object(o[x])
? getKeys(o[x]) : x);
console.log(getKeys(j));

Related

JavaScript Reduce an array to find match in Object

I am trying to incorporate array method: reduce.
Basically, what I am trying to accomplish here is to reduce the array below to an object where anything that matches obj's key value.
const arr = ['a', 'c', 'e'];
const obj = { a: 1, b: 2, c: 3, d: 4 };
let output = select(arr, obj);
console.log(output); // --> { a: 1, c: 3 }
My select method:
function select(arr, obj) {
let newObj = {};
for (let prop in obj) {
for (let i = 0; i < arr.length; i++) {
if (prop === arr[i]) {
newObj[prop] = obj[prop];
}
}
}
return newObj;
}
I set {} as initializer for arr.reduce as such if current value of array matches key of object then it would add to accumulator the key value, but I am receiving an error message from the console that if expression cannot return boolean.
Here is my attempt using .reduce():
function select(arr, obj) {
let result = arr.reduce(function(x, y) {
if (y in obj) {
x[y] = obj[y]
return x;
}
}, {})
return result;
}
Please advise.
You must always return the accumulator. Here is how to use reduce
function select(arr, obj) {
return arr.reduce(function (acc, key) {
if (key in obj) acc[key] = obj[key];
return acc;
}, {});
}
const arr = ['a', 'c', 'e'];
const obj = { a: 1, b: 2, c: 3, d: 4 };
let output = select(arr, obj);
console.log(output); // --> { a: 1, c: 3 }
The accumulator should be returned in all the cases.
I used an implementation using a filter for your reference:
const arr = ['a', 'c', 'e'];
const obj = { a: 1, b: 2, c: 3, d: 4 };
function select (obj,arr){
let newObj = Object.keys(obj).filter(key => arr.includes(key)).reduce((acc,key) => {
acc[key]=obj[key]
return acc
},{})
return newObj
}
console.log(select(obj,arr));
function select(arr, obj) {
return arr.reduce((acc, curr) => {
if(obj[curr]) {
acc[curr] = obj[curr];
}
return acc;
}, {})
}

Cleaner way to filter properties from object

I created a function to remove the properties I tell him to
function trimProperties(data, properties) {
return data.map(o => {
Object.keys(o).forEach(k => {
if (properties.includes(k)) {
delete o[k];
}
});
return o;
});
}
My use case is usually like this
let array = [
{
a: 'A',
b: 'B',
c: 'C'
},
{
a: 'A2',
b: 'B2',
c: 'C2'
}
]
// Remove every property 'b' or 'c' from the objects inside the array
trimProperties(array, ['b','c']);
My question is simple, how can I make this function faster, because my array sometimes can get pretty big since it's the result set from a database access
delete cause indexes recalculation all the time, creating new array would be faster
let array = [
{
a: 'A',
b: 'B',
c: 'C'
},
{
a: 'A2',
b: 'B2',
c: 'C2'
}
]
function trimProperties(data, properties) {
let i = 0;
const result = []
while (i < data.length) {
var o = {};
Object.keys(data[i]).forEach(k => {
if (!properties.includes(k)) {
o[k] = data[i][k];
}
})
i++;
if (Object.keys(o).length) {
result.push(o);
}
}
return result;
}
// Remove every property 'b' or 'c' from the objects inside the array
console.log(trimProperties(array, ['b','c']));
A one liner:
array.map(o => Object.fromEntries(Object.entries(o).filter(([k,v]) => !['b','c'].includes(k))))
Demo:
const array = [
{
a: 'A',
b: 'B',
c: 'C'
},
{
a: 'A2',
b: 'B2',
c: 'C2'
}
];
const excluded = ['b','c'];
const filtered = array.map(o => Object.fromEntries(Object.entries(o).filter(([k,v]) => !excluded.includes(k))));
console.log(filtered)

How to join JS objects inside an array? Onliner

What ONELINER could I use to join ANY number of objects inside of an array? The repeated values should be placed inside a nested array, like so:
I have this input:
[
{a: 123},
{b: "abc", c: 455},
{d: null, c: 01, b: {}}
]
I should get this result:
{
a: 123,
b: ["abc", {}],
c: [455, 01],
d: null
}
I have tried this, but no results :(
my_array.map(function(x) {
var result = {};
for(var i in x) {
result[i] = x[i];
}
return result;
});
Thank you!
You could reduce the array of objects. Inside, loop through keys of the each object. If the accumulator doesn't have the key, add it. If the key already exists, use concat to create an array of values. [].concat(acc[k], v) will handle if acc[k] is an array or a single value.
const input = [
{a: 123},
{b: 2222, c: 455},
{d: null, c: 01}
]
const output = input.reduce((acc, o) => {
Object.entries(o).forEach(([k, v]) => {
if (acc.hasOwnProperty(k))
acc[k] = [].concat(acc[k], v)
else
acc[k] = v
})
return acc
}, {})
console.log(output)
Here's a ES5 version of the above answer:
var input = [
{a: 123},
{b: 2222, c: 455},
{d: null, c: 01}
]
var output = input.reduce(function(acc, o) {
Object.keys(o).forEach(function(k) {
if (acc.hasOwnProperty(k))
acc[k] = [].concat(acc[k], o[k])
else
acc[k] = o[k]
})
return acc
}, {})
console.log(output)

How to change key name with map into nested object?

Here is the object
{
a: 1,
b: {
c: {
d: 2
},
e: 3
}
}
Here is the map
{
'a': 'aaa',
'b': 'bbb',
'b.c.d': 'bcd'
}
Here is the expected result.
{
aaa: 1,
bbb: {
c: {
bcd: 2
},
e: 3
}
}
I know there's a function in lodash _.get could get the value like b.c.d.
But how can I change the key name with the map?
You can do this recursively by keeping track of the current path and building a key into the map with that:
let o = {a: 1,b: {c: {d: 2},e: 3}}
let map = {
'a': 'aaa',
'b': 'bbb',
'b.c.d': 'bcd'
}
function makeObj(obj, map, p=[]) {
let ret = {}
Object.entries(obj).forEach(([k, v]) => {
let path = p.concat(k) // add onto current path
let mapKey = map[path.join('.')] || k
ret[mapKey] = (typeof v === 'object')
? makeObj(v, map, path) // if an object recurse and pass on the current path
: v // otherwise set the value
})
return ret
}
console.log(makeObj(o, map))

Access a property on an object from an array of keys

I want to write a helper function to unpack a specific object property from each object in an array of objects. Sometimes this property will be top level, other times it will be nested an arbitrary number of levels. So the crux of this question is: how can I access an object property based on an array of key names of variable length?
I'm hoping for something like:
const func = (arrOfObjects, ...keys) {
return arrOfObjects.map(object => {
return object[keys[0]][keys[1]] ... [keys[N]];
})
}
with example behaviour:
const input = [
{a: b: {c: 10}},
{a: b: {c: 11}},
{a: b: {c: 12}}
]
console.log(func(input, 'a', 'b', 'c'))
// [10, 11, 12]
console.log(func(input, 'a', 'b'))
// [{c: 10}, {c: 11}, {c : 12}]
I feel like there has to be a nice ES6 wizardry solution but as yet haven't found it so any help would be much appreciated!
Cheers,
P
You can get a short and easy solution using Array#reduce
const input = [
{a: { b: {c: 10}}},
{a: { b: {c: 11}}},
{a: { b: {c: 12}}}
]
console.log(func(input, ['a', 'b', 'c']))
// [10, 11, 12]
console.log(func(input, ['a', 'b']))
// [{c: 10}, {c: 11}, {c : 12}]
function func(input, props) {
return input.map(x => exctractByProps(x, props));
}
function exctractByProps(obj, props) {
return props.reduce(
(acc, prop) => typeof acc === 'object' && prop in acc ? acc[prop] : undefined,
obj
)
}
The main logic is to grab all the properties passed in and then try to get the value corresponding to obj[prop[0]][prop[1]][prop[2]]/* ... */[prop[n]]. If the object has an odd shape that doesn't match up with prop (for example, an input of {a: 1}, ['a', 'b'] or {d: {c: 1}}, ['a', 'b']) then the function returns undefined.
Based on the answers you gave me to my questions and your example. It seems as if the order will of the input will always match the objects nesting. So here is my solution:
const func = (arrOfObjects, ...keys) => {
return arrOfObjects.map(object => {
let obj = object, integer = keys.length;
for (let index = 0; index < integer; index++) {
obj = obj[keys[index]];
if(obj === undefined) break;
}
return obj;
});
};
const input = [
{ a: { b: { c: 10 } } },
{ a: { b: { c: 11 } } },
{ a: { b: { c: 12 } } }
];
console.log(func(input, "a", "b", "c"));
// [10, 11, 12]
console.log(func(input, "a", "b"));
// [{c: 10}, {c: 11}, {c : 12}]
Unfortunately there is no such thing as the javascript magic you where expecting.
Note: this code will not work when the order of the keys inside the object are nested at random depth. But for what you are trying to solve, this should work just fine. Also, I tried to preserve your initial code as good as possible
If you supply the accessor like [a, b, c[0], name], you can write a custom function which returns the value if found in a nested Object, otherwise returns undefined
let obj = {
a: 1,
b: 2,
c: {
d: 1,
e: [{
f: 3,
g: [{
z: 45
}]
}]
}
}
function findKeyFromPattern(obj, patternArr) {
let value = obj;
for(let i = 0; i < patternArr.length; i++) {
const arrmatch = patternArr[i].match(/(\w+)\[(\d+)\]/);
if(arrmatch) { // pattern matches and array accessor syntax
value = value[arrmatch[1]];
if(typeof value === 'object' && value !== null) {
value = value[arrmatch[2]];
} else {
return;
}
} else {
if(value[patternArr[i]]) {
value = value[patternArr[i]];
} else {
return;
}
}
}
return value;
}
console.log(findKeyFromPattern(obj, ['c', 'e[0]', 'g']));
console.log(findKeyFromPattern(obj, ['c', 'e[1]', 'g']))

Categories