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.
Related
I need to return the value of a specific property of nested objects with ES5 syntax. Every object can have its own structure, so the needed property can be on different levels/places. E.g. -> I have three different objects and the value of property "source" is needed:
first_data has that property in list.details.source
second_data has that property in list.details.products[0]._new.source
third_data does not have this property, therefore it should return false
So how can I return the value of the specific property with consideration, that it can be on any position in object?
var first_data = {
area: "South",
code: "S265",
info: {
category: "enhanced",
label: "AB | 27AS53",
variable: "VR"
},
list: {
area: "Mid",
details: [
{
source: "Spain",
totals: 12,
products: [
{
name: "ABC",
brand: "Nobrand",
id: "111",
category: "Men",
}
]
}
]
},
time: 1654775446138
};
var second_data = {
area: "South",
code: "S265",
info: {
category: "enhanced",
label: "AB | 27AS53",
variable: "VR"
},
list: {
area: "Mid",
details: [
{
products: [
{
name: "ABC",
brand: "Nobrand",
id: "111",
category: "Men",
_new: {
source: "Spain",
totals: 12
}
}
]
}
]
},
time: 1654775446138
};
var third_data = {
area: "South",
code: "S265",
info: {
category: "enhanced",
label: "AB | 27AS53",
variable: "VR"
},
list: {
area: "Mid",
details: [
{
products: [
{
name: "ABC",
brand: "Nobrand",
id: "111",
category: "Men"
}
]
}
]
},
time: 1654775446138
};
I first tried to solve it with ES6, so that I can rewrite it in a second step into ES5. Here is what I have so far. The first problem is that here I am getting a false, but the property exists.
var propertyExists = function (obj, key) {
if(obj === null || obj === undefined) {
return false;
}
for(const k of Object.keys(obj)) {
if(k === key) {
return obj[k]
}
else {
const val = obj[k];
if(typeof val === 'object') {
if(propertyExists(val, key) === true) {
return true;
}
}
}
}
return false;
}
propertyExists(first_data, 'source')
Your propertyExists function didn't work because it returned the value of source but it later checked if the value is equal to true (as described by Felix Kling in a comment above).
Here's my implementation (originally in ES6 but I used a typescript compiler with target set to ES5):
var findProp = function (obj, prop) {
if (typeof obj != "object") {
return false;
}
if (obj.hasOwnProperty(prop)) {
return obj[prop];
}
for (var _i = 0, _a = Object.keys(obj); _i < _a.length; _i++) {
var p = _a[_i];
if (typeof obj[p] === "object") {
var t = findProp(obj[p], prop);
if (t) {
return t;
}
}
}
return false;
};
Note: It might be faster to detect which object structure it is and retrieve the value because you would then know where it is.
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) // β
}
I have a Json data that I want to have in a different format.
My original json data is:
{
"info": {
"file1": {
"book1": {
"lines": {
"102:0": [
"102:0"
],
"105:4": [
"106:4"
],
"106:4": [
"107:1",
"108:1"
]
}
}
}
}
}
And I want to map it as following:
{
"name": "main",
"children": [
{
"name": "file1",
"children": [
{
"name": "book1",
"group": "1",
"lines": [
"102",
"102"
],
[
"105",
"106"
],
[
"106",
"107",
"108"
]
}
],
"group": 1,
}
],
"group": 0
}
But the number of books and number of files will be more. Here in the lines the 1st part (before the :) inside the "" is taken ("106:4" becomes "106"). The number from the key goes 1st and then the number(s) from the value goes and make a list (["106", "107", "108"]). The group information is new and it depends on parent-child information. 1st parent is group 0 and so on. The first name ("main") is also user defined.
I tried the following code so far:
function build(data) {
return Object.entries(data).reduce((r, [key, value], idx) => {
//const obj = {}
const obj = {
name: 'main',
children: [],
group: 0,
lines: []
}
if (key !== 'reduced control flow') {
obj.name = key;
obj.children = build(value)
if(!(key.includes(":")))
obj.group = idx + 1;
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
The group information is not generating correctly. I am trying to figure out that how to get the correct group information. I would really appreciate if you can help me to figure it out.
You could use reduce method and create recursive function to build the nested structure.
const data = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}}
function build(data) {
return Object.entries(data).reduce((r, [key, value]) => {
const obj = {}
if (key !== 'lines') {
obj.name = key;
obj.children = build(value)
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
I couldn't understand the logic behind group property, so you might need to add more info for that, but for the rest, you can try these 2 functions that recursively transform the object into what you are trying to get.
var a = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}};
var transform = function (o) {
return Object.keys(o)
.map((k) => {
return {"name": k, "children": (k === "lines" ? parseLines(o[k]) : transform(o[k])) }
}
)
}
var parseLines = function (lines) {
return Object.keys(lines)
.map(v => [v.split(':')[0], ...(lines[v].map(l => l.split(":")[0]))])
}
console.log(JSON.stringify(transform(a)[0], null, 2));
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
I have the following valid JSON. It describes a tree structure:
{
"items": [
{
"id": "d1"
},
{
"id": "2",
"children": [
{
"id": "3"
},
{
"id": "4"
},
{
"id": "5",
"children": [
{
"id": "6"
},
{
"id": "7",
"children": [
{
"id": "8"
},
{
"id": "9"
}
]
},
{
"id": "10"
}
]
},
{
"id": "11"
},
{
"id": "12"
}
]
},
{
"id": "13"
},
{
"id": "14"
}
]
}
I need to be able to get any of the "items" by id and any of the child items. For example. Initially I tried grep:
var returnedData = $.grep(obj.items, function(element, index){return element.id == "2";
});
This worked great for item with id==2 but fails completely when I try to obtain element.id=="7"
Any assistance would be appreciated. Thanks in advance.
You can make a recursive function to search in the data:
function find(source, id)
{
for (key in source)
{
var item = source[key];
if (item.id == id)
return item;
// Item not returned yet. Search its children by recursive call.
if (item.children)
{
var subresult = find(item.children, id);
// If the item was found in the subchildren, return it.
if (subresult)
return subresult;
}
}
// Nothing found yet? return null.
return null;
}
// In the root object, the array of items is called 'items', so we pass in
// data.items to look into. The root object itself doesn't seem to have an id anyway.
var result = find(data.items, 7);
// Show the name of item 7, if it had one...
alert(result.name);
Demo: http://jsfiddle.net/rj26H/
In this function I just looped over the object, so its a bit more verbose. You could probably also use $.grep to do the searching and make the code a bit smaller. Anyway, the trick is to search all children if the item is not found on the main level. Apparently grep doesn't work in a recursive fashion.
Try this:
var id = 7;
var data = {"items": [{"id": "d1"},{"id": "2","children": [{"id": "3"},{"id": "7"},{"id": "11"},{"id": "12"}]}]};
function search(values) {
$.each(values, function(i, v) {
if (v.id == id) {
console.log('found', v);
return false;
}
if (v.children) {
search(v.children);
}
});
}
search(data.items);
Demo Link
I know this have been already answered, but I wanted to show how you could leverage the new the new JavaScript 1.7 features to solve this. Please note that the same approach could have been used without support for generators, but the code would have been longer.
//Returns an iterator that knows how to walk a tree
function treeIterator(root, childGetter, childCountGetter) {
let stack = [root], node;
while (node = stack.pop()) {
yield node;
for (let i = childCountGetter(node); i--;) stack.push(childGetter(node, i));
}
}
//Our custom search function
function findNodeById(tree, id) {
let it = treeIterator(tree,
function (node, i) { return node.children[i]; },
function (node) { return node.children? node.children.length : 0; }
);
for (let node in it) if (node.id === id) return node;
return null;
}
var tree = {
id: 'root',
children: [
{ id: 'a' },
{
id: 'b',
children: [
{ id: 'b1' },
{ id: 'b2' }
]
},
{ id: 'c' }
]
};
findNodeById(tree, 'b1'); //Object { id="b1"}
Note that you can also set the __iterator__ on the data structure so that functions that needs to iterate over this data structure do not have to know implementation details.
tree.__iterator__ = treeIterator.bind(null, tree,
function (node, i) { return node.children[i]; },
function (node) { return node.children? node.children.length : 0; }
);
Then the findNodeById function can be:
function findNodeById(tree, id) {
for (let node in it) if (node.id === id) return node;
return null;
}