make object structure recursive structure with same keys in javascript - javascript

Currently, I have this object
const path ={
"posts": {
"backend": {
"a.mdx": "./pages/posts/backend/a.mdx"
},
"frontend": {},
"retrospective": {
"b.mdx": "./pages/posts/retrospective/b.mdx",
"c.mdx": "./pages/posts/retrospective/c.mdx",
"d.mdx": "./pages/posts/retrospective/d.mdx"
}
}
}
What I want is..
const path = [{
title: 'posts',
sub: [
{
title: 'backend',
sub: [
{ title: 'a.mdx', path: './pages/posts/backend/a.mdx' },
],
},
{
title: 'frontend',
sub: [],
},
{
title: 'retrospective',
sub: [
{ title: 'b.mdx', path: './pages/posts/retrospective/b.mdx' },
{ title: 'c.mdx', path: './pages/posts/retrospective/c.mdx' },
{ title: 'd.mdx', path: './pages/posts/retrospective/d.mdx' },
],
},
],
}];
In this situation, how can I make the structure recursively?
I've read the lodash library document but I couldn't find a good combination to handle this issue.
Can I get any hint for this?

You can create recursive function and run it again if value is object, or set path if it is string. Check inline comments:
// Object
const path = {
"posts": {
"backend": {
"a.mdx": "./pages/posts/backend/a.mdx"
},
"frontend": {},
"retrospective": {
"b.mdx": "./pages/posts/retrospective/b.mdx",
"c.mdx": "./pages/posts/retrospective/c.mdx",
"d.mdx": "./pages/posts/retrospective/d.mdx"
}
}
};
// Recursive function
const recursiveFn = data => {
// Set array for function runtime result
const res = [];
// Iterate through keys in your object
for(const key in data) {
// If value is object, process it
// again in recursion
if(typeof data[key] === 'object' && data[key] !== null) {
res.push({
"title": key,
"sub": recursiveFn(data[key])
});
// If value is string
} else if(typeof data[key] === 'string') {
res.push({
"title": key,
"path": data[key]
});
}
}
// Return result
return res;
}
// Run test
console.log(recursiveFn(path));

Related

JS: append array of objects with data from another

I've got some JS data holding all kinds of data, numbers, child objects, arrays, etc in all manner of different structures:
let datapile = {
cover_img: { uid:'u2a3j4' },
avatar_img: { uid:'u5j3vg' },
created: 8273736384,
friends: [
{ name:'John', img: { uid:'u2726b' }, },
{ name:'Jane', parent: { profile_img: { uid:'u293k4' }, } },
],
occupation: {
past: current,
prior: {
title: 'Accountant',
company: {
logo: { img: { uid:'u29374' } },
}
},
},
...
}
And then I've got this JS list of images:
let imgs : [
{ uid:'u2a3j4', format:'jpg', alt_txt:'Lorem...', size:583729, dominant_color:'#d79273' },
{ uid:'u5j3vg', format:'png', alt_txt:'Lorem...', size:284849, dominant_color:'#f99383' },
{ uid:'u2726b', format:'gif', alt_txt:'Lorem...', size:293742, dominant_color:'#349a83' },
...
],
Now, what I need is a function I can call that will look through the datapile and append img data objects from the imgs list below. So where the datapile now has only the uid reference, it should have the entire img object. And I will then do the same with all kinds of other pieces of referenced data.
I've tried the following function:
function isArray(x){ return ( x !== undefined && Array.isArray(x) ) }
function isObject(x){ return (x && typeof x === "object" && !Array.isArray(x)) }
function get_item(type, uid) { /* loops through eg. imgs and returns img matching uid */ }
function append_referenced_relations(data){
if( !data ) return data
if( isObject(data) && data['uid'] !== undefined ) {
let item = get_item('any', data['uid'])
data = item
}
if( isObject(data) || isArray(data) ) {
for( let key in data ) {
data[key] = this.append_referenced_relations(deepClone(data[key]))
}
}
return data
}
... but I just can't get it to work. And my best googling efforts for similar scenarios have also come up empty. Can the internet help me out here?
you can try something like this
basically it use recursion and Object.fromEntries /entries to check all the keys of the inner object
if you have any specific question feel free to ask me
const decorate = (obj, data) => {
if (typeof obj !== 'object') {
return obj
}
if (Array.isArray(obj)) {
return obj.map(e => decorate(e, data))
}
return Object.fromEntries(
Object.entries(obj).flatMap(([k, v]) => {
if (k === 'uid') {
const imgData = data.find(d => v === d.uid)
return Object.entries(imgData || [[k, v]])
}
return [
[k, decorate(v, data)]
]
})
)
}
let datapile = {
cover_img: {
uid: 'u2a3j4'
},
avatar_img: {
uid: 'u5j3vg'
},
created: 8273736384,
friends: [{
name: 'John',
img: {
uid: 'u2726b'
},
},
{
name: 'Jane',
parent: {
profile_img: {
uid: 'u293k4'
},
}
},
],
occupation: {
past: 'current',
prior: {
title: 'Accountant',
company: {
logo: {
img: {
uid: 'u29374'
}
}
}
}
}
}
let imgs = [{
uid: 'u2a3j4',
format: 'jpg',
alt_txt: 'Lorem...',
size: 583729,
dominant_color: '#d79273'
},
{
uid: 'u5j3vg',
format: 'png',
alt_txt: 'Lorem...',
size: 284849,
dominant_color: '#f99383'
},
{
uid: 'u2726b',
format: 'gif',
alt_txt: 'Lorem...',
size: 293742,
dominant_color: '#349a83'
}
]
console.log(decorate(datapile, imgs))
You need to recurse in the nested datapile to identify the object with uids and add the img properties to be added.
Few cases to consider:
Objects. (If the Object has uid property, then stop recursion for its properties)
Object values having objects.
Array of Objects.
No need to return anywhere in your function actually as we can update objects inline.
Try like below.
let imgs = [ { uid: "u2a3j4", format: "jpg", alt_txt: "Lorem...", size: 583729, dominant_color: "#d79273", }, { uid: "u5j3vg", format: "png", alt_txt: "Lorem...", size: 284849, dominant_color: "#f99383", }, { uid: "u2726b", format: "gif", alt_txt: "Lorem...", size: 293742, dominant_color: "#349a83", }, { uid: "u293k4", format: "gif", alt_txt: "Lorem...", size: 193742, dominant_color: "#349a83", }, { uid: "u29374", format: "gif", alt_txt: "Lorem...", size: 793742, dominant_color: "#349a83", }, ]; let datapile = { cover_img: { uid: "u2a3j4" }, avatar_img: { uid: "u5j3vg" }, created: 8273736384, friends: [ { name: "John", img: { uid: "u2726b" } }, { name: "Jane", parent: { profile_img: { uid: "u293k4" } } }, ], occupation: { past: "current", prior: { title: "Accountant", company: { logo: { img: { uid: "u29374" } }, }, }, }, };
function isArray(x) {
return x !== undefined && Array.isArray(x);
}
function isObject(x) {
return typeof x === "object" && !Array.isArray(x);
}
function get_item(uid) {
return imgs.find((img) => img.uid === uid);
}
function append_referenced_relations(data) {
if (isObject(data)) {
if (data["uid"] !== undefined) {
const img = get_item(data.uid);
// Add img properties to the same object as properties
Object.entries(img).forEach(([key, value]) => {
data[key] = value;
});
} else {
// Recurse for the object values
Object.values(data).forEach((item) => {
append_referenced_relations(item);
});
}
} else if (isArray(data)) {
data.forEach((item) => {
// Recurse for the array entries
append_referenced_relations(item);
});
}
}
append_referenced_relations(datapile);
console.log(JSON.stringify(datapile, null, 2));

Having issues with deleting object from an array where match is found

I have the follow function that will delete object from array. It also returns the array tree without the items that was deleted. My issues is that it works when my objToFindBy is null deleting everything where {group: null} is found however it error with promise rejection if I set objToFindBy {group: 'some string'}
This code should delete all occurrences where the objToFindBy is a match, example {group: null} will find everywhere will the group is null and delete all object and then return the full tree without the objects that was deleted
findAndDeleteAll(tree, 'items', {group: null}) // work and delete all where match. then returns the tree without deleted objects
findAndDeleteAll(tree, 'items', {group: 'd575c91f-4765-4073-a948-5e305116610c'}) // promise rejection
const tree ={
"type": "app",
"info": "Custom Layout",
"items": [
{
"id": "d575c91f-4765-4073-a948-5e305116610c",
"title": "Fc",
"group": null
},
{
"id": "890d5a1e-3f03-42cd-a695-64a17b6b9bea",
"title": null,
"group": null
},
{
"id": "cbe00537-0bb8-4837-8019-de48cb04edd6",
"title": null,
"group": "d575c91f-4765-4073-a948-5e305116610c",
},
{
"id": "b8751c32-2121-4907-a229-95e3e49bcb39",
"title": null,
"group": "d575c91f-4765-4073-a948-5e305116610c"
}
],
"Children": []
}
var findAndDeleteAll = function findAndDeleteAll(tree, childrenKey, objToFindBy) {
var treeModified = false;
var findKeys = Object.keys(objToFindBy);
var findSuccess = false;
findKeys.forEach(function (key) {
(0, _lodash2.default)(tree[key], objToFindBy[key]) ? findSuccess = true : findSuccess = false;
});
if (findSuccess) {
Object.keys(tree).forEach(function (key) {
return delete tree[key];
});
return tree;
}
function innerFunc(tree, childrenKey, objToFindBy) {
if (tree[childrenKey]) {
var _loop = function _loop(index) {
var findKeys = Object.keys(objToFindBy);
var findSuccess = false;
findKeys.forEach(function (key) {
(0, _lodash2.default)(tree[childrenKey][index][key], objToFindBy[key]) ? findSuccess = true : findSuccess = false;
});
if (findSuccess) {
tree[childrenKey].splice(index, 1);
treeModified = true;
}
if (tree[childrenKey][index].hasOwnProperty(childrenKey)) {
innerFunc(tree[childrenKey][index], childrenKey, objToFindBy);
}
};
for (var index = tree[childrenKey].length - 1; index >= 0; index--) {
_loop(index);
}
}
}
innerFunc(tree, childrenKey, objToFindBy);
return treeModified ? tree : false;
};
how about shorter solution?
const findAndDeleteAll = (tree, childrenKey, nestedKey, nestedValue) => {
return{...tree, [childrenKey]: tree[childrenKey].filter((row) => {
return row[nestedKey] !== nestedValue;
})}
}
const a = findAndDeleteAll(tree, 'items', 'group', null) // work and delete all where match. then returns the tree without deleted objects
const b = findAndDeleteAll(tree, 'items', 'group', 'd575c91f-4765-4073-a948-5e305116610c') // promise rejection
console.warn(a);
console.warn(b);
Your function would be so much simper, reusable, better, if you send not the redundant tree - but instead deleteFrom(tree.items, "group", null);. think about it.
const deleteFrom = (arr, pr, val) => arr.filter(ob => ob[pr] !== val);
const tree = {
type: "app",
info: "Custom Layout",
items: [
{ id: "10c", title: "Fc", group: null },
{ id: "bea", title: null, group: null },
{ id: "dd6", title: null, group: "10c" },
{ id: "b39", title: null, group: "10c" },
],
Children: []
};
const items = deleteFrom(tree.items, "group", null);
console.log(items); // Only the filtered items array
const newTree = {...tree, items};
console.log(newTree); // Your brand new tree!

how to change value of all nodes of json array

I have a json array with different key values and need to add a ServerUrl to the beginning of all node values using a loop without writing multiple statements to do that by using javascript:
"Urls": [
{ "getCar": "/getAllCars" },
{ "getPerson": "/getAllPersons" },
{ "getBook": "/getAllBooks" }
],
"ServerUrl": "http://192.168.1.1:3000"
The expected result must be:
"Urls": [
{ "getCar": "http://192.168.1.1:3000/getAllCars" },
{ "getPerson": "http://192.168.1.1:3000/getAllPersons" },
{ "getBook": "http://192.168.1.1:3000/getAllBooks" }
],
Any advice would be appreciated.
You can use map to map your objects to new objects. Those objects have a single property, which you can get with Object.keys. The new object can get that same property name using the computed property name feature:
var obj = {
"Urls": [
{ "getCar": "/getAllCars" },
{ "getPerson": "/getAllPersons" },
{ "getBook": "/getAllBooks" }
],
"ServerUrl": "http://192.168.1.1:3000"
};
var urls = obj.Urls.map(o => Object.keys(o).map(k => ({ [k]: obj.ServerUrl + o[k] }))[0]);
console.log(urls);
const jsonVal = {
"Urls": [
{ "getCar": "/getAllCars" },
{ "getPerson": "/getAllPersons" },
{ "getBook": "/getAllBooks" }
],
"ServerUrl": "http://192.168.1.1:3000"
}
const result = jsonVal.Urls.map(val =>
Object.keys(val).reduce((resultObj, endpointKey) => {
resultObj[endpointKey] = `${jsonVal.ServerUrl}${val[endpointKey]}`;
return resultObj;
}, {})
);
Try (where your data are in d)
d.Urls.forEach( (x,i,a,k=Object.keys(x)[0]) => x[k] = d.ServerUrl + x[k]);
let d = {
"Urls": [
{ "getCar": "/GetAllGroupCustomers" },
{ "getPerson": "/getAllItems" },
{ "getBook": "/GetAllCustomers" }
],
"ServerUrl": "http://192.168.1.1:3000"
}
d.Urls.forEach( (x,i,a,k=Object.keys(x)[0]) => x[k] = d.ServerUrl + x[k]);
console.log(d);
A version that modifies your own object
var obj = {
"Urls": [
{ "getCar": "/getAllCars" },
{ "getPerson": "/getAllPersons" },
{ "getBook": "/getAllBooks" }
],
"ServerUrl": "http://192.168.1.1:3000"
};
obj.Urls.forEach(o => o[Object.keys(o)[0]] = `${obj.ServerUrl}${o[Object.keys(o)[0]]}`);
console.log(obj);

Deep replace a value knowing the value of an attribute within the object we must inject value in

We have a deeply nested structure which varies each time we run the app.
{
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
}
We must inject, in this structure, proper value. We receive for example
{
group1: 'the proper value'
}
And we must replace the value in the proper group to obtain:
{
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "the proper value" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
}
We tried to use lodash mergeWith but since we cannot know where exactly is the value we must inject and we only know the value of of of the key of the object we must inject the value in, we didn't manage to get this working.
Have a recursive function going through the object and mutating it depending on the value of what you seek.
const obj = {
some: {
complex: {
unknown: {
structure: {
fields: [{
name: 'group1',
other: 'data',
currentValue: '',
},
{
name: 'group2',
other: 'another data',
currentValue: '',
},
],
},
},
},
},
};
const toChange = {
group1: 'the proper value',
group2: 'the proper value 2',
};
// Recursive function that go replace
function lookAndReplace(config, ptr) {
// If we deal with an object look at it's keys
if (typeof ptr === 'object') {
Object.keys(ptr).forEach((x) => {
// If the keys is the one we was looking for check the value behind
if (x === config.keyToCheck) {
// We have found one occurence of what we wanted to replace
// replace the value and leave
if (ptr[x] === config.key) {
ptr[config.keyToReplace] = config.value;
}
return;
}
// Go see into the value behind the key for our data
lookAndReplace(config, ptr[x]);
});
}
// If we are dealing with an array, look for the keys
// inside each of the elements
if (ptr instanceof Array) {
ptr.forEach(x => lookAndReplace(config, x));
}
}
// For each group we look for, go and replace
Object.keys(toChange).forEach(x => lookAndReplace({
key: x,
value: toChange[x],
keyToCheck: 'name',
keyToReplace: 'currentValue',
}, obj));
console.log(obj);
/!\ Important this soluce also work with nested arrays
const obj = {
some: {
complex: {
unknown: {
structure: {
// fields is an array of array
fields: [
[{
name: 'group1',
other: 'data',
currentValue: '',
}],
[{
name: 'group2',
other: 'another data',
currentValue: '',
}],
],
},
},
},
},
};
const toChange = {
group1: 'the proper value',
group2: 'the proper value 2',
};
// Recursive function that go replace
function lookAndReplace(config, ptr) {
// If we deal with an object look at it's keys
if (typeof ptr === 'object') {
Object.keys(ptr).forEach((x) => {
// If the keys is the one we was looking for check the value behind
if (x === config.keyToCheck) {
// We have found one occurence of what we wanted to replace
// replace the value and leave
if (ptr[x] === config.key) {
ptr[config.keyToReplace] = config.value;
}
return;
}
// Go see into the value behind the key for our data
lookAndReplace(config, ptr[x]);
});
}
// If we are dealing with an array, look for the keys
// inside each of the elements
if (ptr instanceof Array) {
ptr.forEach(x => lookAndReplace(config, x));
}
}
// For each group we look for, go and replace
Object.keys(toChange).forEach(x => lookAndReplace({
key: x,
value: toChange[x],
keyToCheck: 'name',
keyToReplace: 'currentValue',
}, obj));
console.log(obj);
const obj = {
some: {
complex: {
unknown: {
structure: {
fields: [{
name: "group1",
other: "data",
currentValue: ""
},
{
name: "group2",
other: "another data",
currentValue: ""
},
]
}
}
}
}
};
const toChange = {
group1: 'the proper value',
group2: 'the proper value 2',
};
// Recursive function that go replace
function lookAndReplace({
key,
value,
keyToCheck,
keyToReplace,
}, ptr) {
// If we deal with an object
if (typeof ptr === 'object') {
Object.keys(ptr).forEach((x) => {
if (x === keyToCheck) {
// We have found one
if (ptr[x] === key) {
ptr[keyToReplace] = value;
}
} else {
lookAndReplace({
key,
value,
keyToCheck,
keyToReplace,
}, ptr[x]);
}
});
}
if (ptr instanceof Array) {
ptr.forEach(x => lookAndReplace({
key,
value,
keyToCheck,
keyToReplace,
}, x));
}
}
// For each group we look for, go and replace
Object.keys(toChange).forEach(x => lookAndReplace({
key: x,
value: toChange[x],
keyToCheck: 'name',
keyToReplace: 'currentValue',
}, obj));
console.log(obj);
A solution could be to use a recursive function like this:
object={
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
};
newValue={
group1: 'the proper value'
};
var inserted=false;
function search(data, newData){
if(inserted) return;
for(key in data){
if(data[key]==Object.keys(newData)[0]){
data["currentValue"]=newData[Object.keys(newData)[0]];
inserted=true;
return;
}else
search(data[key], newData);
}
}
search(object, newValue);
console.log(object);
You could do a recursive search and replace...
let theObj = {
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
}
function updateObj(obj, replacement) {
if(Array.isArray(obj)) {
let key = Object.keys(replacement)[0]
let itm = obj.find(i => i.name == key)
itm.data = replacement[key]
} else if(typeof obj == 'object') {
for(let i in obj) {
updateObj(obj[i], replacement)
}
}
}
updateObj(theObj, { group1: 'the proper value' })
console.log(theObj)

Converting an array of objects (with string array elements) into a nested tree

I've been trying to solve a problem with arrays and objects for some time now - but I still can't find the right solution.
This is my starting position:
list = [
{
lastItemData: {data:"root"},
path: ["Root"]
},
{
lastItemData: {data:"level2_Summer"},
path: ["Root", "Level2_Summer"]
},
{
lastItemData: {data:"Level3_Monday"},
path: ["Root", "Level2_Winter", "Level3_Monday"]
},
{
lastItemData: {data:"Level4_Morning"},
path: ["Root", "Level2_Spring", "Level3_Tuesday", "Level4_Morning"]
},
{
lastItemData: {data:"Level3_Sunday"},
path: ["Root", "Level2_Autumn", "Level3_Sunday"]
}]
and this is what i need:
result = [
{
text: "Root",
lastItemData: {data:"root"},
Items:[
{
text:"Level2_Summer",
lastItemData: {data:"level2_Summer"},
Items: []
},
{
text:"Level2_Winter",
Items:[
{
text: "Level3_Monday",
lastItemData: {data:"Level3_Monday"},
Items: []
}
]
},
{
text:"Level2_Spring",
Items:[
{
text: "Level3_Tuesday"
Items: [
{
text:"Level4_Morning"
Items:[],
lastItemData: {data:"Level4_Morning"},
}
]
}
]
},
{
text:"Level2_Autumn",
Items:[
{
text: "Level3_Sunday"
}
]
},
]
}]
I'v try something like this (code is based on user jonas answer from my deleted post)
const property = list.reduce((previous, current, index) => {
let acc = {}; //the accumulator that will go deep into the object
const result = acc; //our result will be the top level object
debugger;
//Now we iterate over our array
for (var i = 0; i < current.stringPath.length; i++) {
debugger;
//add the string part
acc["level" + (i + 1)] = current.stringPath[i];
//Add the children array
acc["Items"] = [{}];
//And go into the object
acc = acc["Items"][0];
}
console.log(result)
previous.push(result);
return previous;
}, []);
console.log("property", property);
But unfortunately the result does not correspond to the desired structure. Can anyone help me?
This is a far more complex problem as it looks like, because you have to build a nested structure with a given name (text property) and you need to check if that name exist or not. If not create a new object and push it to the parent array.
This solution features a hash table where all nested hashes for each level are collected and maintained. The result is an array with the wanted tree.
It works as follows:
First have a look into the root of the hash table and check if the path value exists. If not, create a new node with the information and push it to the hash collector, denoted with _. Then return that node and go on with iteration of the given path.
At the and of the path use lastItemData for the wanted object.
var list = [{ lastItemData: { data: "root" }, path: ["Root"] }, { lastItemData: { data: "level2_Summer" }, path: ["Root", "Level2_Summer"] }, { lastItemData: { data: "Level3_Monday" }, path: ["Root", "Level2_Winter", "Level3_Monday"] }, { lastItemData: { data: "Level4_Morning" }, path: ["Root", "Level2_Spring", "Level3_Tuesday", "Level4_Morning"] }, { lastItemData: { data: "Level3_Sunday" }, path: ["Root", "Level2_Autumn", "Level3_Sunday"] }],
tree = function (array) {
var result = [],
hash = { _: { Items: result } };
array.forEach(function (object) {
object.path.reduce(function (o, p) {
if (!o[p]) {
o[p] = { _: { text: p, Items: [] } };
o._.Items.push(o[p]._);
}
return o[p];
}, hash)._.lastItemData = object.lastItemData;
});
return result;
}(list);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources