Getting Nested Objects Length - javascript

I have the following object:
const object = {
user1: {
"article1": {}
},
user2: {
"article2": {},
"article3": {},
"article4": {}
},
user3: {
"article5": {}
},
user4: {
"article6": {}
}
};
I can count the user articles using method below.
const user1count = (object && object.user1 && Object.keys(object.user1).length) || 0
const user2count = (object && object.user2 && Object.keys(object.user2).length) || 0
const user3count = (object && object.user3 && Object.keys(object.user3).length) || 0
const user4count = (object && object.user4 && Object.keys(object.user4).length) || 0
Is there a more optimal method someone can suggest? I would prefer an object like one below.
const counts = {
user1: 1,
user2: 3,
user3: 1,
user4: 1
};
Thanks in advance.

You can make use of Object.entries(), Object.keys() and .reduce() methods to get the desired output:
const object = {
user1: {
"article1": {}
},
user2: {
"article2": {},
"article3": {},
"article4": {}
},
user3: {
"article5": {}
},
user4: {
"article6": {}
}
};
const counts = Object.entries(object)
.reduce((r, [k, o]) => (r[k] = Object.keys(o).length, r), {});
console.log(counts);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You don't need to check like this object && object.user1. Just loop on outer object keys and then find the length. Create final object using reduce.
const input = {
user1: {
"article1": {}
},
user2: {
"article2": {},
"article3": {},
"article4": {}
},
user3: {
"article5": {}
},
user4: {
"article6": {}
}
};
const output = Object.keys(input).reduce((prev, key) => {
prev[key] = Object.keys(input[key]).length;
return prev;
}, {});
console.log(output);

You could map the entries of the object. Create an array of entries with the same key and the value set the length of keys. Use Object.fromEntries() to convert the entries to an object
const object = {
user1: {
"article1": {}
},
user2: {
"article2": {},
"article3": {},
"article4": {}
},
user3: {
"article5": {}
},
user4: {
"article6": {}
}
};
const entries = Object.entries(object).map(([k, v]) => [k, Object.keys(v).length]),
output = Object.fromEntries(entries);
console.log(output)

You can use forEach loop
const object = {
user1: {
"article1": {}
},
user2: {
"article2": {},
"article3": {},
"article4": {}
},
user3: {
"article5": {}
},
user4: {
"article6": {}
}
};
Object.keys(object).forEach(o => {
console.log(o, Object.keys(object[o]))
if (typeof object[o] == 'object') {
console.log(Object.keys(object[o]).length);
}
})

const counts = Object.entries(object)
.reduce((acc, entry) => ({
...acc,
[entry[0]]: Object.keys(entry[1] || {}).length
}));

You can use Object.assign() with Object.entries(). First, you can .map() the entires to objects (with the key being the user and value the number of keys in that user object) and then assign each individual object in the mapped array to a single object:
const object = {user1: { "article1": {} }, user2: { "article2": {}, "article3": {}, "article4": {} }, user3: { "article5": {} }, user4: { "article6": {} }};
const res = Object.assign(
{},
...Object.entries(object).map(([key, obj]) => ({[key]: Object.keys(obj).length}))
);
console.log(res);

Related

Separating (n) keys from array of objects into a single array with keys names

I need to perform filter in the array of objects to get all the keys. Although, whenever there is a obj inside of that key, I would need to get the key name and concat with the key name from the obj, so for example:
const data = [ id: 5, name: "Something", obj: { lower: True, higher: False } ]
result = ["id", "name", "obj.lower", "obj.higher"]
I could manage to do the above code, but, if there is more objs inside the data, I would need to keep adding a if condition inside of my logic, I would like to know if there is any other way, so it doesn't matter how many objects I have inside the objects, It will concat always.
The code I used from the above mention:
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const csvData = [
Object.keys(itemsArray[0]),
...itemsArray.map(item => Object.values(item))
].map(e => e.join(",")).join("\n")
// Separating keys
let keys = []
const allKeys = Object.entries(itemsArray[0]);
for (const data of allKeys) {
if (typeof data[1] === "object") {
const gettingObjKeys = Object.keys(data[1]);
const concatingKeys = gettingObjKeys.map((key) => data[0] + "." + key);
keys.push(concatingKeys);
} else {
keys.push(data[0])
}
}
//Flating
const flattingKeys = keys.reduce((acc, val: any) => acc.concat(val), []);
What I would like to achieve, lets suppose I have this array of object:
const data =
[
{ id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}}
...
]
Final result = ["id", "obj.name", "obj.obj2.name2", "obj.obj2.test"]
OBS: The first obj contains all the keys I need, no need to loop through other to get KEYS.
I would like to achieve, all the keys from the first object of the array, and if there is objects inside of objects, I would like to concat the obj names (obj.obj2key1)
You could map the key or the keys of the nested objects.
const
getKeys = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getKeys(v).map(s => `${k}.${s}`)
: k
),
getValues = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getValues(v)
: v
),
data = { id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
keys = getKeys(data),
values = getValues(data);
console.log(keys);
console.log(values);
.as-console-wrapper { max-height: 100% !important; top: 0; }
something like this
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const item = itemsArray[0];
const getAllKeys = (obj, prefix=[]) => {
if(typeof obj !== 'object'){
return prefix.join('.')
}
return Object.entries(obj).flatMap(([k, v]) => getAllKeys(v, [...prefix, k]))
}
console.log(getAllKeys(item))
The OP solution can be simplified by accepting a prefix param (the parent key) and a results param (defaulted to [] and passed into the recursion) to do the flattening...
let obj = { key0: 'v0', key1: { innerKey0: 'innerV0', innerInner: { deeplyNested: 'v' } }, key2: { anotherInnerKey: 'innerV' } }
function recursiveKeys(prefix, obj, result=[]) {
let keys = Object.keys(obj);
keys.forEach(key => {
if (typeof obj[key] === 'object')
recursiveKeys(key, obj[key], result);
else
result.push(`${prefix}.${key}`)
});
return result;
}
console.log(recursiveKeys('', obj))
function getKeys(obj) {
return Object.keys((typeof obj === 'object' && obj) || {}).reduce((acc, key) => {
if (obj[key] && typeof obj[key] === 'object') {
const keys = getKeys(obj[key]);
keys.forEach((k) => acc.add(`${key}.${k}`));
} else {
acc.add(key);
}
return acc;
}, new Set());
}
// accumulate the keys in a set (the items of the array may
// have different shapes). All of the possible keys will be
// stored in a set
const s = itemsArray.reduce(
(acc, item) => new Set([...acc, ...getKeys(item)]),
new Set()
);
console.log('Keys => ', Array.from(s));
You can use recursion as follows. Since typeof([1,3,5]) is object, we also have to confirm that value is not an array, !Array.isArray(value):
const obj = { id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}};
const getKeys = (o,p) => Object.entries(o).flatMap(([key,value]) =>
typeof(value) === 'object' && !Array.isArray(value) ?
getKeys(value, (p?`${p}.`:"") + key) :
(p ? `${p}.`: "") + key
);
console.log( getKeys(obj) );

Javascript Recursion Issue - deeply nested object

I have a function which loops over the keys of an object and looks for sensitive keys like email, key, password, apiKey, secrets, secret and userKey.
If it finds any that have a value, it redacts the value.
However, its failing sometimes with an error like:
"RangeError: Maximum call stack size exceeded"
Whats causing the endless recursion??
const deepObjectRedactor = obj => {
const sensitiveKeys = [
'email',
'key',
'password',
'apiKey',
'secrets',
'secret',
'userKey'
];
Object.keys(obj).forEach(key => {
if (
sensitiveKeys.map(e => e.toLowerCase()).includes(key.toLowerCase()) &&
obj[key]
) {
obj[key] = '**********';
return;
}
if (obj[key] && typeof obj[key] === 'object') {
deepObjectRedactor(obj[key]);
}
});
};
// function invoking the redactor
const customRedactorFormat = info => {
if (info.message && typeof info.message === 'object') {
deepObjectRedactor(info.message);
}
return info;
});
You can write a generic map that works for objects and arrays. And then write redact as a specialization of map -
function map (t, f)
{ switch (t?.constructor)
{ case Array:
return t.map((v, k) => f(k, map(v, f)))
case Object:
return Object.fromEntries(
Object.entries(t).map(([k, v]) => [k, f(k, map(v, f))])
)
default:
return t
}
}
const redact = (t, keys = new Set) =>
map(t, (k, v) => keys.has(k) ? "*****" : v)
const data =
[ { user: 1, cc: 1234, password: "foo" }
, { nested: [ { a: 1, pin: 999 }, { b: 2, pin: 333 } ] }
, { deeply: [ { nested: [ { user: 2, password: "here" } ] } ] }
]
console.log(redact(data, new Set(["cc", "password", "pin"])))
[
{
"user": 1,
"cc": "*****",
"password": "*****"
},
{
"nested": [
{
"a": 1,
"pin": "*****"
},
{
"b": 2,
"pin": "*****"
}
]
},
{
"deeply": [
{
"nested": [
{
"user": 2,
"password": "*****"
}
]
}
]
}
]

How to update a property in a deeply nested array of objects of unknown size using Javascript?

I have a large array of objects that I need to recursively loop through and find the object I need to update by its uid and update the data property within that object. This array is of unknown size and shape. The following method works, but I was wondering if there was a "cleaner" way of doing this process.
The following code loops through every object / array until it finds an object with property uid, if this uid equals to the block I need to update, I set the data property of this object. Is there a better way to do this?
Traversal Method
function traverse(x) {
if (isArray(x)) {
traverseArray(x)
} else if ((typeof x === 'object') && (x !== null)) {
traverseObject(x)
} else {
}
}
function traverseArray(arr) {
arr.forEach(function (x) {
traverse(x)
})
}
function traverseObject(obj) {
for (var key in obj) {
// check if property is UID
if (key == "uid") {
// check if this uid is equal to what we are looking for
if (obj[key] == "bcd") {
// confirm it has a data property
if (obj.hasOwnProperty('data')) {
// assign new data to the data property
obj['data'] = newData;
return;
}
}
}
if (obj.hasOwnProperty(key)) {
traverse(obj[key])
}
}
}
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]'
}
// usage:
traverse(this.sections)
Sample data
this.sections = [
{
uid: "abc",
layout: {
rows: [
{
columns: [
{
blocks: [
{
uid: "bcd",
data: {
content: "how are you?"
}
}
]
},
{
blocks: [
{
uid: "cde",
data: {
content: "how are you?"
}
}
]
},
],
},
{
columns: [
{
blocks: [
{
uid: "def",
data: {
content: "how are you?"
}
}
]
}
],
}
]
}
}
]
Is there a better way to handle this? Thanks!
I would recommend starting with a generic object mapping function. It takes an object input, o, and a transformation to apply, t -
const identity = x =>
x
const mapObject = (o = {}, t = identity) =>
Object.fromEntries(Object.entries(o).map(([ k, v ]) => [ k, t(v) ]))
Then build your recursive object mapping function using your shallow function. It takes an object to transform, o, and a transformation function, t -
const recMapObject = (o = {}, t = identity) =>
mapObject // <-- using mapObject
( o
, node =>
Array.isArray(node) // <-- recur arrays
? node.map(x => recMapObject(t(x), t))
: Object(node) === node // <-- recur objects
? recMapObject(t(node), t)
: t(node)
)
Now you build the object transformation unique to your program using our recursive object mapping function. It takes the complex nested data, graph, the uid to match, and a transformation to apply to the matched node, t -
const transformAtUid = (graph = {}, uid = "", t = identity) =>
recMapObject // <-- using recMapObject
( graph
, (node = {}) =>
node.uid === uid // if uid matches,
? { ...node, data: t(node.data) } // then transform node.data,
: node // otherwise no change
)
The above step is important because it detangles the specific logic about node.uid and node.data from the rest of the generic object transformation code.
Now we call our function on the input, data, to transform nodes matching node.uid equal to "bcd" using an example transformation -
const result =
transformAtUid
( data // <-- input
, "bcd" // <-- query
, node => ({ ...node, changed: "x" }) // <-- transformation
)
console.log(result)
Output -
{
"uid": "abc",
"layout": {
"rows": [
{
"columns": [
{
"blocks": [
{
"uid": "bcd",
"data": {
"content": "how are you?",
"changed": "x" // <-- transformed
}
}
]
}
// ...
}
Expand the snippet below to verify the results in your own browser -
const identity = x =>
x
const mapObject = (o = {}, t = identity) =>
Object.fromEntries(Object.entries(o).map(([ k, v ]) => [ k, t(v) ]))
const recMapObject = (o = {}, t = identity) =>
mapObject // <-- using mapObject
( o
, node =>
Array.isArray(node) // <-- recur arrays
? node.map(x => recMapObject(t(x), t))
: Object(node) === node // <-- recur objects
? recMapObject(t(node), t)
: t(node) // <-- transform
)
const transformAtUid = (graph = {}, uid = "", t = identity) =>
recMapObject
( graph
, (node = {}) =>
node.uid === uid
? { ...node, data: t(node.data) }
: node
)
const data =
{uid:"abc",layout:{rows:[{columns:[{blocks:[{uid:"bcd",data:{content:"how are you?"}}]},{blocks:[{uid:"cde",data:{content:"how are you?"}}]},],},{columns:[{blocks:[{uid:"def",data:{content:"how are you?"}}]}],}]}}
const result =
transformAtUid
( data
, "bcd"
, node => ({ ...node, changed: "x" })
)
console.log(JSON.stringify(result, null, 2))
In your original question, I see you have an array of objects to change -
// your original
this.sections = [ {...}, {...}, ... ]
To use recMapObject and transformAtUid with an array of objects, we can use Array.prototype.map -
this.sections = this.sections.map(o => transformAtUid(o, "bcd", ...))
It can be simpler if use a queue:
var sections = [
{
uid: "abc",
layout: {
rows: [
{
columns: [
{
blocks: [
{
uid: "bcd",
data: {
content: "how are you?"
}
}
]
},
{
blocks: [
{
uid: "cde",
data: {
content: "how are you?"
}
}
]
}
]
},
{
columns: [
{
blocks: [
{
uid: "def",
data: {
content: "how are you?"
}
}
]
}
]
}
]
}
}
];
var queue = [];
function trav(ss) {
queue = queue.concat(ss);
while (queue.length > 0) {
tObj(queue.pop());
}
console.log("result", ss);
}
function tObj(obj) {
if (obj.uid === "bcd") {
obj.data = "new data";
} else {
Object.keys(obj).forEach(el => {
if (Array.isArray(obj[el])) {
queue = queue.concat(obj[el]);
} else if (typeof (obj[el]) === "object") {
tObj(obj[el]);
}
});
}
}
trav(sections);

Es6 way to convert object key value to one single object

I want to convert all data into one object,
let d = {
"Coupon_Code": "code",
"Coupon_Name": "namie",
"Coupon_Desc": 1000,
"selectedCity": [
{
"Coupon_City_Name": "xyz"
}
],
"selectedCategory": [
{
"Coupon_Category_Name": "Shopping"
}
],
"selectedCompany": [
{
"Coupon_Company_Name": "Shopper Stop"
}
],
"selectedState": [
{
"Coupon_State_Name": "abc"
}
],
"Coupon_Date": "2222-02-22",
}
i tried some methods of Object like keys , entries but dont no what to use.
Final output should be
let d = {
Coupon_Code: "code",
Coupon_Name: "namie",
Coupon_Desc: 1000,
Coupon_City_Name: "xyz",
Coupon_Category_Name: "Shopping",
Coupon_Company_Name: "Shopper Stop",
Coupon_State_Name: "abc",
Coupon_Date: "2222-02-22",
};
what's the best and optimum way to have above result using Venila Js and Es6
Reduce the entries of the original object. If the entry's value is an array merge the 1st element, if not merge the original key and value. You can merge the properties into the object using object spread:
const data = {"Coupon_Code":"code","Coupon_Name":"namie","Coupon_Desc":1000,"selectedCity":[{"Coupon_City_Name":"xyz"}],"selectedCategory":[{"Coupon_Category_Name":"Shopping"}],"selectedCompany":[{"Coupon_Company_Name":"Shopper Stop"}],"selectedState":[{"Coupon_State_Name":"abc"}],"Coupon_Date":"2222-02-22"};
const result = Object.entries(data)
.reduce((r, [k, v]) => ({
...r,
...Array.isArray(v) ? v[0] : { [k]: v }
}), {});
console.log(result);
You can use Array.reduce and Object.entries
let d = {"Coupon_Code":"code","Coupon_Name":"namie","Coupon_Desc":1000,"selectedCity":[{"Coupon_City_Name":"xyz"}],"selectedCategory":[{"Coupon_Category_Name":"Shopping"}],"selectedCompany":[{"Coupon_Company_Name":"Shopper Stop"}],"selectedState":[{"Coupon_State_Name":"abc"}],"Coupon_Date":"2222-02-22"};
d = Object.entries(d).reduce((a,[k,v]) => {
// If the value is an array, iterate over it to merge into the resultant object
if(Array.isArray(v)) Object.assign(a, ...v)
else Object.assign(a, {[k]:v}) // if it is not an array, merge into resultant object
return a;
}, {});
console.log(d);
You could take a recursive approach.
const
fn = o => Object.assign(...Object.entries(o).map(([k, v]) => Array.isArray(v) ? Object.assign(...v.map(fn)) : { [k]: v })),
d = { Coupon_Code: "code", Coupon_Name: "namie", Coupon_Desc: 1000, selectedCity: [{ Coupon_City_Name: "xyz" }], selectedCategory: [{ Coupon_Category_Name: "Shopping" }], selectedCompany: [{ Coupon_Company_Name: "Shopper Stop" }], selectedState: [{ Coupon_State_Name: "abc" }], Coupon_Date: "2222-02-22" },
result = fn(d);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A possible iterative solution is:
function flatten(obj) {
let r = {}
for (let [key, value] of Object.entries(obj)) {
if (Array.isArray(value)) {
Object.assign(r, value[0]);
} else {
Object.assign(r, {[key]: value});
}
}
return r;
}
Something like this:
const d = { Coupon_Code: "code", Coupon_Name: "namie", Coupon_Desc: 1000, selectedCity: [{ Coupon_City_Name: "xyz" }], selectedCategory: [{ Coupon_Category_Name: "Shopping" }], selectedCompany: [{ Coupon_Company_Name: "Shopper Stop" }], selectedState: [{ Coupon_State_Name: "abc" }], Coupon_Date: "2222-02-22" };
function toSingleObj(obj) {
var result = {};
Object.entries(obj).forEach(([key,value]) => {
if (Array.isArray(value)) {
Object.entries(value[0]).forEach(([k,v]) => {
result[k] = v;
});
} else {
result[key] = value;
}
});
return result;
}
console.log("Result: ", toSingleObj(d));

Iterate till the end of the nested objects in javascript

I have a nested object like this :
let obj = {
_id: {},
person: {
$search: {
lname: true
},
_id: {},
fname: {},
something:{
$search: {
fname: true
},
}
},
code: {},
$search: {
mname: true
},
vnvEmpName: {}
}
I have to retrieve all the keys inside the $search key, even if the object contains multiple occurences of $search it should return all the keys inside it which is :
lname
fname
mname
I tried this :
function search(obj, id) {
var result = "";
// iterate the object using for..in
for (var keys in obj) {
// check if the object has any property by that name
if (obj.hasOwnProperty(keys) && typeof obj[keys] === 'object') {
// if the key is not undefined get it's value
if (obj[keys][id] !== undefined) {
result = (obj[keys][id])
} else {
// else again call the same function using the new obj value
console.log("reahced")
search(obj[keys], id)
}
}
}
return result;
}
console.log(search(obj, '$search'))
But I am getting only the key(lname) which is under the first instance of the $search. Please help me out to iterate it till the end.
You can try this
let obj = {
_id: {},
person: {
$search : {
lname: true
},
_id: {},
fname: 'test',
something:{
$search: {
fname: true
},
}
},
code: {},
$search: {
mname: true
},
vnvEmpName: {}
}
var result;
function search(obj, id) {
// iterate the object using for..in
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key][id] !== undefined) {
result = (obj[key][id]);
return result;
} else {
// else again call the same function using the new obj value
console.log("reahced")
search(obj[key], id)
}
}
}
return result;
}
console.log(search(obj, 'lname'))
You could check if the key is $search and take that nested key or check the nested objects.
function getSearch(object, key) {
return Object.entries(object).reduce((r, [k, v]) => r.concat(
k === key
? Object.keys(v)[0]
: v && typeof v === 'object'
? getSearch(v, key)
: []
), []);
}
var object = { _id: {}, person: { $search: { lname: true }, _id: {}, fname: {}, something: { $search: { fname: true } } }, code: {}, $search: { mname: true }, vnvEmpName: {} };
console.log(getSearch(object, '$search'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can create an iterator function using generators and then use it for your purpose. Here is an example code:
let obj = { _id: {}, person: { $search: { lname: true }, _id: {}, fname: {}, something: { $search: { fname: true } } }, code: {}, $search: { mname: true }, vnvEmpName: {} };
//iterator function
function* iterate(obj, stack='') {
for (let property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
yield *iterate(obj[property], stack + '.' + property);
} else {
yield [stack, property, obj[property]];
}
}
}
}
//using iterator function for searching
let searchkey = "$search"
for (let value of iterate(obj)) {
if(value[0].indexOf(searchkey) !== -1)
console.log(value);
}
I hope it helps.

Categories

Resources