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.
Related
I have some attributes from a nested object that is inside the parent object but I would like to merge nested object with the parent object to be flatten.
Original object:
enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'string'
}
expected flatten object:
user: {
id: 'string',
name: 'string',
finished: 'boolean',
path: 'string'
}
You can recursively build object any number of nested objects. So, this function is not your case dependent:
var enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
var enrollment2 = {
user: {
id: 'string',
name: 'string'
},
test: {
test1: {
test2: {
val0:'val0',
test4: { //3rd level nested object for example
val1: 'val1',
val2: 'val2'
}
}
}
},
finished: 'boolean',
path: 'boolean'
}
const flat = (obj, out) => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] == 'object') {
out = flat(obj[key], out) //recursively call for nesteds
} else {
out[key] = obj[key] //direct assign for values
}
})
return out
}
console.log(flat(enrollment, {}))
console.log(flat(enrollment2, {}))
I needed something that avoids rewriting keys with the same name that were in different levels in the original object. So I wrote the following:
const flattenObject = (obj, parentKey = '') => {
if (parentKey !== '') parentKey += '.';
let flattened = {};
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(flattened, flattenObject(obj[key], parentKey + key))
} else {
flattened[parentKey + key] = obj[key]
}
})
return flattened;
}
var test = {
foo: 'bar',
some: 'thing',
father: {
son1: 'son1 value',
son2: {
grandchild: 'grandchild value',
duplicatedKey: 'note this is also used in first level',
},
},
duplicatedKey: 'note this is also used inside son2',
}
let flat = flattenObject(test);
console.log(flat);
// how to access the flattened keys:
let a = flat['father.son2.grandchild'];
console.log(a);
Also checks if the object is null, as I was having some problems with that in my usage.
Here's a quick and dirty way to flatten your object:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
var user = Object.assign(enrollment.user);
user.fineshed = enrollment.fineshed;
user.path = enrollment.path;
For a generic method with a couple of caveats of no shared key names and only flattening 1 level of depth:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
const flatten = (object) => {
let value = {};
for (var property in object) {
if (typeof object[property] === 'object') {
for (var p in object[property]) {
value[p] = object[property][p];
}
} else {
value[property] = object[property];
}
}
return value;
};
let user = flatten(enrollment);
console.log(user);
using recursion and reduce.
note that if value itself is an array containing objects, you might want add another check like !Array.isArray(value) depending on your case
function flatObj(obj) {
return Object.entries(obj).reduce(
(flatted, [key, value]) =>
typeof value == "object"
? { ...flatted, ...flatObj(value) }
: { ...flatted, [key]: value },
{}
);
}
Just want a single Object:
const enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
function propsToUser(enrollObj){
const u = {...enrollObj.user};
for(let i in enrollObj){
if(i !== 'user')u[i] = enrollObj[i];
}
return u;
}
const user = propsToUser(enrollment);
console.log(user);
Below code snippet takes nested input object like this :
{
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
and returns result flattened object like this:
{
"name": "Namig",
"surname": "Hajiyev",
"address.city": "Sumgait",
"address.country": "Azerbaijan",
"address.geo.lat": "40.5897200",
"address.geo.long": "49.6686100"
}
Here is my code :
function flattenObject(obj, newObj, prefix) {
newObj = newObj || {};
prefix = prefix || "";
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
const type = typeof obj[key];
const newKey = !!prefix ? prefix + "." + key : key;
if (type === "string") {
newObj[newKey] = obj[key];
}
else if (type === "object") {
flattenObject(obj[key], newObj, newKey);
}
}
}
return newObj;
}
var obj = {
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
console.log(flattenObject(obj));
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);
I need to replace every time value in a nested object with a momentJS element of its value.
const input = {
data: {
sub1: {
time: 1578857603218
}
sub2: {
some: 'thing,
foo: [{
value: 123,
time: 1578857603218
}]
}
}
}
Right now my code looks very ugly, as I'm doing this manually as there are specific fields with an optional time value.
if (data && data.sub2 && data.sub2.foo && data.sub2.foo[0].time) {
data.sub2.foo[0].time = moment(data.sub2.foo[0].time).toDate()
}
To do this in a more dynamic way, I see two options:
Pass something like an array/map with all optional time fields and replace them with a loop
Is there a better way to replace my if conditions to go through all relacing time fields?
Iterate through all keys
But this would not work for nested objects.
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// do stuff
}
}
If you know the key value pair, then what you show as already in use is exactly what you should use. It is O(1), very quick, and essentially a single line.
Making that dynamic will require O(n) where n is the number of key value pairs, and will require several lines of code.
const renderTime = input => {
if (Array.isArray(input)) {
input.forEach(el => renderTime(el));
}
if (typeof input === 'object' && !!input) {
Object.keys(input).forEach(k => {
if (k === 'time') input[k] = 'moment(input[k]).toDate()';
else renderTime(input[k]);
});
}
};
const input = {
data: {
sub1: {
time: 1578857603218
},
sub2: {
some: 'thing',
foo: [{
value: 123,
time: 1578857603218
}]
}
}
};
renderTime(input);
console.log(input);
Whenever you want to deal with nested objects with an undetermined level of depth, think of recursivity
const setTime = (object, time) => {
for (let prop in object) {
if (!Object.prototype.hasOwnProperty.call(object, prop)) {
continue;
}
if (typeof (object[prop]) === 'object') {
setTime(object[prop], time);
}
if (prop === 'time') {
object.time = time;
}
}
return object;
};
const input = {
data: {
sub1: {
time: 1578857603218
},
sub2: {
some: 'thing',
foo: [{
value: 123,
time: 1578857603218
}]
}
}
}
setTime(input, 666);
Try this one:) The secret is recursive loop
const input = {
data: {
sub1: {
time: 1578857603218
},
sub2: {
some: 'thing',
foo: [{
value: 123,
time: 1578857603218
}]
}
}
};
function changeTime(obj) {
obj && Object.keys(obj).forEach(key => {
if(key === 'time') {
obj.time = moment(obj.time); // you have to use moment
}
if(typeof obj[key] === 'object'){
changeTime(obj[key]);
}
});
}
changeTime(input);
console.log(input);
This won't handle the case where the nested fields are in an array, but it should work for nested objects
function replaceNestedValues(obj, targetVal, nextVal) {
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key];
if (typeof value === 'object' && value !== null) {
acc[key] = replaceNestedValues(value, targetVal, nextVal);
} else if (value === targetVal) {
acc[key] = nextVal;
} else {
acc[key] = value;
}
return acc;
}, {});
}
Example
const data = { a: { b: 10, c: null }, d: null };
replaceNestedValues(data, null, '');
// => { a: { b: 10, c: '' }, d: '' }
You can use this code to modify your property. The property can be present in deeply nested object or within array of objects.
foo(entry: any | any[]) {
if (Array.isArray(entry)) {
entry.forEach(ent => this.foo(ent));
} else if (typeof entry === 'object' && entry !== null) {
for (let [key, value] of Object.entries(entry)) {
if (typeof value === 'object' && value !== null) {
this.foo(value);
} else if (Array.isArray(value)) {
this.foo(value);
}
if (key === "time") {
entry[key] = "changed"; // modified value
}
}
return entry;
}
}
this.foo(data); // your object
I have an object like:
{
categories: {
Professional: {
active: false,
names: [
{
id: 1,
name: "Golf",
active: false
},
{
id: 2,
name: "Ultimate Frisbee",
active: false
}
]
}}
and i want update categories.Professional.active with true, into the reducer i have:
return {
...state,
categories: {
...state.categories,
Professional: {
...state.categories.Professional,
active: true
}
}
}
now i want write a function for spreadfy an object and update a single property by json path. Eg.
return deepPatch(state, 'categories.Professional.active', true);
the goal for the function deepPatch is build at runtime this structure:
return Object.assign({}, obj, {
categories: Object.assign({}, state.categories, {
Professional: Object.assign({}, state.Professional, {
active: true
})
})
});
i have tried but don't know how make a recursive spread:
function deepPatch(obj: any, path: string; value: any){
const arrayPath: string[] = path.split('.');
const currObj = null;
for (let i = 0, e = arrayPath.length; i < e; i++) {
const currPath = arrayPath[i];
currObj = obj[currPath];
currObj = Object.assign({}, currObj, ???);
}
return currObj;
}
You could get the first key and create a new object by calling the function again until no more keys are available.
function deepPatch(object, path, value) {
var [key, rest] = path.match(/^[^.]+|[^.].*$/g);
return { ...object, [key]: rest
? deepPatch(object[key], rest, value)
: value
};
}
var state = { categories: { Professional: { active: false, names: [{ id: 1, name: "Golf", active: false }, { id: 2, name: "Ultimate Frisbee", active: false }] } } },
result = deepPatch(state, 'categories.Professional.active', true);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const deepSet = function (object, path, value) {
if (typeof path === 'string') {
path = path.split('.');
}
if (path.length > 1) {
const e = path.shift();
deepSet(object[e] = Object.prototype.toString.call(object[e]) === '[object Object]' ? object[e] : {}, path, value);
} else {
object[path[0]] = value;
}
};
I'm using this function. Works for me.
I have a sanitizeStr() function that I need to run on EVERY property/subproperty that exists in an object like the one below:
const data = {
info: 'schools',
schools: [
{ name: 'Johnson Elementary', type: 'elementary' },
{ name: 'Iselin Middle School', type: 'middle' }
],
bestStudent: {
name: 'John',
grade: 'sixth'
}
};
The issue is that for every single one of these properties, they may or may not exist. Right now, I'm having to do multiple if checks for each property and manually running the function:
// Is there a better way to do this rather than what I have here:
if (data.info) {
data.info = sanitizeStr(data.info);
}
if (data.bestStudent) {
if (data.bestStudent.name) {
data.bestStudent.name = sanitizeStr(data.bestStudent.name);
}
if (data.bestStudent.grade) {
data.bestStudent.grade = sanitizeStr(data.bestStudent.grade);
}
}
if (data.schools) {
data.schools.forEach((school, i) => {
if (school.name) {
data.schools[i].name = sanitizeStr(school.name);
}
if (school.grade) {
data.schools[i].grade = sanitizeStr(school.grade);
}
});
}
If anyone knows of a cleaner/less manual way of doing this, it would be appreciated.
You could use an iterative and recursive approach for objects and call the function for non objects only.
function sanitizeStr(s) {
return '#' + s;
}
function iterAll(object) {
Object.keys(object).forEach(function (k) {
if (object[k] && typeof object[k] === 'object') {
iterAll(object[k]);
return;
}
object[k] = sanitizeStr(object[k]);
})
}
var data = { info: 'schools', schools: [{ name: 'Johnson Elementary', type: 'elementary' }, { name: 'Iselin Middle School', type: 'middle' }], bestStudent: { name: 'John', grade: 'sixth' } };
iterAll(data);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You must me looking for this
const sanitizeObject = (obj, callBack, isClone = false) => {
let tempObj = obj;
if(typeof callBack === 'function' && (typeof tempObj === 'string' || typeof tempObj === 'number')){
return callBack(tempObj)
}else if(typeof tempObj === 'object' && tempObj !== null){
tempObj = isClone ? (Array.isArray(tempObj) ? [...tempObj] : {...tempObj}) : tempObj;
Object.keys(tempObj).forEach(objKey => {
const valueOfobject = tempObj[objKey]
if(typeof valueOfobject === 'string' || typeof valueOfobject === 'number'){
tempObj[objKey] = callBack(tempObj[objKey])
}else {
tempObj[objKey] = sanitizeObject(valueOfobject, callBack, isClone)
}
})
}
return tempObj;
}
const data = {
test1: {
test2: [{
property: "any string",
property2: null
}]}
}
console.log(sanitizeObject(data, function (stringValue){
return stringValue + " apend"
}))