I do a simple explorer on Angular (there are a list of directories that contain other directories or text files). The question is: I receive the following data from the server ("path" is the path of the folder, ids of parent directories):
[
{
id: "6np5E3yyEISXLNX9muyt",
name: "sec list",
path: ["", "GnBOclNO1v3n9FW7aGv0", "X5YNJ6Vco2BtGxNZVsYV"],
},
{
id: "GnBOclNO1v3n9FW7aGv0",
name: "In aeroport",
path: [""],
},
{
id: "H6AvpwXc49v4oDRWSjym",
name: "Delete",
path: [""],
},
{
id: "LQ73vVoTuw9xd40jMs3j",
name: "Aeroport list",
path: [""],
},
{
id: "X5YNJ6Vco2BtGxNZVsYV",
name: "Bordery words",
path: ["", "GnBOclNO1v3n9FW7aGv0"],
},
{
id: "jWeClRAw55Er8z0Ow9uq",
name: "mail list",
path: ["", "GnBOclNO1v3n9FW7aGv0", "X5YNJ6Vco2BtGxNZVsYV"],
}
];
How can I regroup that into code below? I know recursion is needed, but I can not understand, how to do it right. Help me, please.
[
{
id: "GnBOclNO1v3n9FW7aGv0",
name: "In aeroport",
children: [
{
id: "X5YNJ6Vco2BtGxNZVsYV",
name: "Bordery words",
children: [
{
id: "6np5E3yyEISXLNX9muyt",
name: "sec list",
},
{
id: "jWeClRAw55Er8z0Ow9uq",
name: "mail list",
}
],
}
],
},
{
id: "H6AvpwXc49v4oDRWSjym",
name: "Delete",
},
{
id: "LQ73vVoTuw9xd40jMs3j",
name: "Aeroport list",
},
]
Simple DFS solves the problem, but there are multiple ways to do this. One way is below
var paths = [
{
id: "6np5E3yyEISXLNX9muyt",
name: "sec list",
path: ["", "GnBOclNO1v3n9FW7aGv0", "X5YNJ6Vco2BtGxNZVsYV"],
},
{
id: "GnBOclNO1v3n9FW7aGv0",
name: "In aeroport",
path: [""],
},
{
id: "H6AvpwXc49v4oDRWSjym",
name: "Delete",
path: [""],
},
{
id: "LQ73vVoTuw9xd40jMs3j",
name: "Aeroport list",
path: [""],
},
{
id: "X5YNJ6Vco2BtGxNZVsYV",
name: "Bordery words",
path: ["", "GnBOclNO1v3n9FW7aGv0"],
},
{
id: "jWeClRAw55Er8z0Ow9uq",
name: "mail list",
path: ["", "GnBOclNO1v3n9FW7aGv0", "X5YNJ6Vco2BtGxNZVsYV"],
}
];
var dfs = function( parentJson , path){
for(var i=0;i<paths.length;i++){
if(paths[i].path.join("") == path ){
var child = {id:paths[i].id,name:paths[i].name,children:[]}
parentJson.push(child)
dfs(child.children,path+paths[i].id)
}
}
}
var json = [];
dfs(json,"")
console.log(json)
You could do this in two phases:
First make a nested object structure where the children properties are not arrays, but objects, where the object keys are the id values. That way you can quickly navigate in that structure with a given path, and extend/deepen it at the same time.
In a final phase, you can use recursion to walk through that tree structure to convert those children properties to arrays.
Here is how that looks:
function makeTree(data) {
// drill down the object structure, where children properties
// are nested objects with id values as keys.
let result = {}; // root of the tree data structure
for (let {id, name, path} of data) {
Object.assign(path.slice(1).concat(id).reduce((acc, key) => {
if (!acc.children) acc.children = {};
if (!acc.children[key]) acc.children[key] = {};
return acc.children[key];
}, result), { id, name });
}
return (function unkey(node) {
// Convert children objects to arrays
if (node.children) node.children = Object.values(node.children).map(unkey);
return node;
})(result);
}
let data = [{id: "6np5E3yyEISXLNX9muyt",name: "sec list",path: ["", "GnBOclNO1v3n9FW7aGv0", "X5YNJ6Vco2BtGxNZVsYV"],},{id: "GnBOclNO1v3n9FW7aGv0",name: "In aeroport",path: [""],},{id: "H6AvpwXc49v4oDRWSjym",name: "Delete",path: [""],},{id: "LQ73vVoTuw9xd40jMs3j",name: "Aeroport list",path: [""],},{id: "X5YNJ6Vco2BtGxNZVsYV",name: "Bordery words",path: ["", "GnBOclNO1v3n9FW7aGv0"],},{id: "jWeClRAw55Er8z0Ow9uq",name: "mail list",path: ["", "GnBOclNO1v3n9FW7aGv0", "X5YNJ6Vco2BtGxNZVsYV"],}];
console.log(makeTree(data));
Note that the first value of the path value is never used. It seems to always be the empty string.
Related
I have a state variable projects which should store a dictionary of arrays, where the key is id of the organisation that owns the project, and the array consists of objects storing information about the project. For example:
{
orgId123: [
project1: {
name: "my cool project",
status: "submitted"
},
projectAwesome: {
name: "Stan's project",
status: "draft"
}
],
orgUSA: [
newProj234: {
name: "another great project",
status: "submitted"
}
]
}
I try to get a list of all organisation IDs using Objects.keys(projects), however that returns an empty array.
I suspect that somehow my projects variable is structured wrongly. When I console.log the contents of projects, I get:
Notice how the root level object says just {}.
When I tried to re-create what the projects variable should look like and logged that, I saw a slightly different output:
In this manually created object, the root-level object is shown as {orgId1}: Array(1) instead of the previously-shown {} (on the actual object).
What does this say about how the object is structured and why can I not get a list of keys from the first object using Object.keys()?
For context, I create the original variable using the following code:
async function fetchProjects() {
// Load list of organisations that the user is a member of
const organisationsSnapshot = await getDocs(query(
collection(db, 'organisations'),
where(`members.${user.uid}`, '!=', null)
))
const organisations = organisationsSnapshot.docs.map(organisationSnap => ({
...organisationSnap.data(),
id: organisationSnap.id
}))
// Load list of projects for each organisation the user is a member of
const projectsDict = {}
organisations.forEach(async (organisation) => {
const projectsQuery = query(collection(db, `organisations/${organisation.id}/projects`))
const projectsSnap = await getDocs(projectsQuery)
projectsDict[organisation.id] = projectsSnap.docs.map(projectSnap => ({
...projectSnap.data(),
id: projectSnap.id
}))
})
setProjects(projectsDict)
}
You cannot have an array of key:values. You should wrap it in {}.
[ key1: value1, key2: value2, ] //Unexpected Token
[ { key1: value1 }, { key2: value2 }, ] //Good to go
So instead of:
{
orgId123: [
project1: {
name: "my cool project",
status: "submitted"
},
projectAwesome: {
name: "Stan's project",
status: "draft"
}
],
orgUSA: [
newProj234: {
name: "another great project",
status: "submitted"
}
]
}
You should have:
{
orgId123: [
{
project1: {
name: "my cool project",
status: "submitted"
}
},
{
projectAwesome: {
name: "Stan's project",
status: "draft"
}
}
],
orgUSA: [
{
newProj234: {
name: "another great project",
status: "submitted"
}
}
]
}
OR
{
orgId123: {
project1: {
name: "my cool project",
status: "submitted"
},
projectAwesome: {
name: "Stan's project",
status: "draft"
}
},
orgUSA: {
newProj234: {
name: "another great project",
status: "submitted"
}
}
}
Honestly, I would structure your projects as follows:
const organisations = [{
orgId: "orgId123",
projects: [{
projectId: "project1",
name: "my cool project",
status: "submitted"
}, {
projectId: "projectAwesome",
name: "Stan's project",
status: "draft"
}]
},
{
orgId: "orgUSA",
projects: [{
projectId: "newProj234",
name: "another great project",
status: "submitted"
}]
}
]
//This way, organisations is an array of organisations,
//which is an object that has orgId, projects which is an array of its projects.
//It will be much more intuitive to work with while iterating over it.
//Such as if you need to display all the orgIds,
console.log("Organisation IDs:")
for (const org of organisations) {
console.log(org.orgId)
}
console.log("=================");
//If you need all project IDs and names:
console.log("Projects:")
for (const org of organisations) {
console.log(`Organisation ${org.orgId} has the following projects:`)
for (const proj of org.projects) {
console.log(`Project ID ${proj.projectId}: ${proj.name}`)
}
console.log("=================");
}
console.log("=================");
I would like to transform my object into nested object in order to be able to render inputs recursively in react (if input has sub input render below this inputs) Sometimes inputs are in wrong order so I have to transform object like this
const start = {
asdf: {
id: "asdf",
question: "How old are you?",
type: "text"
},
"asdf/zxcv": {
id: "asdf/zxcv",
expect: 18,
question: "Are you male?",
type: "text"
},
"asdf/zxcv/yuio": {
id: "asdf/zxcv/yuio",
expect: "yes",
question: "Do you like videogames?",
type: "text"
},
"asdf/dfgh": {
id: "asdf/dfgh",
expect: 21,
question: "Where do you live?",
type: "text"
},
"asdf/dfgh/fghj": {
id: "asdf/dfgh/fghj",
expect: "Boston",
question: "What's the weather?",
type: "text"
},
qwer: {
id: "qwer",
question: "What is your name?",
type: "text"
},
"qwer/asdf": {
id: "qwer/asdf",
expect: "David",
question: "What is your surname",
type: "text"
}
};
into something like this
const result = {
asdf: {
id: "asdf",
question: "How old are you?",
type: "text",
subs: [
{
id: "asdf/zxcv",
expect: 18,
question: "Are you male?",
type: "text",
subs: [
{
id: "asdf/zxcv/yuio",
expect: "yes",
question: "Do you like videogames?",
type: "text"
}
]
},
{
id: "asdf/dfgh",
expect: 21,
question: "Where do you live?",
type: "text",
subs: [
{
id: "asdf/dfgh/fghj",
expect: "Boston",
question: "What's the weather?",
type: "text"
}
]
}
]
},
qwer: {
id: "qwer",
question: "What is your name?",
type: "text",
subs: [
{
id: "qwer/asdf",
expect: "David",
question: "What is your surname",
type: "text"
}
]
}
};
I can create the first level of objects but I don't know how to add dynamically object to subs if needed
This is my try. It doesn't work.
const createObjToRender = object => {
Object.values(object)
.filter(o => o)
.reduce((obj, el) => {
const idHistory = el.id.split("/");
if (idHistory.length > 1) {
let elToAddSubInput = obj;
for (let i = 0; i <= idHistory.length; i++) {
i === 0 && (elToAddSubInput = elToAddSubInput[idHistory[i]]);
i > 0 && (elToAddSubInput = elToAddSubInput.subs[idHistory[i]]);
}
elToAddSubInput.subs[idHistory.join("/")] = el;
}
if (idHistory.length === 1) {
const id = idHistory[0];
const { question, type } = el;
if (!obj[id]) {
obj[id] = {
id,
question,
type,
subs: {}
};
}
}
}, {});
};
You could use a nested hashtable for building the tree fast:
const start = {
asdf: {
id: "asdf",
question: "How old are you?",
type: "text"
},
"asdf/zxcv": {
id: "asdf/zxcv",
expect: 18,
question: "Are you male?",
type: "text"
},
"asdf/zxcv/yuio": {
id: "asdf/zxcv/yuio",
expect: "yes",
question: "Do you like videogames?",
type: "text"
},
"asdf/dfgh": {
id: "asdf/dfgh",
expect: 21,
question: "Where do you live?",
type: "text"
},
"asdf/dfgh/fghj": {
id: "asdf/dfgh/fghj",
expect: "Boston",
question: "What's the weather?",
type: "text"
},
qwer: {
id: "qwer",
question: "What is your name?",
type: "text"
},
"qwer/asdf": {
id: "qwer/asdf",
expect: "David",
question: "What is your surname",
type: "text"
}
};
// We use a symbol to allow fast lookups while still having a resulting array without it
const lookup = Symbol();
const root = { [lookup]: {}, sub: [] };
// As the ids are part of the values itself, we can ignore the objects keys and directly iterate the values
for(const el of Object.values(start)) {
// Now we traverse down the nested lookup tree
let parent = root;
for(const part of el.id.split("/")) {
// If a certain path doesnt exist yet, set it up
if(!parent[lookup][part])
parent.sub.push(parent[lookup][part] = { [lookup]: {}, sub: [] });
// Dive deeper
parent = parent[lookup][part];
}
// We reached the node were the data belongs, so just assign it here:
Object.assign(parent, el);
}
// you could also get it as an array with root.sub
console.log(root[lookup]);
I browsed So many questions to find out logics for finding Index in a deeply nested array of object, I didn't find it useful for my requirement though.
in search of Solution in Javascript, Lodash/Underscore would be Fine too.
Let me just phrase out the Whole requirement, Hoping I get a path to find the solution for this issue.
Requirement:
I have an array of objects
arrObj =[
{
"id":3208,
"name":"List",
"issueResponses":[
],
"isActive":false
},
{
"id":3209,
"name":"Me",
"issueResponses":[
],
"isActive":false
},
{
"id":3314,
"name":"SNew",
"issueResponses":[
],
"isActive":false
},
{
"id":3315,
"name":"Olive",
"issueResponses":[
{
"id":3282,
"name":"related to Olive",
"issueResponses":[
],
"isActive":false
},
{
"id":3316,
"name":"My olives are not yet picked",
"issueResponses":[
{
"id":3317,
"name":"Pickup Not Done",
"issueResponses":[
],
"isActive":false
}
]
}
]
}
]
As we can see its deeply nested, I have another array
delValue = [3317,3282], And tomorrow it might be anything in these Deep Nesting.
I have to find these delValue arrays in arrObj and delete all those Objects which has "id" as these Values.
I am trying to solve this in a generic way which can support any number deep search Level and Deletion of the Object from that.
Please help me out in this, if More Information needed will be happy to provide.
You can use filter to achieve this:
function removeDeletions(array, deletion) {
return array.filter(el => {
if (Array.isArray(el.issueResponses)) el.issueResponses = removeDeletions(el.issueResponses, deletion);
return ! deletion.includes(el.id);
})
}
DEMO:
let arrObj = [{
"id": 3208,
"name": "List",
"issueResponses": [
],
"isActive": false
},
{
"id": 3209,
"name": "Me",
"issueResponses": [],
"isActive": false
},
{
"id": 3314,
"name": "SNew",
"issueResponses": [
],
"isActive": false
},
{
"id": 3315,
"name": "Olive",
"issueResponses": [{
"id": 3282,
"name": "related to Olive",
"issueResponses": [
],
"isActive": false
},
{
"id": 3316,
"name": "My olives are not yet picked",
"issueResponses": [{
"id": 3317,
"name": "Pickup Not Done",
"issueResponses": [
],
"isActive": false
}]
}
]
}
]
let delValue = [3317, 3282];
function removeDeletions(array, deletion) {
return array.filter(el => {
if (Array.isArray(el.issueResponses)) el.issueResponses = removeDeletions(el.issueResponses, deletion);
return !deletion.includes(el.id);
})
}
console.log(removeDeletions(arrObj, delValue));
Just keep checking issueResponses - like so:
function deleteIssueResponses(issues, ids) {
for (let i = issues.length; i >= 0; i--) {
if (issues[i].issueResponses.length) {
deleteIssueResponses(issues[i].issueResponses, ids);
}
if (ids.contains(issues[i].id)) {
issues.splice(i, 1);
}
}
}
And call it:
deleteIssueResponses(arrObj, [3317,3282]);
This should be quite simple to solve with some recursion. This function will delete the given ids in the array passed to it, or else call itself to do the same for the nested arrays.
function deleteIds(arr, ids) {
for (let i = 0; i < arr.length; i++) {
if (ids.indexOf(arr[i].id) !== -1) {
arr.splice(i, 1);
i--;
} else {
deleteIds(arr[i].issueResponses, ids);
}
}
}
Call with deleteIds(arrObj, delValue) as in your question.
We use object-scan for basic data processing tasks like this. Once you wrap your head around how to use it, it's pretty good and powerful. Here is how you could answer your questions:
// const objectScan = require('object-scan');
const prune = (values, data) => objectScan(['**[*].id'], {
rtn: 'count',
filterFn: ({ gparent, gproperty, value }) => {
if (values.includes(value)) {
gparent.splice(gproperty, 1);
return true;
}
return false;
}
})(data);
const arrObj = [{ id: 3208, name: 'List', issueResponses: [], isActive: false }, { id: 3209, name: 'Me', issueResponses: [], isActive: false }, { id: 3314, name: 'SNew', issueResponses: [], isActive: false }, { id: 3315, name: 'Olive', issueResponses: [{ id: 3282, name: 'related to Olive', issueResponses: [], isActive: false }, { id: 3316, name: 'My olives are not yet picked', issueResponses: [{ id: 3317, name: 'Pickup Not Done', issueResponses: [], isActive: false }] }] }];
console.log(prune([3317, 3282], arrObj)); // return number of deletes
// => 2
console.log(arrObj);
// => [ { id: 3208, name: 'List', issueResponses: [], isActive: false }, { id: 3209, name: 'Me', issueResponses: [], isActive: false }, { id: 3314, name: 'SNew', issueResponses: [], isActive: false }, { id: 3315, name: 'Olive', issueResponses: [ { id: 3316, name: 'My olives are not yet picked', issueResponses: [] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#15.0.0"></script>
Disclaimer: I'm the author of object-scan
I have something like this :
let user = [
{
name: "step-one",
values: {companyName: "Name", address: "company address"}
},
{
name: "step-two",
values: {name: "User", mobile: 0123}
},
{
name: "step-three",
values: [
{file: "companyLogo", values: {active: true, fileName: "some name"}},
{file: "avatar", values: {active: true, fileName: "file name"}}
]
}
]
I want to get only values and put them into a new object. Thus, something like :
let wantedResult = {
companyName: "Name",
address: "company address",
name: "User",
mobile: 0123,
files: [
{file: "companyLogo", values: {active: false, fileName: "some name"}},
{file: "avatar", values: {active: false, fileName: "file name"}}
]
};
Any advice how I can do that?
You can try this!
let user = [{
name: "step-one",
values: {
companyName: "Name",
address: "company address"
}
}, {
name: "step-two",
values: {
name: "User",
mobile: 0123
}
}, {
name: "step-three",
values: [{
file: "companyLogo",
values: {
active: true,
fileName: "some name"
}
}, {
file: "avatar",
values: {
active: true,
fileName: "file name"
}
}]
}]
var wantedResult = Object.assign({}, user[0].values, user[1].values, {files: user[2].values})
console.log(wantedResult)
It is a bit hacky because of the files array, but i would do it like that:
var wantedResult = user.reduce((result, step) => {
var values = Array.isArray(step.values) ? { files: step.values } : step.values;
return Object.assign({}, result, values)
}, {});
Of course it'll work only for that kind of structure you provided. If you have more objects that have an array in the 'values' property, you'll need to rethink the approach.
Step 3 is inconsistent with the others. If possible, make it consistent to have: values: files: [ { file1_data }, { file2_data } ].
After fixing the inconsistency, you can iterate through each of the steps and add the new properties to the result.
let wantedResult = user.reduce(function(result, spec) {
Object.keys(spec.values).forEach(function(key) {
result[key] = spec.values[key];
});
return result;
}, {});
If not possible to change step 3, you can make it a bit less clean:
let wantedResult = user.reduce(function(result, spec) {
if (spec.name === "step-three") {
result.files = spec.values;
}
else {
Object.keys(spec.values).forEach(function(key) {
result[key] = spec.values[key];
});
}
return result;
}, {});
I'm just starting with immutable.js and I'm having trouble figuring out how to set a new property on objects within an array. I'm having trouble finding any examples in the docs of this kind of change.
I'm basically just trying to take change this:
[{
gitInfo: {id: 8001, host: '', …},
module: {id: 24875, name: "blah", …}
}...]
to this:
[{
gitInfo: {id: 8001, host: '', …},
module: {id: 24875, name: "blah", isStared: true …}
}...]
So w/out immutable.js I would have something like:
function markModules(modules) {
modules.map( (module) => {
module.module.isStarred = false;
if (contains(this.props.stars, module.module.id)) {
module.module.isStarred = true;
}
})
return modules;
}
I'm assuming I need something like set() with a List, but again, I'm not finding any examples how to do this.
Thanks for any tips or links to examples.
You'd do it the same way you would without Immutable.js (.map).
const data = Immutable.fromJS([{
gitInfo: {id: 8001, host: ''},
module: {id: 24875, name: "blah"}
}, {
gitInfo: {id: 6996, host: ''},
module: {id: 666, name: "wef"}
}]);
const transformed = data.map((x) => {
return x.get('module').set('isStarred', true);
})
transformed.toJS() === [
{
"id": 24875,
"name": "blah",
"isStarred": true
},
{
"id": 666,
"name": "wef",
"isStarred": true
}
]
And if you want to put the extra logic in there:
function markModules(modules) {
return modules.map((module) => {
const isStarred = contains(this.props.stars, module.getIn(['module', 'id']));
return module.setIn(['module', 'isStarred'], isStarred);
})
}
The key is to turn your if statements into values/functions that return values instead of updating the data structure.