JSON.stringify different from direct access to object property, strongloop - javascript

I use strongloop to build my api.
On a particular route the query includes model's relations. I get an array of objects that I would like to arrange.
In this particular arranging function I face the following problem.
The function receive an object named "item" containing a "trans" field (this field is an array of another object).
this piece of code :
console.log(JSON.stringify(item, null, 2));
produces this result :
{
"id": 1,
"created": "2015-08-19T21:04:16.000Z",
"updated": null,
"authorid": 0,
"likes": 0,
"shares": 0,
"fav": 0,
"validated": 0,
"comments": 0,
"trans": [
{
"text": "Première question en français",
"questionId": 1
}
],
"answers": [
{
"id": 1,
"questionid": 1,
"questionId": 1,
"trans": [
{
"text": "q1 : reponse 1 en francais",
"answerId": 1
}
]
},
{
"id": 2,
"questionid": 1,
"questionId": 1,
"trans": [
{
"text": "q1 : reponse 2 en francais",
"answerId": 2
}
]
}
]
}
This problem is when I try to reach this part :
item.trans[0].text
console says "item.trans is undifined" and when I try this piece of code :
console.log(item.trans);
I have this result :
function (condOrRefresh, options, cb) {
if (arguments.length === 0) {
if (typeof f.value === 'function') {
return f.value(self);
} else if (self.__cachedRelations) {
return self.__cachedRelations[name];
}
} else {
if (typeof condOrRefresh === 'function'
&& options === undefined && cb === undefined) {
// customer.orders(cb)
cb = condOrRefresh;
options = {};
condOrRefresh = undefined;
} else if (typeof options === 'function' && cb === undefined) {
// customer.orders(condOrRefresh, cb);
cb = options;
options = {};
}
options = options || {}
// Check if there is a through model
// see https://github.com/strongloop/loopback/issues/1076
if (f._scope.collect &&
condOrRefresh !== null && typeof condOrRefresh === 'object') {
//extract the paging filters to the through model
['limit','offset','skip','order'].forEach(function(pagerFilter){
if(typeof(condOrRefresh[pagerFilter]) !== 'undefined'){
f._scope[pagerFilter] = condOrRefresh[pagerFilter];
delete condOrRefresh[pagerFilter];
}
});
// Adjust the include so that the condition will be applied to
// the target model
f._scope.include = {
relation: f._scope.collect,
scope: condOrRefresh
};
condOrRefresh = {};
}
return definition.related(self, f._scope, condOrRefresh, options, cb);
}
}
How can I simply access the "trans" property in this case to get the text inside ?
(Not really at easy in js)
Thanks in advance.

It's possible that your item object has implemented the toJSON function.
Pop open your browser's console and run this snippet to see an example of how this can make a difference between the stringified JSON and the actual object:
var x = {
name: "foo",
children : function() {
return [ { name: 'child 1' }, { name: 'child 2' } ];
},
toJSON: function() {
var simplified = { name: this.name, children: this.children() };
return simplified
}
};
// shows children as a simple array
console.log( JSON.stringify( x, null, 2 ) );
// {
// "name": "foo",
// "children": [
// {
// "name": "child 1"
// },
// {
// "name": "child 2"
// }
// ]
// }
// oops... not what you expected
console.log( x.children[0].name );
// Uncaught TypeError: Cannot read property 'name' of undefined
Of course, the easiest fix would be to parse the stringify result:
var y = JSON.parse( JSON.stringify( x ) );
console.log( y.children[0].name );
It's a last-case-scenario-type solution, though, since JSON.stringify is a very expensive function.

Related

Javascript Recursive functions for array of objects to update or delete from array based on id key

I'm trying to create two (2) recursive functions to "loop" over an array of objects like below. I think the two functions are "similar" but they do two different things.
Function 1 should update the object - which could be every field in the "found" object and return the "new" array of objects, so the function needs to identify the appropriate object by .id
Function 2 needs to identify the appropriate object by .id BUT to delete that object and again return the "new" array of objects.
I've tried a number of ways (below the array of objects) - but to no avail, I cannot get the new object to return.
To note even if each object has varying/different keys, there will always be an .id key -
[
{
"type":"section",
"name":"Section 1",
"hassection":[
{
"type":"section",
"id":1,
"name":"Section 1 child section 1",
"hasMenuItem":
[
{
"type":"item",
"id":2,
"name":"Item 1",
"prices":
{
"type":"price",
"price":"15.95"
},
"description":"Blah Blah..."
},{
"type":"item",
"id":3,"name":
"Item 2",
"prices":[
{
"type":"price",
"price":"24.95"
},{
"type":"price",
"price":"13.95"
}
],
"description":"Blah Blah..."
}
]
},{
"type":"section",
"id":4,
"name":"Section 1 child section 2",
"hasitem":[
{
"type":"item",
"name":"Item 3",
"prices":
{
"type":"price","price":"14.50"
},
"description":"Blah Blah..."
},{
"type":"item",
"id":5,
"name":"Item 4",
"prices":
{
"type":"price",
"price":"14.50"
},
"description":"Blah Blah..."
}
]
},
]},{
"type":"section",
"name":"Section 2",
"hassection":[
{
"type":"section",
"id":6,
"name":"Section 2 child section 1",
"hasitem":[
{
"type":"item",
"id":7,
"name":"Item 5",
"prices":
{
"type":"price",
"price":"15.95"
},
"description":"Blah Blah..."
},
{
"type":"item",
"id":8,
"name":"Item 6",
"prices":
{
"type":"price",
"price":"13.95"
},
"description":"Blah Blah..."
}
]
}
]}
]
My update function
function updateNestedObj(obj,updates) {
const updateToApply = updates.find(upd => upd.id === obj.id);
if (updateToApply) {
// UPDATE THE OBJ
}
for(let k in obj) {
if (typeof(obj[k]) === 'object') {
// LOOP THROUGH THE OBJECT
updateNestedObj(obj[k], updates);
}
}
return updateToApply
}
My Delete function
function deleteNestedObj(obj, updates) {
const updateToApply = updates.find(upd => upd.id === obj.id);
if (updateToApply) {
delete upd;
}
for(let k in obj) {
if (typeof(obj[k]) === 'object') {
deleteNestedObj(obj[k], updates);
}
}
}
I just cannot fathom out how to "work them" - thanks in advance, any help much appreciated.
generics
Let's start with a immutable update(t, func) that takes a value of any type, t, and a callable updater function, func. func transforms t per the caller's specified return value. If no value is returned, ie undefined, then update will remove that value from the tree -
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.entries(t).reduce((r, [k, v]) => {
const newValue = update(func(v), func)
if (newValue !== undefined) r[k] = newValue
return r
}, {})
case Array:
return t.flatMap(v => {
const newValue = update(func(v), func)
return newValue === undefined ? [] : [newValue]
})
default:
return t
}
}
Immutable remove(t, func) can be defined as a specialization of update -
function remove(t, func) {
return update(t, v => Boolean(func(v)) ? undefined : v)
}
special forms
The functions can be further specialized to match your particular needs. updateWithObj(t, obj) will recursively update t where a node's id matches obj.id -
function updateWithObj(t, obj) {
return update(t, v => v.id == obj.id ? {...v, ...obj} : v)
}
Likewise removeWithObj(t, obj) recursively removes from t where a node's id matches obj.id -
function removeWithObj(t, obj) {
return remove(t, v => v.id == obj.id)
}
examples
Let's create some sample data. For what it's worth, update doesn't care whether it is an array of elements, [...] or a single object, {...} -
const data = [
{id: 1, data: 50 },
{id: 2, data: {id: 3, data: "foo"}},
{id: 4, data: [{id: 5, data: 3.141}, {id: 6, data: {id: 7, data: "bar"}}]}
]
We'll start with a simple update on obj.id == 1. Note the existing data attribute remains in tact and a new ok attribute is added. All other nodes remain unchanged -
console.log(updateWithObj(data, {id: 1, ok: "βœ…"}))
[
{
"id": 1,
"data": 50, // πŸ‘πŸ½ remains unchanged
"ok": "βœ…" // πŸ‘πŸ½ updated
},
{
"id": 2,
"data": {
"id": 3,
"data": "foo"
}
},
{
"id": 4,
"data": [
{
"id": 5,
"data": 3.141
},
{
"id": 6,
"data": {
"id": 7,
"data": "bar"
}
}
]
}
]
Here we see a deeply nested update with obj.id == 7. Note the data attribute for this node is updated and a new ok attribute is added -
console.log(updateWithObj(data, {id: 7, data: 0.123, ok: "βœ…"}))
[
{
"id": 1,
"data": 50
},
{
"id": 2,
"data": {
"id": 3,
"data": "foo"
}
},
{
"id": 4,
"data": [
{
"id": 5,
"data": 3.141
},
{
"id": 6,
"data": {
"id": 7,
"data": 0.123, // πŸ‘πŸ½ updated
"ok": "βœ…" // πŸ‘πŸ½ updated
}
}
]
}
]
Now let's see removal using removeWithObj. Notice obj.id == 6 is removed along with its descendants -
console.log(removeWithObj(data, {id: 6}))
[
{
"id": 1,
"data": 50
},
{
"id": 2,
"data": {
"id": 3,
"data": "foo"
}
},
{
"id": 4,
"data": [
{
"id": 5,
"data": 3.141
}
// πŸ‘πŸ½ node removed
]
}
]
live demo
Here's a demo you can run in your own browser -
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.entries(t).reduce((r, [k, v]) => {
const newValue = update(func(v), func)
if (newValue !== undefined) r[k] = newValue
return r
}, {})
case Array:
return t.flatMap(v => {
const newValue = update(func(v), func)
return newValue === undefined ? [] : [newValue]
})
default:
return t
}
}
function remove(t, func) {
return update(t, v => Boolean(func(v)) ? undefined : v)
}
function updateWithObj(t, obj) {
return update(t, v => v.id == obj.id ? {...v, ...obj} : v)
}
function removeWithObj(t, obj) {
return remove(t, v => v.id == obj.id)
}
const data = [
{id: 1, data: 50 },
{id: 2, data: {id: 3, data: "foo"}},
{id: 4, data: [{id: 5, data: 3.141}, {id: 6, data: {id: 7, data: "bar"}}]}
]
console.log(updateWithObj(data, {id: 1, ok: "βœ…"}))
console.log(updateWithObj(data, {id: 7, data: 0.123, ok: "βœ…"}))
console.log(removeWithObj(data, {id: 6}))
.as-console-wrapper { min-height: 100%; top: 0; }
why undefined?
#Scott's comment draws attention to use of undefined as the mechanism for removal. I have always advocated for the programmer to reserve the right to use undefined for her/his particular needs. If a user gives undefined to a program, they can expect undefined behavior. For update, the undefined value is explicitly used to make a value not defined, or not there, ie remove it.
Other reasons support this choice. In most cases, an object with an explicitly undefined key behaves the same as one without the key. If the user really wants a var/key to be present but not yet set to a value, this is the perfect use of null -
const a = { foo: undefined } // foo is defined, but also not defined ??
const b = {} // does not have foo
console.log(a.foo, b.foo) // same behavior
// undefined undefined
JSON considers an undefined as "not defined" and so removes it when serializing an object -
const o = { a: undefined, b: null, c: false, d: 0, e: "" }
const j = JSON.stringify(o)
// "a" is not defined, so it's not serialized
console.log(j)
// looking for "a"? it's not defined :D
console.log(JSON.parse(j).a)
// undefined input gives undefined output. it's not defined :D
console.log(JSON.stringify(undefined))
ReScript plainly encodes None (ie "no value") as undefined in its Option module. Any Some(value) is represented as value -
// rescript
let foo = Some(1)
switch foo {
| Some(z) => Js.log(z)
| None => Js.log("no value")
}
// compiled javascript
var foo = 1;
if (foo !== undefined) {
console.log(foo);
} else {
console.log("no value");
}
explicit symbol
Maybe none of that convinces you and your fragile program still depends on having that undefined appear in the output. An explicit none sentinel can be used to signal to update that a particular value should be removed -
const none = Symbol() // βœ… removal sentinel
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.entries(t).reduce((r, [k, v]) => {
const newValue = update(func(v), func)
if (newValue !== none) r[k] = newValue // βœ…
return r
}, {})
case Array:
return t.flatMap(v => {
const newValue = update(func(v), func)
return newValue === none ? [] : [newValue] // βœ…
})
default:
return t
}
}
function remove(t, func) {
return update(t, v => Boolean(func(v)) ? none : v) // βœ…
}

Cannot convert undefined or null to object when iterating through valid JSON

I have valid JSON with this structure
const myJSONExample = {
"SubItems": [
{
"SubItems": [
{
"ItemNo": "000001"
}
],
"ItemNo": null,
"Number": null,
"price": 114.46
},
{
"SubItems": [
{
"Group": "0.4.004"
}
],
"type": null
},
{
"SubItems": [
{
"ItemNo": "000005"
},
{
"Quantity": 2
}
],
"Material": "Steel"
},
{
"Description": null
}
]
}
and just simply trying to format all number types in it, using recursive iteration.
const iterate = (obj) => {
Object.keys(obj).forEach(key => {
if(typeof(item[key]) == "number"){
item[key] = new Intl.NumberFormat("de-DE").format(item[key]) //format number for german lang.
}
if (typeof obj[key] === 'object') {
iterate(obj[key])
}
})
}
iterate(myJSONExample);
I used this functions on other JSONs, and been trying to understand for some time, why this throws TypeError: Cannot convert undefined or null to object
null is an "object" hence your issue. So add a truthy check
const iterate = (obj) => {
Object.keys(obj).forEach(key => {
const value = obj[key]
const valueType = typeof value
if (valueType === "number") {
obj[key] = new Intl.NumberFormat("de-DE").format(value)
} else if (valueType === 'object' && value) {
iterate(value)
}
})
}

Get path of an element inside a JSON object

I have an object like the following :
[
{
"uid": "aaa-aaa",
"name": "foo",
"children": []
},
{
"uid": "aaa-bbb",
"name": "bar",
"children": [
{
"uid": "aaa-bbc",
"name": "baz",
"children": []
},
{
"uid": "aaa-ccc",
"name": "fooz",
"children": [
{
"uid": "aaa-bcb",
"name": "Yeah !",
"children": []
}
]
}
]
}
]
I am trying to write a function that would take that object an uid as parameters and would return a path to the element with the uid in that object (or null if it's not found).
Something like this :
> getElementPath(bigObject, 'aaa-bcb')
[1, "children", 1, "children", 0]
or
> getElementPath(bigObject, 'aaa-bcb')
[1, 1, 0]
I know the function has to be recursive since there should be no limit in nesting levels. I have tried this but it always returns null :
function getElementPath (haystack, uid, currentPath = []) {
if (haystack.uid === uid) {
return currentPath
}
if (Array.isArray(haystack.children)) {
for (let i = 0; i < haystack.children.length; i++) {
let newPath = [...currentPath, i]
let path = getElementPath(haystack.children[i], uid, newPath)
if (path !== null) {
return path
}
}
}
return null
}
I'd use flat
Flatten the object and then loop over the Object keys until you find the one that has the appropriate value. Once you find it, the key is the path.
https://www.npmjs.com/package/flat
My (naive and quick) implementation would look like this. But what I don't love about it is that it knows to look at the "children" property, it's fine if you're data structure is well defined and doesn't change very often, the flat idea will work no matter if you change your data structure or not.
getPathForUid = (uid,obj,thisPath = []) => {
if(Array.isArray(obj)) {
return obj.reduce((acc,item,idx) => getPathForUid(uid,item,thisPath.concat(idx)),[]);
}
return obj.uid === uid ? thisPath : getPathForUid(uid,obj.children,thisPath.concat('children'));
}
Try this:
function getObject(listaJson, uid) {
var object = null,
param,
type = null;
if (listaJson.uid === uid) {
return listaJson;
}
for (param in listaJson) {
type = typeof(listaJson[param]);
if (type.toString().toLowerCase() === 'object') {
object = getObject(listaJson[param], uid);
}
if (object) {
return object;
}
}
return object;
}
console.log(getObject(json, 'aaa-aaa'));
console.log(getObject(json, 'aaa-bbb'));
console.log(getObject(json, 'aaa-bbc'));
console.log(getObject(json, 'aaa-ccc'));
console.log(getObject(json, 'aaa-bcb'));
console.log(getObject(json, 'aaa-xxx')); // null
console.log(getObject(json, 'yyy-jjj')); // null

React iterate recursively through nested objects

i'm trying to iterate recursively over nested objects to get the key and the value.
My data structure is:
{
"id": 8743077530231325861,
"name": "Name of XYZ",
"key X": 0,
"key Y": {
"details": {
"value": {
"up": 5,
"down": 3
},
"path": "xyz"
},
"key Z": {
"value": 1,
"path": "abc"
}
},
"createdTimestamp": 1554446703000
}
and my function is:
recursion = (item, keyString) => {
if(isObject(item)){
Object.keys(item).map((key) => {
return this.recursion(item[key], keyString+`.${key}`)
})
}else{
return {item, keyString}
}
}
and i call it by:
Object.keys(data).map(key =>{
console.log(this.recursion(data[key], key))
})
My problem is that keys which are objects are always undefined.
I know thats because they need to iterate an other time and the output is faster then the function. When i print out the values at the end of the recursion function instead of returning them, they are not undefined.
At the end i want for every key all the deepest values and "paths". For Example
8743077530231325861,"id"
Name of XYZ,"name"
0, "keyX"
5, "keyY.details.value.up"
...
I already tried to use await / async but i cant manage to get all values
would be nice if someone have a hint for me
You need to return the result of mapping.
const
isObject = v => v && typeof v === 'object',
recursion = (item, path = '') => isObject(item)
? Object
.keys(item)
.flatMap(k => recursion(item[k], path + (path && '.') + k))
: { item, path };
var data = { id: 8743077530231326000, name: "Name of XYZ", "key X": 0, "key Y": { details: { value: { up: 5, down: 3 }, path: "xyz" }, "key Z": { value: 1, path: "abc" } }, createdTimestamp: 1554446703000 },
result = recursion(data);
console.log(result);

Find a full object path to a given value with JavaScript

I have an array of objects with items (only have name property) and groups (with a children property, they may contain items or other groups) and I need to get a full path to needle value, so in this case it'd be myObj[2]["children"][0]["children"][1]["children"][0], plus I'm limited to quite old JS version ECMA 262 (I'm using it inside Photoshop)
var myObj = [
{
"name": "group1",
"children": [
{
"name": "group2",
"children": [
{
"name": "item0"
}]
}]
},
{
"name": "item1"
},
{
"name": "needleGroup",
"children": [
{
"name": "needleNestedGroup",
"children": [
{
"name": "item3"
},
{
"name": "needleNestedDeeperGroup",
"children": [
{
"name": "needle"
}]
}]
}]
}];
My first idea was to transform object to array or arrays so it'd be easier to process, so my object became
[
[
[
"item0"
]
],
"item1",
[
[
"item3",
[
"needle"
]
]
]
];
But.. that's it. I can't figure out hot to track down only the indexes I need. Could you please point out a correct direction.
Use a recursive function to look for the item you want. Once the function find it, it will return an array. Each step back of the recursion will unshift the object key of this step:
function find(obj, item) {
for(var key in obj) { // for each key in obj (obj is either an object or an array)
if(obj[key] && typeof obj[key] === "object") { // if the current property (value of obj[key]) is also an object/array
var result = find(obj[key], item); // try finding item in that object
if(result) { // if we find it
result.unshift(key); // we shift the current key to the path array (result will be an array of keys)
return result; // and we return it to our caller
}
} else if(obj[key] === item) { // otherwise (if obj[key] is not an object or array) we check if it is the item we're looking for
return [key]; // if it is then we return the path array (this is where the path array get constructed)
}
}
}
The output of this function will be an array of keys leading to item. You can easily transform it to the format in the question:
function findFormatted(obj, item) {
var path = find(obj, item); // first let's get the path array to item (if it exists)
if(path == null) { // if it doesn't exist
return ""; // return something to signal its inexistance
}
return 'myObj["' + path.join('"]["') + '"]'; // otherwise format the path array into a string and return it
}
Example:
function find(obj, item) {
for(var key in obj) {
if(obj[key] && typeof obj[key] === "object") {
var result = find(obj[key], item);
if(result) {
result.unshift(key);
return result;
}
} else if(obj[key] === item) {
return [key];
}
}
}
function findFormatted(obj, item) {
var path = find(obj, item);
if(path == null) {
return "";
}
return 'myObj["' + path.join('"]["') + '"]';
}
var myObj = [{"name":"group1","children":[{"name":"group2","children":[{"name":"item0"}]}]},{"name":"item1"},{"name":"needleGroup","children":[{"name":"needleNestedGroup","children":[{"name":"item3"},{"name":"needleNestedDeeperGroup","children":[{"name":"needle"}]}]}]}];
console.log("find(myObj, \"needle\"): " + JSON.stringify(find(myObj, "needle")));
console.log("findFormatted(myObj, \"needle\"): " + findFormatted(myObj, "needle"));
Note: The indexes for the arrays are also formatted as strings, but that won't be a problem as someArray["2"] is equivalent to someArray[2].
I've created something you might use. The code below returns an Array of paths to keys, values, objects you are looking for.
See snippet and example to see what you can do.
To make it work you have to pass key and/or value you want to find in element and element which is an Array or Object.
It's written in newer JS standard but it shouldn't be a problem to compile it to older standard.
function findKeyValuePairsPath(keyToFind, valueToFind, element) {
if ((keyToFind === undefined || keyToFind === null) &&
(valueToFind === undefined || valueToFind === null)) {
console.error('You have to pass key and/or value to find in element!');
return [];
}
const parsedElement = JSON.parse(JSON.stringify(element));
const paths = [];
if (this.isObject(parsedElement) || this.isArray(parsedElement)) {
checkObjOrArr(parsedElement, keyToFind, valueToFind, 'baseElement', paths);
} else {
console.error('Element must be an Object or Array type!', parsedElement);
}
console.warn('Main element', parsedElement);
return paths;
}
function isObject(elem) {
return elem && typeof elem === 'object' && elem.constructor === Object;
}
function isArray(elem) {
return Array.isArray(elem);
}
function checkObj(obj, keyToFind, valueToFind, path, paths) {
Object.entries(obj).forEach(([key, value]) => {
if (!keyToFind && valueToFind === value) {
// we are looking for value only
paths.push(`${path}.${key}`);
} else if (!valueToFind && keyToFind === key) {
// we are looking for key only
paths.push(path);
} else if (key === keyToFind && value === valueToFind) {
// we ale looking for key: value pair
paths.push(path);
}
checkObjOrArr(value, keyToFind, valueToFind, `${path}.${key}`, paths);
});
}
function checkArr(array, keyToFind, valueToFind, path, paths) {
array.forEach((elem, i) => {
if (!keyToFind && valueToFind === elem) {
// we are looking for value only
paths.push(`${path}[${i}]`);
}
checkObjOrArr(elem, keyToFind, valueToFind, `${path}[${i}]`, paths);
});
}
function checkObjOrArr(elem, keyToFind, valueToFind, path, paths) {
if (this.isObject(elem)) {
checkObj(elem, keyToFind, valueToFind, path, paths);
} else if (this.isArray(elem)) {
checkArr(elem, keyToFind, valueToFind, path, paths);
}
}
const example = [
{
exampleArr: ['lol', 'test', 'rotfl', 'yolo'],
key: 'lol',
},
{
exampleArr: [],
key: 'lol',
},
{
anotherKey: {
nestedKey: {
anotherArr: ['yolo'],
},
anotherNestedKey: 'yolo',
},
emptyKey: null,
key: 'yolo',
},
];
console.log(findKeyValuePairsPath('key', 'lol', example)); // ["baseElement[0]", "baseElement[1]"]
console.log(findKeyValuePairsPath(null, 'yolo', example)); // ["baseElement[0].exampleArr[3]", "baseElement[2].anotherKey.nestedKey.anotherArr[0]", "baseElement[2].anotherKey.anotherNestedKey", "baseElement[2].key"]
console.log(findKeyValuePairsPath('anotherNestedKey', null, example)); //["baseElement[2].anotherKey"]
I came accross this issue and took the chance to create find-object-paths, which solves this problem: Finding paths in an object by either keys, values or key/value combinations.
NPM: find-object-paths
Github: getPaths
Example object:
{
"company": {
"name": "ACME INC",
"address": "1st Street, Toontown, TT",
"founded": "December 31st 1969",
"hasStocks": true,
"numberOfEmployees": 2,
"numberOfActors": 3
},
"employees": [
{
"employeeNumber": 1,
"name": "Hugo Boss",
"age": 65,
"isCEO": true
},
{
"employeeNumber": 2,
"name": "Herbert Assistant",
"age": 32,
"isCEO": false
}
],
"actors": [
{
"actorId": 1,
"name": "Bugs Bunny",
"retired": false,
"playedIn": [
{
"movie": "Who Framed Roger Rabbit",
"started": 1988
},
{
"movie": "Space Jam",
"started": 1996
},
{
"movie": "Looney Tunes: Back in Action",
"started": 2003
}
]
},
{
"actorId": 2,
"name": "PepΓ© le Pew",
"retired": true,
"playedIn": [
{
"movie": "For Scent-imental Reasons",
"started": 1949
}
]
},
{
"actorId": 3,
"name": "Marvin the Martian",
"retired": true,
"playedIn": [
{
"movie": "Marvin the Martian in the Third Dimension",
"started": 1996
},
{
"movie": "Duck Dodgers and the Return of the 24Β½th Century",
"started": 1980
},
{
"movie": "Hare-Way to the Stars",
"started": 1958
}
]
},
{
"actorId": 4,
"name": "Yosemite Sam",
"retired": false,
"playedIn": [
{
"movie": "Who Framed Roger Rabbit",
"started": 1988
},
{
"movie": "Space Jam",
"started": 1996
},
{
"movie": "Looney Tunes: Back in Action",
"started": 2003
}
]
}
],
"distributionChannels": [
"Celluloyd",
[
"VHS",
"Betamax",
"DVD",
"Blueray"
],
[
"channel",
12,
true
]
]
}
So, the basic usage could be:
import { findObjectPaths } from 'findObjectPaths';
class TestMe {
static async main() {
let acmeInc = {};
rawFileContent = fs.readFileSync(p.resolve(__dirname, 'acmeInc.json'), 'utf-8');
acmeInc = JSON.parse(rawFileContent);
let path = findObjectPaths(acmeInc, {key: 'founded'});
// company.founded
path = findObjectPaths(acmeInc, {value: 'December 31st 1969'});
// company.founded
const allPaths: string[] = findObjectPaths(acmeInc, {key: 'actorId'}) as string[];
/* [ 'actors[0].actorId',
'actors[1].actorId',
'actors[2].actorId',
'actors[3].actorId' ]
*/
const ceoPath = findObjectPaths(acmeInc, {key: 'isCEO', value: true});
// employees[0].isCEO
}
}
TestMe.main();
See the full documentation here: https://github.com/maugenst/getPaths#readme
BR
Assuming that you have a nested and repetitive pattern of objects in your data-set, the following solution would do the trick for you.
const nodePath = { value: [] };
function findTreeNodeAndPath(
targetNodeKey,
targetNodeValue,
nodeChildrenKey,
tree
) {
if (tree[targetNodeKey] === targetNodeValue) {
nodePath.value.push(tree);
return;
}
if (tree[nodeChildrenKey].length > 0) {
tree[nodeChildrenKey].forEach(children => {
if (nodePath.value.length < 1) {
findTreeNodeAndPath(
targetNodeKey,
targetNodeValue,
nodeChildrenKey,
children
);
}
});
} else if (tree[nodeChildrenKey].length === 0) {
return;
}
if (nodePath.value.length > 0) {
nodePath.value.push(tree);
}
}
const exampleData = {
name: "Root",
children: [
{
name: "A2",
children: [
{
name: "AC1",
children: [
{
name: "ACE1",
children: []
}
]
},
{
name: "AC2",
children: [
{
name: "ACF1",
children: []
},
{
name: "ACF2",
children: [
{
name: "ACFG1",
children: []
}
]
},
{
name: "ACF3",
children: [
{
name: "ACFH1",
children: []
}
]
}
]
}
]
}
]
};
findTreeNodeAndPath("name", "ACFG1", "children", exampleData);
console.log(nodePath.value)
The recursive part of the code will iterate on the children of the current node. The existing strategy here is depending on the nodePath.value having at least one element, which indicates that it found the targetted node. Later on, it'll skip the remaining nodes and would break out of recursion.
The nodePath.value variable will give you the node-to-root path.

Categories