Get parent in javascript without modifying object - javascript

I have original nested object which contains the huge tree kind of structure. This is is basically JSON string which is converted into JavaScript object.
Structure is like -
original = {
type : "table",
children :[
{
type : "cell",
children : [
{
type : "label",
children : []
}
]
}
{
type : "cell",
children : []
}
]
}
I have selected item as -
var select = original.children[1].children[0];
What I want is get the parent of selected item.
Here is sample demo - https://stackblitz.com/edit/angular-v5m9ua
Note : I need to trace over the original object to find the parent. I had looked at the other answers but they had mentioned how to design the object structure to get the parent but I don't want to change the original object.

You could create recursive function with for...in loop and return last parent element that was of object type.
const data = {
type: "table",
children: [{
type: "cell",
children: [{
type: "label",
children: []
}]
}, {
type: "cell",
children: []
}]
}
var select = data.children[0].children[0];
function getParent(data, obj, parent = null) {
let result = null;
(function loop(data, obj, parent) {
if (typeof data == 'object' && !Array.isArray(data)) {
parent = data
}
for (let i in data) {
if (select == data[i]) {
result = parent;
break;
}
if (typeof data[i] == 'object') {
loop(data[i], obj, parent)
}
}
})(data, obj, parent)
return result;
}
let parent = getParent(data, select)
console.log(parent)

You could search the tree:
findParent(root, toFind) {
for(const child of root.children || []) {
if(child === toFind){
return root;
}
const result = this.findParent(child, toFind);
if(result){
return result;
}
}
}
That can be used as:
findParent(original, select)

Related

How to walk through the object/array in javascript by layers

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));

Update existing javascript object with new data recursively. Arrays elements should be merged by id

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

JavaScript: Print full path of netsted object

is there anyway in JavaScript to print complete path of a nested object attribute. For example assume I have a below object
var items = [ {
2019 : {
December: {
Groceries : {
KitchenNeeds : {
[
"Milk",
"cheese"
]
}
}
}
}
}];
To access Milk, I can access as items[0].2019.December.Groceries.KitchenNeeds[0] But is there any way, if I choose "Milk" it should print all the tree path thats needs to be travelled to get "Milk".
You could check the if the nested getting the pathe returns a truthy value and return an array with the actual key and the path of the nested value.
function getPath(object, target) {
var path;
if (object && typeof object === 'object') {
Object.entries(object).some(([k, v]) => {
if (v === target) return path = [k];
var temp = getPath(v, target);
if (temp) return path = [k, ...temp];
});
}
return path;
}
var items = [{ 2019: { December: { Groceries: { KitchenNeeds: ["Milk", "cheese"] } } } }];
console.log(getPath(items, 'Milk'));

How to convert array of object to nested object and vise versa in js

Array of objects where the hierarchy objects is stored in hierarchy
property. nesting of objects are done based on this hierarchy
[
{
"hierarchy" : ["obj1"],
"prop1":"value"
},
{
"hierarchy" : ["obj1","obj2"],
"prop2":"value",
"prop3":"value"
},
{
"hierarchy" : ["obj1","obj3"],
"prop4":"value",
"prop5":"value"
},
{
"hierarchy" : ["obj1","obj3", "obj4"],
"prop6":"value",
"prop7":"value",
"arr" :["val1", "val2"]
}
]
Expected nested object, hierarchy key removed here
{
"obj1":{
"prop1":"value",
"obj2" : {
"prop2":"value",
"prop3":"value"
},
"obj3":{
"prop4":"value",
"prop5":"value",
"obj4" : {
"prop6":"value",
"prop7":"value",
"arr" :["val1", "val2"]
}
}
}
}
Code I tried but at line 8 unable to get the hierarchy
var input = "nested array as above";
var output = {};
var globalTemp = output;
for(var i = 0 ; i<input.length ; i++){
var tempObj = input[i];
for(var key in tempObj){
if(key == "hierarchy"){
globalTemp = globlalTemp[tempObj[key]] = {};
}
}
}
console.log(globalTemp);
You can use forEach and reduce methods and inside create shallow copy of current object and delete hierarchy property.
const data = [{"hierarchy":["obj1"],"prop1":"value"},{"hierarchy":["obj1","obj2"],"prop2":"value","prop3":"value"},{"hierarchy":["obj1","obj3"],"prop4":"value","prop5":"value"},{"hierarchy":["obj1","obj3","obj4"],"prop6":"value","prop7":"value","arr":["val1","val2"]}]
const result = {}
data.forEach(function(o) {
o.hierarchy.reduce(function(r, e) {
const clone = Object.assign({}, o);
delete clone.hierarchy
return r[e] = (r[e] || clone)
}, result)
})
console.log(result)
With a newer version of javascript, you could use restparameters for the wanted value key/value pairs and build a nested structure by iterating the given hierarchy property by saving the last property for assigning the rest properties.
The reclaimed part getFlat uses an array as stack without recursive calls to prevent a depth first search which tries to get the most depth nodes first.
At start, the stack is an array with an array of the actual object and another object with an empty hierarchy property with an empty array, because actually no key of the object is known.
Then a while loop checks if the stack has some items and if so, it takes the first item of the stack and takes a destructuring assignment for getting an object o for getting back all key/value pairs and another object temp with a single property hierarchy with an array of the path to the object o.
The push flag is set to false, because only found properties should be pushed later to the result set.
Now all properties of the object are checked and if
the value is truthy (to prevent null values),
the type is an object (null is an object) and
the property is not an array
then a new object is found to inspect. This object is pushed to the stack with the actual path to it.
If not, then a value is found. This key/value pair is added to the temp object and the flag is set to true, for later pushing to the result set.
Proceed with the keys of the object.
Later check push and push temp object with hierarchy property and custom properties to the result set.
function getFlat(object) {
var stack = [[object, { hierarchy: [] }]],
result = [],
temp, o, push;
while (stack.length) {
[o, temp] = stack.shift();
push = false;
Object.keys(o).forEach(k => {
if (o[k] && typeof o[k] === 'object' && !Array.isArray(o[k])) {
stack.push([o[k], { hierarchy: temp.hierarchy.concat(k) }]);
} else {
temp[k] = o[k];
push = true;
}
});
push && result.push(temp);
}
return result;
}
var data = [{ hierarchy: ["obj1"], prop1: "value" }, { hierarchy: ["obj1", "obj2"], prop2: "value", prop3: "value" }, { hierarchy: ["obj1", "obj3"], prop4: "value", prop5: "value" }, { hierarchy: ["obj1", "obj3", "obj4"], prop6: "value", prop7: "value", arr: ["val1", "val2"] }],
object = data.reduce((r, { hierarchy, ...rest }) => {
var last = hierarchy.pop();
hierarchy.reduce((o, k) => o[k] = o[k] || {}, r)[last] = rest;
return r;
}, {}),
reclaimedData = getFlat(object);
console.log(object);
console.log(reclaimedData);
.as-console-wrapper { max-height: 100% !important; top: 0; }

JavaScript recursive search in JSON object

I am trying to return a specific node in a JSON object structure which looks like this
{
"id":"0",
"children":[
{
"id":"1",
"children":[...]
},
{
"id":"2",
"children":[...]
}
]
}
So it's a tree-like child-parent relation. Every node has a unique ID.
I'm trying to find a specific node like this
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
currentNode.children.forEach(function (currentChild) {
findNode(id, currentChild);
});
}
}
I execute the search for example by findNode("10", rootNode). But even though the search finds a match the function always returns undefined. I have a bad feeling that the recursive function doesn't stop after finding the match and continues running an finally returns undefined because in the latter recursive executions it doesn't reach a return point, but I'm not sure how to fix this.
Please help!
When searching recursively, you have to pass the result back by returning it. You're not returning the result of findNode(id, currentChild), though.
function findNode(id, currentNode) {
var i,
currentChild,
result;
if (id == currentNode.id) {
return currentNode;
} else {
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
for (i = 0; i < currentNode.children.length; i += 1) {
currentChild = currentNode.children[i];
// Search in the current child
result = findNode(id, currentChild);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
var result;
currentNode.children.forEach(function(node){
if(node.id == id){
result = node;
return;
}
});
return (result ? result : "No Node Found");
}
}
console.log(findNode("10", node));
This method will return the node if it present in the node list. But this will loop through all the child of a node since we can't successfully break the forEach flow. A better implementation would look like below.
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
for(var index in currentNode.children){
var node = currentNode.children[index];
if(node.id == id)
return node;
findNode(id, node);
}
return "No Node Present";
}
}
console.log(findNode("1", node));
I use the following
var searchObject = function (object, matchCallback, currentPath, result, searched) {
currentPath = currentPath || '';
result = result || [];
searched = searched || [];
if (searched.indexOf(object) !== -1 && object === Object(object)) {
return;
}
searched.push(object);
if (matchCallback(object)) {
result.push({path: currentPath, value: object});
}
try {
if (object === Object(object)) {
for (var property in object) {
if (property.indexOf("$") !== 0) {
//if (Object.prototype.hasOwnProperty.call(object, property)) {
searchObject(object[property], matchCallback, currentPath + "." + property, result, searched);
//}
}
}
}
}
catch (e) {
console.log(object);
throw e;
}
return result;
}
Then you can write
searchObject(rootNode, function (value) { return value != null && value != undefined && value.id == '10'; });
Now this works on circular references and you can match on any field or combination of fields you like by changing the matchCallback function.
Since this old question has been brought back up, here's a different approach. We can write a fairly generic searchTree function which we then use in a findId function. searchTree does the work of traversing the object; it accepts a callback as well as the tree; the callback determines if a node matches. As well as the node, the callback is supplied two functions, next and found, which we call with no parameters to signal, respectively, that we should proceed or that we've found our match. If no match is found, we return null.
It looks like this:
const searchTree = (fn) => (obj) =>
Array.isArray(obj)
? obj.length == 0
? null
: searchTree (fn) (obj [0]) || searchTree (fn) (obj .slice (1))
: fn (
obj,
() => searchTree (fn) (obj .children || []),
() => obj
)
const findId = (target, obj) => searchTree (
(node, next, found) => node.id == target ? found () : next(),
) (tree)
const tree = {id: 1, name: 'foo', children: [
{id: 2, name: 'bar', children: []},
{id: 3, name: 'baz', children: [
{id: 17, name: 'qux', children: []},
{id: 42, name: 'corge', children: []},
{id: 99, name: 'grault', children: []}
]}
]}
console .log (findId (42, tree))
console .log (findId (57, tree))
This code is specific to the structure where subnodes are found in an array under the property children. While we can make this more generic as necessary, I find this a common structure to support.
There is a good argument that this would be better written with mutual recursion. If we wanted, we could get the same API with this version:
const searchArray = (fn) => ([x, ...xs]) =>
x === undefined
? null
: searchTree (fn) (x) || searchArray (fn) (xs)
const searchTree = (fn) => (obj) =>
fn (
obj,
() => searchArray (fn) (obj .children || []),
(x) => x
)
This works the same way. But I find the code cleaner. Either should do the job, though.
We use object-scan for our data processing needs. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you could solve your question
// const objectScan = require('object-scan');
const findNode = (id, input) => objectScan(['**'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.id === id
})(input);
const data = { id: '0', children: [{ id: '1', children: [ { id: '3', children: [] }, { id: '4', children: [] } ] }, { id: '2', children: [ { id: '5', children: [] }, { id: '6', children: [] } ] }] };
console.log(findNode('6', data));
// => { id: '6', children: [] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
Similar questions were answered several times, but I just want to add a universal method that includes nested arrays
const cars = [{
id: 1,
name: 'toyota',
subs: [{
id: 43,
name: 'supra'
}, {
id: 44,
name: 'prius'
}]
}, {
id: 2,
name: 'Jeep',
subs: [{
id: 30,
name: 'wranger'
}, {
id: 31,
name: 'sahara'
}]
}]
function searchObjectArray(arr, key, value) {
let result = [];
arr.forEach((obj) => {
if (obj[key] === value) {
result.push(obj);
} else if (obj.subs) {
result = result.concat(searchObjectArray(obj.subs, key, value));
}
});
console.log(result)
return result;
}
searchObjectArray(cars, 'id', '31')
searchObjectArray(cars, 'name', 'Jeep')
I hope this helps someone
I really liked a tree search! A tree is an extremely common data structure for most of today's complex structured tasks. So I just had similar task for lunch too. I even did some deep research, but havent actually found anything new! So what I've got for you today, is "How I implemented that in modern JS syntax":
// helper
find_subid = (id, childArray) => {
for( child of childArray ) {
foundChild = find_id( i, child ); // not sub_id, but do a check (root/full search)!
if( foundChild ) // 200
return foundChild;
}
return null; // 404
}
// actual search method
find_id = (id, parent) => (id == parent.id) : parent : find_subid(id, parent.childArray);
Recursive structure search, modification, keys/values adjustments/replacement.
Usage Example:
const results = []; // to store the search results
mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
// do something cool with "v" (or key, or obj)
// return nothing (undefined) to keep the original value
// if we search:
if (key === 'name' && v === 'Roman'){
results.push(obj);
}
// more example flow:
if (isCircular) {
delete obj[key]; // optionally - we decide to remove circular links
} else if (v === 'Russia') {
return 'RU';
} else if (key.toLocaleLowerCase() === 'foo') {
return 'BAR';
} else if (key === 'bad_key') {
delete obj[key];
obj['good_key'] = v;
} else {
return v; // or undefined, same effect
}
});
Tips and hints:
You can use it as a search callback, just return nothing (won't affect anything) and pick values you need to your Array/Set/Map.
Notice that callback is being run on every leaf/value/key (not just objects).
Or you can use the callback to adjust particular values and even change keys. Also it automatically detects circular loops and provides a flag for you to decide how to handle them.
The code
(uses ES6)
Function itself + some example demo data
function mapNodesRecursively(obj, mapCallback, { wereSet } = {}) {
if (!wereSet) {
wereSet = new Set();
}
if (obj && (obj === Object(obj) || Array.isArray(obj))) {
wereSet.add(obj);
for (let key in obj) {
if (!obj.hasOwnProperty(key)){
continue;
}
let v = obj[key];
const isCircular = wereSet.has(v);
const mapped = mapCallback({ v, key, obj, isCircular });
if (typeof (mapped) !== 'undefined') {
obj[key] = mapped;
v = mapped;
}
if (!isCircular) {
mapNodesRecursively(v, mapCallback, { wereSet });
}
}
}
return obj;
}
let obj = {
team: [
{
name: 'Roman',
country: 'Russia',
bad_key: 123,
},
{
name: 'Igor',
country: 'Ukraine',
FOO: 'what?',
},
{
someBool: true,
country: 'Russia',
},
123,
[
1,
{
country: 'Russia',
just: 'a nested thing',
a: [{
bad_key: [{
country: 'Russia',
foo: false,
}],
}],
},
],
],
};
// output the initial data
document.getElementById('jsInput').innerHTML = JSON.stringify(obj, null, 2);
// adding some circular link (to fix with our callback)
obj.team[1].loop = obj;
mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
if (isCircular) {
delete obj[key]; // optionally - we decide to remove circular links
} else if (v === 'Russia') {
return 'RU';
} else if (key.toLocaleLowerCase() === 'foo') {
return 'BAR';
} else if (key === 'bad_key') {
delete obj[key];
obj['good_key'] = v;
} else {
return v;
}
});
// output the result - processed object
document.getElementById('jsOutput').innerHTML = JSON.stringify(obj, null, 2);
.col {
display: inline-block;
width: 40%;
}
<div>
<h3>Recursive structure modification, keys/values adjustments/replacement</h3>
<ol>
<li>
Replacing "Russia" values with "RU"
</li>
<li>
Setting the value "BAR" for keys "FOO"
</li>
<li>
Changing the key "bad_key" to "good_key"
</li>
</ol>
<div class="col">
<h4>BEFORE</h4>
<pre id="jsInput"></pre>
</div>
<div class="col">
<h4>AFTER</h4>
<pre id="jsOutput"></pre>
</div>
</div>

Categories