I'm dealing with the following JavaScript object:
{
"gender": "man",
"jobinfo": {
"type": "teacher"
},
"children": [
{
"name": "Daniel",
"age": 12,
"pets": [
{
"type": "cat",
"name": "Willy",
"age": 2
},
{
"type": "dog",
"name": "Jimmie",
"age": 5
}
]
}
]
}
I want to print out each of the paths (keys and array indices) within the object, including the parents (i.e. children should be printed as should everything in it).
gender
jobinfo,
jobinfo.type,
children,
children.0.name,
children.0.age,
children.0.pets,
children.0.pets.0.type,
children.0.pets.0.name,
children.0.pets.0.age,
children.0.pets.1.type,
children.0.pets.1.name,
children.0.pets.1.age
I tried this code with modifications but it didnt work for me:
function getPath(object) {
for (key in object) {
if (Array.isArray(object[key]) === true) {
console.log(key)
getPath(object[key])
} else if (typeof object[key] === 'object') {
console.log(key)
getPath(object[key])
} else {
console.log(key)
}
}
}
It's printing all keys in the JSON, but I'm struggling with joining the paths, especially in nested elements.
This works:
const data = {"gender":"man","jobinfo":{"type":"teacher"},"children":[{"name":"Daniel","age":12,"pets":[{"type":"cat","name":"Willy","age":2},{"type":"dog","name":"Jimmie","age":5}]}]};
const getPath = (currPath, item) => {
console.log(currPath);
if (Array.isArray(item)) {
item.forEach((el, idx) => getPath(`${currPath}.${idx}`, el));
} else if (typeof item == "object") {
Object.entries(item).forEach(([key, value]) => {
getPath(`${currPath}.${key}`, value);
});
}
};
Object.entries(data).forEach(([key, value]) => {
getPath(key, value);
});
Basically I just loop through each of the entries in the initial object, using the key as the path at that stage and checking if the value is an object or array. I always print the path within the function (to provide the outer layers you want) and then I recurse over the inner layers, adding to the path as needed.
In this version array keys that are consisted of numbers like 'children.0' and so on handled and this gives the result exactly what you wanted:
const json = {"gender":"man","jobinfo":{"type":"teacher"},"children":[{"name":"Daniel","age":12,"pets":[{"type":"cat","name":"Willy","age":2},{"type":"dog","name":"Jimmie","age":5}]}]};
function getPath(object, previousPath) {
for (key in object) {
let currentPath = previousPath ? `${previousPath}.${key}` : key
if (Array.isArray(object[key])) {
console.log(currentPath)
getPath(object[key], currentPath)
} else if (typeof object[key] === 'object') {
if (!Array.isArray(object)) { // skipping logging array keys like children.0
console.log(currentPath)
}
getPath(object[key], currentPath)
} else {
console.log(currentPath)
}
}
}
getPath(json)
Related
I have this object, and I want to get the 'user' key's value, I usually can use a for loop to get the value, what if that key I am searching for is nested in another object? How can I write a recursive JS function that automatically gets the key/value I am looking for?
"http://converge.amwell.com/claims": {
"ehr": "DEMO-CVSHI-EPIC-1",
"connection": "con_9ydmEmrkyPulwlwU",
"role": "PATIENT",
"tenant": "CVSHI",
"user": "b3e9c94d-18e4-4743-9f4e-fe7e251e7b17",
"patient": "b3e9c94d-18e4-4743-9f4e-fe7e251e7b17",
"id_type": "INTERNAL_FHIR"
},
"iss": "https://amwellstage.amwell-dev.auth0.com/",
"sub": "oauth2|CVSHI-PATIENT|sick-cost#16xkn1tf.mailosaur.net",
"aud": [
"https://amwellstage.amwell-dev.auth0.com/api/v2/",
"https://amwellstage.amwell-dev.auth0.com/userinfo"
],
"iat": 1665480351,
"exp": 1665481251,
"azp": "cKftX4PfLpO0gFULuQiyDiX57BtTbpwg",
"scope": "openid profile email offline_access",
"org_id": "org_HMAePTi76uUGOfdG"
}```
```for (const [key, value] of Object.entries(obj)) {
// if (key === 'user') {
// user = value
// }
// if (value instanceof Object) {
// for (const [subKey, subValue] of Object.entries(value)) {
// if (subKey === 'user') {
// user = subValue
// }
// }
// }
// }```
You can use this:
function getPropFromObj(obj, prop) {
let valueToFindByKey;
if (!Array.isArray(obj) && obj !== null && typeof obj === "object") {
if (obj.hasOwnProperty(prop)) {
valueToFindByKey = obj[prop];
console.log(valueToFindByKey);
} else {
let i;
for (i = 0; i < Object.keys(obj).length; i++) {
getPropFromObj(obj[Object.keys(obj)[i]], prop);
}
}
}
return null;
}
getPropFromObj(obj, "user");
The above getPropFromObj will do a deep search on the object and pull the value defined by the given key.
Hope it helps.
I am converting the nested data object to the arrays, for the UI Library to show the relationship between the data.
Original
// assume that all object key is unique
{
"top":{
"test":{
"hello":"123"
},
"test2":{
"bye":"123"
"other":{
...
...
...
}
}
}
}
Preferred Result
[
{
id:"top",
parent: null,
},
{
id:"test",
parent: "top",
},
{
id:"hello",
parent: "test",
},
{
id:"test2",
parent: "top",
},
]
To do this, I write the code like this:
const test = []
const iterate = (obj, parent = null) => {
Object.keys(obj).forEach(key => {
const id = typeof obj[key] === 'object' ? key : obj[key]
const loopObj = {
id,
parent
}
test.push(loopObj)
if (typeof obj[key] === 'object') {
iterate(obj[key], id)
}
})
}
iterate(data)
console.log(test) // Done!!
It works.
However, I miss one important things, the library need the layers from the original data, to determine the type/ what function to do.
// The key name maybe duplicated in different layer
{
"top":{ // Layer 1
"test":{ // Layer 2
"hello":"123", // Layer 3
"test":"123" // Layer 3
// Maybe many many layers...
}
}
}
[
{
id:"top",
display:"0-top",
parent: null,
layer: 0
},
{
id: "1-top-test", // To prevent duplicated id, `${layer}-${parentDisplay}-${display}`
display:"test",
parent: "0-top",
parentDisplay: "top",
layer: 1
},
{
id: "3-test-test", // To prevent duplicated id,`${layer}-${parentDisplay}-${display}`
display:"test",
parent: "2-top-test",
parentDisplay: "test",
layer: 3
}
]
Editing the display or id format is very simple, just edit the function and add the field, but I don't know how to get the layer easily.
I tried to add the let count = 0 outside and do count++ when iterate function called.But I realized that it hit when the object detected, no by layers.
The original data may be very big,
So I think editing the original data structure or searching the parent id in the test[] every loop may be not a good solution.
Is there any solution to do this?
Just add the current depth as an argument that gets passed down on every recursive call (as well as the parent name).
const input = {
"top":{
"test":{
"hello":"123"
},
"test2":{
"bye":"123",
"other":{
}
}
}
};
const iterate = (obj, result = [], layer = 0, parentId = null, parentDisplay = '') => {
Object.entries(obj).forEach(([key, value]) => {
const id = `${layer}-${key}`;
result.push({
id,
display: key,
parentId,
parentDisplay,
layer,
});
if (typeof value === 'object') {
iterate(value, result, layer + 1, id, key);
}
});
return result;
}
console.log(iterate(input));
That said, your desired approach can still produce duplicate entries, if there exist two objects at the same level, with different grandparent objects, but whose parent objects use the same key, eg:
const input = {
"top1":{
"test":{
"hello":"123"
},
},
"top2": {
"test": {
"hello":"123"
}
}
};
const input = {
"top1":{
"test":{
"hello":"123"
},
},
"top2": {
"test": {
"hello":"123"
}
}
};
const iterate = (obj, result = [], layer = 0, parentId = null, parentDisplay = '') => {
Object.entries(obj).forEach(([key, value]) => {
const id = `${layer}-${key}`;
result.push({
id,
display: key,
parentId,
parentDisplay,
layer,
});
if (typeof value === 'object') {
iterate(value, result, layer + 1, id, key);
}
});
return result;
}
console.log(iterate(input));
If that's a problem, consider passing down the entire accessor string needed to access the property - eg top1.test.hello and top2.test.hello, which is guaranteed to be unique.
const input = {
"top1":{
"test":{
"hello":"123"
},
},
"top2": {
"test": {
"hello":"123"
}
}
};
const iterate = (obj, result = [], parentAccessor = '') => {
Object.entries(obj).forEach(([key, value]) => {
const accessor = `${parentAccessor}${parentAccessor ? '.' : ''}${key}`;
result.push({
id: key,
accessor,
});
if (typeof value === 'object') {
iterate(value, result, accessor);
}
});
return result;
}
console.log(iterate(input));
I have JSON with following structure:
{
"name":"Scorpiones",
"children":[
{
"name":"Parabuthus",
"children":[
{
"name":"Parabuthus schlechteri"
},
{
"name":"Parabuthus granulatus"
}
]
},
{
"name":"Buthidae",
"children":[
{
"name":"Androctonus",
"children":[
{
"name":"Androctonus crassicauda"
},
{
"name":"Androctonus bicolor"
}
]
}
]
}
]
}
It is required to sort the elements by the name field at each level of the hierarchy. How can this be done with the help of JS?
You can use Object.keys which returns an array contains all object keys, then you can simply sort that array.
const getSortedKeys = (obj) => {
return Object.keys(obj).sort();
}
And to go throw all your object nested keys you can use recursion:
const trav = (obj) => {
if(obj instanceof Array) {
// go inside each object in the array
obj.forEach(item => trav(item))
} else if(typeof obj === "object") {
// sort object keys
let keys = getSortedKeys(obj);
// do whatevery you want...
// go the the next levels
keys.forEach(key => trav(obj[key]))
}
}
I've created an update object API that receives new update data of an existing document.
Let's say, I have two objects oldData and newData
oldData = {
me:{
name:{
short:'will'
long:'william'
}
},
friends:[
{
id: 1,
name:{
short:'mike'
long:'michael'
},games:[]
},
{
id: 2,
name:{
short:'fred'
long:'freddy'
}
},
],
favoriteGames:[
'gta',
'animal crossing',
'mortal kombat'
],
favoriteFood:['bacon'],
}
newData = {
me:{
name:{
long:'willy'
longer:'william'
}
},
friends:[
{
id:3,
name:{
short:'max',
long:'maxwell'
}
},
{
id:1,
name:{
short:'mic',
}
},
],
favoriteGames:[
'tekken'
]
}
calling applyUpdate(oldData, newData)should return
result = {
me:{
name:{
short:'will',
long:'willy',
longer:'william'
}
},
friends:[
{
id:3,
name:{
short:'max',
long:'maxwell'
}
},
{
id: 1,
name:{
short:'mic'
long:'michael'
},games:[]
}
],
favoriteGames:[
'tekken'
],
favoriteFood:['bacon'],
}
Basically, the rules for merging are:
If a key in an object is specified with new data, it overrides the
value of the same key in old data.
If a key is not specified, the
value of the same key in old data is kept.
If the value of a key in new data is an array of objects:
Each object must be merged BY id with elements in the array of the same key in old data.
Elements not included in the arrays of newData are removed from the result.
The order of elements in the arrays of newData should be preserved.
Merging must be done deeply, since nested arrays and objects of unspecified depth should be possible.
I've actually successfully implemented this with a horrendously long and ugly recursive function. But am worried about performance and readability issues. I am open to suggestions using lodash or underscore.
Thanks!
Try this. It's hard to write this in a readable way.
function customizer(oldProp, newProp) {
if (Array.isArray(newProp)) {
// check if `newProp` is an array of objects which has property `id`
if (typeof newProp[0] === 'object' && newProp[0].hasOwnProperty('id')) {
if (!Array.isArray(oldProp)) {
return newProp;
}
// merge objects of 2 arrays in `oldProp` and `newProp`
const mergedArr = [];
for (const objNewArr of newProp) {
const objOldArr = oldProp.find(o => o.id === objNewArr.id);
if (objOldArr) {
mergedArr.push(_.merge(objOldArr, objNewArr));
} else {
mergedArr.push(objNewArr);
}
}
return mergedArr;
}
return newProp;
}
if (typeof newProp === 'object') {
return _.merge(oldProp, newProp);
}
return newProp;
}
_.mergeWith(oldData, newData, customizer); // returns the merged object
Here's what worked. Thanks Duc.
function customizer(oldProp, newProp) {
if (Array.isArray(newProp)) {
if (typeof newProp[0] === 'object') {
const mergedArr = [];
for (const objNewArr of newProp) {
const objOldArr = oldProp.find(o => o._id === objNewArr._id);
if (objOldArr) {
mergedArr.push(_.mergeWith(_.cloneDeep(objOldArr), _.cloneDeep(objNewArr), customizer));
} else {
mergedArr.push(objNewArr);
}
}
return mergedArr;
}else{
return newProp;
}
}else if (typeof newProp === 'object') {
return _.merge(oldProp, newProp);
}else{
return undefined;
}
}
var result = _.mergeWith(_.cloneDeep(oldData), _.cloneDeep(newData), customizer); // returns the merged object
Consider the object:
{
"status": {
"affects": {
"date_of_death": "date_of_death",
"cause_of_death": {
"affects": {
"other_cause_of_death": "other_cause_of_death"
},
"value": "Other"
}
},
"value": "Deceased"
}
}
What I want to do is loop over this and add {hide: true}. But there are some rules:
If the key does not have object as a value, take the value and make it into {hide: true}
If it has affects, like cause of death does, add hide: true after value.
So the resulting object should be:
{
"status": {
"affects": {
"date_of_death": {hide: true},
"cause_of_death": {
"affects": {
"other_cause_of_death": {hide: true}
},
"value": "Other",
"hide": true,
}
},
"value": "Deceased"
}
}
now this might seem easy, until you get something like this:
{
"comorbidities": [
{
"affects": {
"malignancy_type": {
"affects": {
"malignancy_type_other": "malignancy_type_other"
},
"value": "Other"
}
},
"value": "malignancy"
},
{
"affects": {
"heritable_syndrome_type": "heritable_syndrome_type"
},
"value": "heritable syndrome"
}
]
}
The exact same thing should happen here. Accept notice how its an array, This should do it recursively, deep diving into the affects, until no more can be found.
I have gotten this far:
export const createHiddenFields = (dependencies) => {
let hiddenFields = {};
if (dependencies === null) {
return hiddenFields;
}
for (const key in dependencies) {
if (dependencies[key].hasOwnProperty('affects')) {
hiddenFields[key] =
} else {
}
}
return hiddenFields;
}
But I am lost as to how to finish this. I know I want it recursive, but I also want it fast, but still legible.
Any Ideas?
You're probably looking for something like
export function createHiddenFields(dependencies) {
if (Array.isArray(dependencies)) {
return dependencies.map(createHiddenFields);
}
const hiddenFields = {
hide: true,
};
if (typeof dependencies == 'object' && dependencies !== null) {
for (const key in dependencies) {
if (key == 'affects') {
hiddenFields.affects = createHiddenFields(dependencies.affects);
} else {
hiddenFields[key] = dependencies[key];
}
}
}
return hiddenFields;
}
You can do this using recursion and for...in loop and check if the value of the property is object then call the function again or if its not then add the value.
const data = {"comorbidities":[{"affects":{"malignancy_type":{"affects":{"malignancy_type_other":"malignancy_type_other"},"value":"Other"}},"value":"malignancy"},{"affects":{"heritable_syndrome_type":"heritable_syndrome_type"},"value":"heritable syndrome"}]}
function update(obj, val) {
for (let i in obj) {
if (typeof obj[i] == 'object') update(obj[i], val);
else if ('affects' in obj) Object.assign(obj, val);
else obj[i] = val
}
}
update(data, {hide: true});
console.log(data)
I decided to post a late answer anyway, because I see some possible flaws and gotchas in the other answers. Specifically:
for ... in is dangerous because it may loop over any iterable Object.prototype propertyes, which sometimes exist thanks to irresponsible developers. In general it loops over all properties including inherited ones
for ... in is not supposed to be used for looping over an array
It may be unwise to reuse the {hide: true} object. Should you later wish to alter the hide property for some entries in the tree, it would affect all that share the same object reference
So I'd instead write a longer but safer method:
function createHiddenFields(obj) {
// ignore nulls
if(obj == null) {
return;
}
// For an array, just do the update for each object in array
else if(obj instanceof Array) {
for(const subObj of obj) {
createHiddenFields(subObj);
}
}
// For object, do the hidden field thing
else if(typeof obj == "object") {
// Replace all string values with hiddenVal
for(const valueName of Object.getOwnPropertyNames(obj)) {
// Do not override value nodes
if(valueName == "value") {
continue;
}
else {
// Process sub-objects
if(typeof obj[valueName] == "object") {
createHiddenFields(obj[valueName]);
}
// Replace primitive property values
else {
obj[valueName] = { hide: true };
}
}
}
// add hidden field for affects
if(typeof obj.affects == "object") {
obj.hide = true;
}
}
}
demo