Go through object when keys are know - javascript

I have object
var routes = {
"home":{
hash: "/home",
children: {
"just-home": {
hash: "/home/just-home",
children: {...}
},
"sub-homea": {
hash: "/home/sub-homea",
children: {...}
}
},
"contact":{
hash: "/contact",
children: {
"just-contact": {
hash: "/contact/just-contact",
children: {...}
},
"sub-contact": {
hash: "/contact/sub-contact",
children: {...}
}
}
}
How i can set object to just-contact.children when i know for example - that first key is contact, and next just-contat.. ? I need to assign this object dynamically because the known keys will be all time different. So i need use any loop. something like this -
const pathArray = [contact,just-contact]
Object.keys(routes).map(function (item) {
if (routes[item] === pathArray[counter]){
ob = routes[item];
counter++;
}
})
but this will loop only once and it won't go to deep.
UPDATE for more clean explanation -
I will read from path location (localhost:3000/contact/just-contact) the values (contact,just-contact) , which i will save to array (pathArray=[contact,just-contact]), when the location path will be change, the keys in array will be change too. And i need to find children of last key, in this example children of just-contact key

Found simple solution -
pathArray.map(function (item) {
if (obj[item].hash === item){
obj = obj[item].children;
}
})

Related

Concentate values in a Nested Array of Object with children in JavaScript

I have a nested array of objects having path as one of the keys. The structure of the nested array is as under:
const data = [
{
Name: "item1",
path: "path1",
children:[
{
Name: "item1.1",
path: "path1.1"
},
{
Name: "item1.2",
path: "path1.2",
children:[
{
Name: "item1.2.1",
path: "path1.2.1",
children:[
{
Name: "item1.2.1.1",
path: "path1.2.1.1"
}
]
},
]
}
]
}
]
I need to concentate the path values without changing the structure of array. The expected result would be:
const newdata: [
{
Name: "item1",
path: "path1",
children:[
{
Name: "item1.1",
path: "path1/path1.1"
},
{
Name: "item1.2",
path: "path1/path1.2",
children:[
{
Name: "item1.2.1",
path: "path1/path1.2/path1.2.1",
children:[
{
Name: "item1.2.1.1",
path: "path1/path1.2/path1.2.1/path1.2.1.1",
}
]
}
]
}
]
}
]
How to do it in JavaScript?
This would be best done with a recursive Function, that iterates through your entire data structure and sets the Path by building it up while traversing your data structure.
The first version creates the path information from scratch, by using the index of each child in the array and building up the index that gets appended to the path string.
Further below i've provided changes to this version, that uses the already existing path information and concatenates the path string as you asked for.
// Recursive Function to iterate through a possible endless nested data structure
// We provide the parameter for the previous index and parentPath to build up the path string
function recursivePath(data, index = "", parentPath = "") {
// We will get an Array with all Items as 'data' which we will loop with forEach
data.forEach((item, i) => {
// We recreate the the index of the item by adding current index of
// this item in the data array to the index structure of the parent items
let itemIndex = index !== "" ? `${index}.${i+1}` : `${i+1}`;
// We do the same for the path, we take the path of the parent
// and add the path information of this item to it.
let itemPath = `${parentPath}path${itemIndex}`;
// We set the path property of this item, which will be returned
// after all items of this data are done.
item.path = itemPath;
// We check if this item has some nested childrens and if it does,
// we will repeat this process for all those childrens
if (item.children && typeof item.children.length) {
// We provide the newly created index on which those childs will build upon
// as the same with the path.
// This can be a bit confusing, but we assume here, that the function will return
//the finished childrens and we save the result to our childrens property.
item.children = recursivePath(item.children, itemIndex, itemPath + "/");
}
});
// Lastly we iterated through all Items and are sure to set the Path for all Items
// and their childrens nested inside and return the entire data array.
return data;
}
// Your Data
const data = [{
Name: "item1",
path: "path1",
children: [{
Name: "item1.1",
path: "path1.1"
},
{
Name: "item1.2",
path: "path1.2",
children: [{
Name: "item1.2.1",
path: "path1.2.1",
children: [{
Name: "item1.2.1.1",
path: "path1.2.1.1"
}]
}, ]
}
]
}];
// We use the recursive function and output the results to the console
console.log(recursivePath(data));
If you would use the stored Path value of each item, you could just append the Value onto the parentPath String and save this new String into item.path
You would just change the line in the function, that creates the itemPath a little bit and you can remove the line that creates the itemIndex.
The parameter itemIndex of the recursive function isn't needed anymore and can be removed too.
// We wont need the index anymore, as we use the already existing
// Path value for the Index of each item
function recursivePath(data, parentPath = "") {
// We create a temporary new Data variable, to hold our changed elements.
let newData = [];
data.forEach((item, i) => {
// We'll create a copy of an Object to modify
let copyItem = {};
// Object.assign() copies all enumerable properties of one object to another
// We'll then use the new object to modify all properties,
// thous the original item will be untouched.
Object.assign(copyItem, item)
// We append the path information of this items path value
// onto the parentPath string
let itemPath = `${parentPath}${item.path}`;
// Same as before
copyItem.path = itemPath;
// Same as before
if (copyItem.children && typeof copyItem.children.length) {
// We removed the itemIndex, as it isnt needed anymore
copyItem.children = recursivePath([...copyItem.children], itemPath + "/");
}
// After modification we add the object to the temporary array
// and return it after all items are modified.
newData.push(copyItem);
});
// Returning the newly created array
return newData;
}
// Your Data
const data = [{
Name: "item1",
path: "path1",
children: [{
Name: "item1.1",
path: "path1.1"
},
{
Name: "item1.2",
path: "path1.2",
children: [{
Name: "item1.2.1",
path: "path1.2.1",
children: [{
Name: "item1.2.1.1",
path: "path1.2.1.1"
}]
}, ]
}
]
}];
// We use the recursive function and output the results to the console
console.log(recursivePath(data));
console.log(data);
Fur further clarification of why we need to copy Arrays and/or Objects provided as a parameter:
Arrays and Objects arent provided as their full content, as those could be huge data structures and moving and copying those every time they are provided as parameter would cause a huge memory dump as every parameter would be a redundant content of their original data.
Therefore only references or in other languages called pointers are provided, which point or reference the memory location, where the content is stored.
If you provide an array or object for a function and modify them, the modification will be stored via the reference on the original array and therefore all further access to this variable will also have those modification.
Thats why we need to copy the content of those variables into new array or objects and return those as they are themself new references but to another array with the same but slightly modified content.
The redundancy doesn't matter, as those variables are only block/closure scoped with the prefix of let before, therefore they are garbage collected after the function resolved.

Asynchronously and recursively crawl links Javascript

I am making a Blog using Notion as a content management system. There is an unofficial API provided by notion-api-js, with a function getPagesByIndexId(pageId) that returns a page's content, its subpages' contents, and, its parent's contents. So, an array of objects is returned, looking like:
[
{ moreStuff: ...,
Attributes: { slug: "home page slug", id: "home page id", moreStuff... },
},
{ moreStuff: ..., Attributes: { slug: "parent to homepage", id: "homepage's parent id", moreStuff: ... }
{ moreStuff: ..., Attributes: { slug: "sub page slug 0", id: "sub page id 0", moreStuff: ... } },
{ moreStuff: ..., Attributes: { slug: "sub page slug 1", id: "sub page id 1", moreStuff: ... } },
];
I want to build a tree that is created by recursively looping through the given id and the ids that getPagesByIndexId(given id) return to extract all slugs and ids. The function stops recursing when getPagesByIndexId(id) returns objects with ids already crawled through.
I use a crawledIdsList array to keep track of ids already crawled through, fetchPage is the same as getPagesByIndex, and I use flatmap to ignore empty []s passed by from the map function. Thanks in advance! To run this locally on node, the dependency required is npm i notion-api-js
The tree structure of the page I provided the ID with (I provided the id to the "Dev" page in homePageId) looks like:
My current code follows. It hits the "end" and returns successfully, but it is returning many pages a lot more than once.
const Notion = require("notion-api-js").default;
const token_v2 = "543f8f8529f361ab34596f5be9bc972b96ab8d8dc9e6e41546c05751b51a18a6c7d40b689d80794babae3a91aeb5dd5e47c34edb724cc356ceceacf3a8061158bfab92e68b7614516a0699295990"
const notion = new Notion({
token: token_v2,
});
const fetchPage = (id) => {
return notion.getPagesByIndexId(id);
};
const homePageId = "3be663ea-90ce-4c45-b04e-41161b992dda"
var crawledIdsList = [];
buildTree(tree={}, homePageId).then(tree => {console.log(tree)})
function buildTree(tree, id) {
return fetchPage(id).then((pages) => {
tree.subpages = [];
tree.slug = pages[0].Attributes.slug;
tree.id = id;
crawledIdsList.push(id);
return Promise.all(
pages.flatMap((page) => {
var currentCrawlId = page.Attributes.id;
if (crawledIdsList.indexOf(currentCrawlId) === -1) {
// executes code block if currentCrawlId is not used in fetchPage(id) yet
crawledIdsList.push(currentCrawlId);
return buildTree({}, currentCrawlId).then((futureData) => {
tree.subpages.push(futureData);
return tree;
});
} else {
if (crawledIdsList.indexOf(id) >= 0) {
return [];
}
return tree; // end case. futureData passed to earlier calls is tree, which looks like {subpages: [], slug: someSlug, id: someId}
}
})
)
});
}

How to remove unused objects after matching with another array?

I have an array of objects and another array of names like the following:
array of objects as file info objects
[
{ fieldname: 'banner', path: 'banner_img_path' },
{ fieldname: 'logo' , path: 'logo_img_path' },
{ fieldname: 'random', path: 'random_img_path' }
]
and here is my array of names
['banner', 'logo']
So how can i match or mapping those two variables! note that after mapping or matching, all not used objects should be deleted from the original array of objects?? i need to get object like that
{
banner: banner_img_path,
logo : logo_img_path
}
I'm able to do matching but i was stuck with erasing those not used objects.
Note: May be this question is duplicated but only because i don't know what is the name of this operation!
so please forgive me.
There is a simple solution by using functional native JavaScript functions.
const obj = [
{ fieldname: 'banner', path: 'banner_img_path' },
{ fieldname: 'logo', path: 'logo_img_path' },
{ fieldname: 'random', path: 'random_img_path' },
{ fieldname: 'other', path: 'random_img_path' }
];
const names = ['banner', 'logo'];
obj.map((o, i) => {
let match = false;
names.some((item) => {
if (item === o.fieldname) {
match = true;
return true;
}
});
if (!match) {
delete obj[i];
}
});
for (let o in obj) {
document.querySelector('p').innerHTML += obj[o].fieldname + '<br>';
}
<p></p>
I add the names you want to keep into a set for lookup. I then use forEach and check inside each object if the fieldname matches anything in the set of names. If there's a match, I add it to my newObj.
const namesToKeep = ['banner', 'logo']
const fileInfo = [
{ fieldname: 'banner', path: 'banner_img_path' },
{ fieldname: 'logo' , path: 'logo_img_path' },
{ fieldname: 'random', path: 'random_img_path' }
]
const names = new Set(namesToKeep)
const newObj = {}
fileInfo.forEach(obj => {
if (names.has(obj.fieldname)) {
newObj[obj.fieldname] = obj.path
}
})
console.log(newObj)

Object push Firebase, how to remove key names from pushed items

I have this Object.key code that pushes all items:
const cloned_items = [];
Object.keys(items).sort().map(key => {
let item = {
[`item-${uid}`]: {
item: false
}
}
cloned_items.push({ ...item });
});
database.ref('/app/items').update({
...cloned_items
})
but this produces following result:
"0" : {
"timeslot-87dah2j" : {
item: false
}
},
"1" : {
"timeslot-7s1ahju" : {
item: false
}
}
instead of:
"timeslot-87dah2j" : {
item: false
},
"timeslot-7s1ahju" : {
item: false
}
any idea ?
It seems like you want to create a plain object, not an array.
In that case:
const cloned_items = Object.assign(...Object.keys(items).map(uid =>
({ [`item-${uid}`]: {item: false} })
));
NB: sorting is of no use when creating an object -- its keys are supposed to have no specific order.
You're creating an array of objects. Seems like you want to use .reduce() to create a single object from the array.
const cloned_items = Object.keys(items).sort().reduce((obj, key) =>
Object.assign(obj, { [`item-${uid}`]: { item: false } })
, {});
Your code doesn't show where uid is coming from, but I assume you meant key there, along with timeslot instead of item.
You may find Object.defineProperty to be cleaner, though you'll need to set up the property descriptor as you want it.
const cloned_items = Object.keys(items).sort().reduce((obj, key) =>
Object.defineProperty(obj, `item-${uid}`, {value:{item: false}})
, {});

find and modify deeply nested object in javascript array

I have an array of objects that can be of any length and any depth. I need to be able to find an object by its id and then modify that object within the array. Is there an efficient way to do this with either lodash or pure js?
I thought I could create an array of indexes that led to the object but constructing the expression to access the object with these indexes seems overly complex / unnecessary
edit1; thanks for all yours replies I will try and be more specific. i am currently finding the location of the object I am trying to modify like so. parents is an array of ids for each parent the target object has. ancestors might be a better name for this array. costCenters is the array of objects that contains the object I want to modify. this function recurses and returns an array of indexes that lead to the object I want to modify
var findAncestorsIdxs = function(parents, costCenters, startingIdx, parentsIdxs) {
var idx = startingIdx ? startingIdx : 0;
var pidx = parentsIdxs ? parentsIdxs : [];
_.each(costCenters, function(cc, ccIdx) {
if(cc.id === parents[idx]) {
console.log(pidx);
idx = idx + 1;
pidx.push(ccIdx);
console.log(pidx);
pidx = findAncestorsIdx(parents, costCenters[ccIdx].children, idx, pidx);
}
});
return pidx;
};
Now with this array of indexes how do I target and modify the exact object I want? I have tried this where ancestors is the array of indexes, costCenters is the array with the object to be modified and parent is the new value to be assigned to the target object
var setParentThroughAncestors = function(ancestors, costCenters, parent) {
var ccs = costCenters;
var depth = ancestors.length;
var ancestor = costCenters[ancestors[0]];
for(i = 1; i < depth; i++) {
ancestor = ancestor.children[ancestors[i]];
}
ancestor = parent;
console.log(ccs);
return ccs;
};
this is obviously just returning the unmodified costCenters array so the only other way I can see to target that object is to construct the expression like myObjects[idx1].children[2].grandchildren[3].ggranchildren[4].something = newValue. is that the only way? if so what is the best way to do that?
You can use JSON.stringify for this. It provides a callback for each visited key/value pair (at any depth), with the ability to skip or replace.
The function below returns a function which searches for objects with the specified ID and invokes the specified transform callback on them:
function scan(id, transform) {
return function(obj) {
return JSON.parse(JSON.stringify(obj, function(key, value) {
if (typeof value === 'object' && value !== null && value.id === id) {
return transform(value);
} else {
return value;
}
}));
}
If as the problem is stated, you have an array of objects, and a parallel array of ids in each object whose containing objects are to be modified, and an array of transformation functions, then it's just a matter of wrapping the above as
for (i = 0; i < objects.length; i++) {
scan(ids[i], transforms[i])(objects[i]);
}
Due to restrictions on JSON.stringify, this approach will fail if there are circular references in the object, and omit functions, regexps, and symbol-keyed properties if you care.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_native_JSON#The_replacer_parameter for more info.
As Felix Kling said, you can iterate recursively over all objects.
// Overly-complex array
var myArray = {
keyOne: {},
keyTwo: {
myId: {a: '3'}
}
};
var searchId = 'myId', // Your search key
foundValue, // Populated with the searched object
found = false; // Internal flag for iterate()
// Recursive function searching through array
function iterate(haystack) {
if (typeof haystack !== 'object' || haystack === null) return; // type-safety
if (typeof haystack[searchId] !== 'undefined') {
found = true;
foundValue = haystack[searchId];
return;
} else {
for (var i in haystack) {
// avoid circular reference infinite loop & skip inherited properties
if (haystack===haystack[i] || !haystack.hasOwnProperty(i)) continue;
iterate(haystack[i]);
if (found === true) return;
}
}
}
// USAGE / RESULT
iterate(myArray);
console.log(foundValue); // {a: '3'}
foundValue.b = 4; // Updating foundValue also updates myArray
console.log(myArray.keyTwo.myId); // {a: '3', b: 4}
All JS object assignations are passed as reference in JS. See this for a complete tutorial on objects :)
Edit: Thanks #torazaburo for suggestions for a better code.
If each object has property with the same name that stores other nested objects, you can use: https://github.com/dominik791/obj-traverse
findAndModifyFirst() method should solve your problem. The first parameter is a root object, not array, so you should create it at first:
var rootObj = {
name: 'rootObject',
children: [
{
'name': 'child1',
children: [ ... ]
},
{
'name': 'child2',
children: [ ... ]
}
]
};
Then use findAndModifyFirst() method:
findAndModifyFirst(rootObj, 'children', { id: 1 }, replacementObject)
replacementObject is whatever object that should replace the object that has id equal to 1.
You can try it using demo app:
https://dominik791.github.io/obj-traverse-demo/
Here's an example that extensively uses lodash. It enables you to transform a deeply nested value based on its key or its value.
const _ = require("lodash")
const flattenKeys = (obj, path = []) => (!_.isObject(obj) ? { [path.join('.')]: obj } : _.reduce(obj, (cum, next, key) => _.merge(cum, flattenKeys(next, [...path, key])), {}));
const registrations = [{
key: "123",
responses:
{
category: 'first',
},
}]
function jsonTransform (json, conditionFn, modifyFn) {
// transform { responses: { category: 'first' } } to { 'responses.category': 'first' }
const flattenedKeys = Object.keys(flattenKeys(json));
// Easily iterate over the flat json
for(let i = 0; i < flattenedKeys.length; i++) {
const key = flattenedKeys[i];
const value = _.get(json, key)
// Did the condition match the one we passed?
if(conditionFn(key, value)) {
// Replace the value to the new one
_.set(json, key, modifyFn(key, value))
}
}
return json
}
// Let's transform all 'first' values to 'FIRST'
const modifiedCategory = jsonTransform(registrations, (key, value) => value === "first", (key, value) => value = value.toUpperCase())
console.log('modifiedCategory --', modifiedCategory)
// Outputs: modifiedCategory -- [ { key: '123', responses: { category: 'FIRST' } } ]
I needed to modify deeply nested objects too, and found no acceptable tool for that purpose. Then I've made this and pushed it to npm.
https://www.npmjs.com/package/find-and
This small [TypeScript-friendly] lib can help with modifying nested objects in a lodash manner. E.g.,
var findAnd = require("find-and");
const data = {
name: 'One',
description: 'Description',
children: [
{
id: 1,
name: 'Two',
},
{
id: 2,
name: 'Three',
},
],
};
findAnd.changeProps(data, { id: 2 }, { name: 'Foo' });
outputs
{
name: 'One',
description: 'Description',
children: [
{
id: 1,
name: 'Two',
},
{
id: 2,
name: 'Foo',
},
],
}
https://runkit.com/embed/bn2hpyfex60e
Hope this could help someone else.
I wrote this code recently to do exactly this, as my backend is rails and wants keys like:
first_name
and my front end is react, so keys are like:
firstName
And these keys are almost always deeply nested:
user: {
firstName: "Bob",
lastName: "Smith",
email: "bob#email.com"
}
Becomes:
user: {
first_name: "Bob",
last_name: "Smith",
email: "bob#email.com"
}
Here is the code
function snakeCase(camelCase) {
return camelCase.replace(/([A-Z])/g, "_$1").toLowerCase()
}
export function snakeCasedObj(obj) {
return Object.keys(obj).reduce(
(acc, key) => ({
...acc,
[snakeCase(key)]: typeof obj[key] === "object" ? snakeCasedObj(obj[key]) : obj[key],
}), {},
);
}
Feel free to change the transform to whatever makes sense for you!

Categories