Related
I have function that loops array and I have four if's - if it match I push value to output array:
const generate = (resources, resourceId) => {
let output = [];
for (let i = 0; i < resources.length; i++) {
if (resources[i].id === resourceId) {
if (CREATE & resources[i].privileges) {
output.push(CREATE);
}
if (READ & resources[i].privileges) {
output.push(READ);
}
if (UPDATE & resources[i].privileges) {
output.push(UPDATE);
}
if (DELETE & resources[i].privileges) {
output.push(DELETE);
}
}
}
return output;
};
I want to change this function to use map - is it possible? I try to do something like this:
const generateUsingMap = (resources, resourceId) => {
return resources.map((resource) => {
if (resource.id === resourceId) {
if (CREATE & resource.privileges) {
return CREATE;
}
if (READ & resource.privileges) {
return READ;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
}
});
};
But in this case I will have only one value, because it returns from first if.
Maybe I need to use another function? I don't want to use for or forEach because in that cases I need to create unnecessary variable.
Update
My function is working in loop, function receive 2 arguments resources and resourceId.
For example variable resources contains:
{
"id": "1",
"name": "Test name 1",
"privileges": 1
},
{
"id": "2",
"name": "Test name 2",
"privileges": 2
},
{
"id": "3",
"name": "Test name 3",
"privileges": 8
},
{
"id": "4",
"name": "Test name 4",
"privileges": 0
},
{
"id": "5",
"name": "Test name 5",
"privileges": 15
}
]
Variable resourceId contains number (id) and receive severally values, for example on first iteration 1, for second 2 and so on.
For resources from example expected output will be:
[1]
[2]
[8]
[]
[1,2,4,8]
You can use reduce if both resource object id and privileges do not exist do not check further just return what you already accrued.
Only if both are present then check the CRUD operations.
const result = resources.reduce((output) => {
if (resources[i].id !== resourceId && !resources[i].privileges) {
return output;
}
if (CREATE) {
output.push(CREATE);
}
if (READ) {
output.push(READ);
}
if (UPDATE) {
output.push(UPDATE);
}
if (DELETE) {
output.push(DELETE);
}
return output;
}, [])
const generateUsingMap = (resources, resourceId) => {
return resources.filter(resource => resource.id === resourceId)
.map((resource) => {
if (CREATE & resource.privileges) {
return CREATE;
}
if (READ & resource.privileges) {
return READ;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
});
};
Create an empty array resultArr. Iterate through resources via forEach and append options to it. Return the array at the end of the function
const generateUsingMap = (resources, resourceId) => {
const resultArr = [];
resources.forEach((resource) => {
if (resource.id === resourceId && resource.privileges) {
if (CREATE) {
resultArr.push(CREATE);
}
else if (READ) {
resultArr.push(READ);
}
else if (UPDATE) {
resultArr.push(UPDATE);
}
else if (DELETE) {
resultArr.push(DELETE);
}
}
});
return resultArr;
};
I am having json object like below which will be dynamic,
let data_existing= [
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
}
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
},
]
From the inputs i will get an object like below,
let data_new = {
"client":[
{
"name":"bbbbb",
"filter":{
"name":"456789"
}
}
]
}
I need to append this object into the existing "client" json object. Expected output will be like,
[
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
},
{
"name":"bbbb",
"filter":{
"name":"456789"
}
}
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
}
]
And, if the "data_new" is not exists in the main objects, it should as new objects like below, for example,
let data_new = {
"server2":[
{
"name":"kkkkk",
"filter":{
"name":"111111"
}
}
]
}
output will be like,
[
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
},
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
},
{
"server2":[
{
"name":"kkkkk",
"filter":{
"name":"11111"
}
}
]
}
]
I tried the below method, but it is not working as expected. Some help would be appreciated.
Tried like below and not worked as expected,
function addData(oldData, newData) {
let [key, value] = Object.entries(newData)[0]
return oldData.reduce((op, inp) => {
if (inp.hasOwnProperty(key)) {
console.log("111");
op[key] = inp[key].concat(newData[key]);
} else {
console.log(JSON.stringify(inp));
op = Object.assign(op, inp);
}
return op
}, {})
}
Your function seems to work when the key already belongs to data_existing (e.g.: "client").
But you have to handle the second use-case: when the key was not found in the objects of data_existing (e.g.: "server2").
This shall be performed after the reduce loop, adding the new item to data_existing if the key was not found.
Here is an example of how you could achieve that:
function addData(inputData, inputItem) {
const [newKey, newValue] = Object.entries(inputItem)[0];
let wasFound = false; // true iif the key was found in list
const res = inputData.reduce((accumulator, item) => {
const [key, value] = Object.entries(item)[0];
const keyMatch = key === newKey;
if (keyMatch) {
wasFound = true;
}
// concatenate the lists in case of key matching
const newItem = { [key]: keyMatch ? [...value, ...newValue] : value };
return [...accumulator, newItem];
}, []);
if (!wasFound) {
res.push(inputItem); // if key was not found, add item to the list
}
return res;
}
Hope it helps.
I am studying the use of reduce in javascript, and I am trying to restructure an Array of Objects in a generic way - need to be dynamic.
flowchart - i get totaly lost
I started with this through.
Every ID becomes a Key.
Every PARENT identifies which Key it belongs to.
i have this:
const in = [
{
"id": "Ball",
"parent": "Futebol"
},
{
"id": "Nike",
"parent": "Ball"
},
{
"id": "Volley",
"parent": null
}
]
i want this
out = {
"Futebol": {
"Ball": {
"Nike": {}
}
},
"Volley": {}
}
i try it - and i had miserably failed.
const tree = require('./mock10.json')
// Every ID becomes a Key.
// Every PARENT identifies which Key it belongs to.
const parsedTree = {}
tree.reduce((acc, item) => {
if (parsedTree.hasOwnProperty(item.parent)){
if (parsedTree[`${item.parent}`].length > 0) {
parsedTree[`${item.parent}`][`${item.id}`] = {}
} else {
parsedTree[`${item.parent}`] = { [`${item.id}`]: {} }
}
} else {
// i get lost in logic
}
}, parsedTree)
console.log(parsedTree)
Got a working code for you, feel free to ask me about the implementation
Hope it helps :)
const arrSample = [
{
"id": "Ball",
"parent": "Futebol"
},
{
"id": "Nike",
"parent": "Ball"
},
{
"id": "Volley",
"parent": null
}
]
const buildTree = (arr) => {
return arr.reduce(([tree, treeMap], { id, parent }) => {
const val = {}
treeMap.set(id, val)
if (!parent) {
tree[id] = val
return [tree, treeMap]
}
if (!treeMap.has(parent)) {
const parentVal = { [id]: val }
treeMap.set(parent, parentVal)
tree[parent] = parentVal
return [tree, treeMap]
}
const newParentValue = treeMap.get(parent)
newParentValue[id] = val
treeMap.set(parent, newParentValue)
return [tree, treeMap]
}, [{}, new Map()])
}
const [result] = buildTree(arrSample)
console.log(JSON.stringify(result, 0, 2))
You could use reduce method for this and store each id on the first level of the object. This solution will work if the objects in the array are in the correct order as in the tree structure.
const data = [{"id":"Futebol","parent":null},{"id":"Ball","parent":"Futebol"},{"id":"Nike","parent":"Ball"},{"id":"Volley","parent":null}]
const result = data.reduce((r, { id, parent }) => {
if (!parent) {
r[id] = {}
r.tree[id] = r[id]
} else if (r[parent]) {
r[parent][id] = {}
r[id] = r[parent][id]
}
return r
}, {tree: {}}).tree
console.log(result)
If reduce solution is just an option, you can try this way:
var input = [
{
"id": "Ball",
"parent": "Futebol"
},
{
"id": "Nike",
"parent": "Ball"
},
{
"id": "Volley",
"parent": null
}
];
var output = {};
input.forEach(item => {
var temp = input.find(x => x.id === item.parent);
if (temp) {
temp[item.id] = {};
}
});
input = input.filter(item => !input.find(x => x.hasOwnProperty(item.id)));
input.forEach(item => {
if (!item.parent) {
output[item.id] = {};
} else {
for (var [id, value] of Object.entries(item)) {
if (typeof value === 'object') {
output[item.parent] = { [item.id]: { id: {} } };
}
}
}
})
console.log(output);
I have tried many things, but none works if we use an Array.prototype.reduce
As there are missing parents, and the elements are out of order, plus the fact that there can be an infinity of levels, I really do not believe that this question can be resolved with a simple reduce
This code should work whatever the cases :
- if all parents are not declared
- if there are infinitely many levels
- if they are in disorder
const origin =
[ { id: 'Ball', parent: 'Futebol' }
, { id: 'Nike', parent: 'Ball' }
, { id: 'Volley', parent: null }
, { id: 'lastOne', parent: 'level4' } // added
, { id: 'level4', parent: 'Nike' } // added
, { id: 'bis', parent: 'Nike' } // added
];
const Result = {} // guess who ?
, Parents = [] // tempory array to keep parents elements address by key names
;
let nbTodo = origin.length // need this one to verify number of elements to track
;
// set all the first levels, add a todo flags
origin.forEach(({id,parent},i,ori)=>
{
ori[i].todo = true // adding todo flag
if (parent===null)
{
Result[id] = {} // new first level element
ori[i].todo = false // one less :)
nbTodo--
Parents.push(({ref:id,path:Result[id]}) ) // I know who you are!
}
else if (origin.filter(el=>el.id===parent).length===0) // if he has no parent...
{
Result[parent] = {} // we create it one
Parents.push({ref:parent,path:Result[parent]} )
}
})
// to put the children back in their parents' arms
while(nbTodo>0) // while there are still some
{
origin.forEach(({id,parent,todo},i,ori)=> // little by little we find them all
{
if(todo) // got one !
{
let pos = Parents.find(p=>p.ref===parent) // have parent already been placed?
if(pos)
{
ori[i].todo = false // to be sure not to repeat yourself unnecessarily
nbTodo-- // one less :)
pos.path[id] = {} // and voila, parentage is done
Parents.push(({ref:id,path:pos.path[id]}) ) // he can now take on the role of parent
}
}
})
}
for (let i=origin.length;i--;) { delete origin[i].todo } // remove todo flags
console.log( JSON.stringify(Result, 0, 2) )
.as-console-wrapper { max-height: 100% !important; top: 0; }
I finaly made this one, based on this previous on, and done with a first step by a reduce...
to by pass the Array of Parents, I made a recursive function for searching each parent elements thru the levels of parsedTree result.
here is the code:
const Tree =
[ { id: 'Ball', parent: 'Futebol' }
, { id: 'Nike', parent: 'Ball' }
, { id: 'Volley', parent: null }
, { id: 'lastOne', parent: 'level4' } // added
, { id: 'level4', parent: 'Nike' } // added
, { id: 'bis', parent: 'Nike' } // added
];
const parsedTree = Tree.reduce((parTree, {id,parent},i ) => {
Tree[i].todo = false
if (parent===null)
{ parTree[id] = {} }
else if (Tree.filter(el=>el.id===parent).length===0) // if he has no parent...
{ parTree[parent] = { [id]: {} } }
else
{ Tree[i].todo = true }
return parTree
}, {})
function parsedTreeSearch(id, part) {
let rep = null
for(let kId in part) {
if (kId===id)
{ rep = part[kId] }
else if (Object.keys(part[kId]).length)
{ rep = parsedTreeSearch(id, part[kId]) }
if (rep) break
}
return rep
}
while (Boolean(Tree.find(t=>t.todo))) {
Tree.forEach(({id,parent,todo},i)=>{ // little by little we find them all
if (todo) {
let Pelm = parsedTreeSearch(parent, parsedTree)
if (Boolean(Pelm)) {
Pelm[id] = {}
Tree[i].todo = false
} } }) }
for (let i=Tree.length;i--;) { delete Tree[i].todo } // remove todo flags
console.log( JSON.stringify( parsedTree ,0,2))
.as-console-wrapper { max-height: 100% !important; top: 0; }
I am working on ReactJS project in which I am simulating a mini filesystem. This is a sample response that I'm getting from server.
Response
const response = {
"file_a.js": "console.log('here')",
"dir_b" : {
"something.txt": "Yada yada"
},
"dir_c": {
"dir_d": {
"file_f.txt": "This is some file"
},
"dir_g": {
"file_h.cpp": "#include<stdio.h>",
"dir_i" : {
"j.java": "prntln"
},
},
},
"dir_none": {},
"dir_z" : {
"dir_x.arr" : "[ 1, 2, 3 ]",
"select.sql": "SELECT * FROM USERS"
}
}
How can I seperate directory and file names like below to show a list of both of them on UI?
Output
const directories = [
"dir_b",
"dir_c",
"dir_c/dir_d",
"dir_c/dir_g",
"dir_c/dir_g/dir_i",
"dir_none",
"dir_z"
]
const files = [
"file_a.js",
"something.txt",
"file_f.txt",
"file_h.cpp",
"j.java",
"dir_x.arr",
"select.sql"
]
What I already have tried
const directories = []
const files = []
Object.keys(response).forEach(path=>{
if(typeof(response[path])==="string") files.push(path)
else directories.push(path)
})
console.log(directories)
console.log(files)
Output
[ "dir_b", "dir_c", "dir_none", "dir_z" ]
[ "file_a.js" ]
Your code isn't too far off, but you'd want the method to be recursive. If it comes across an object, it should append the directory to the list and call itself, passing the child as an argument.
const response = { "file_a.js": "console.log('here')", "dir_b" : { "something.txt": "Yada yada" }, "dir_c": { "dir_d": { "file_f.txt": "This is some file" }, "dir_g": { "file_h.cpp": "#include<stdio.h>", "dir_i" : { "j.java": "prntln" }, }, }, "dir_none": {}, "dir_z" : { "dir_x.arr" : "[ 1, 2, 3 ]", "select.sql": "SELECT * FROM USERS" } }
const directories = []
const files = []
function getFilesAndDirectories(obj, prefix = "") {
Object.keys(obj).forEach(path=>{
if(typeof(obj[path])==="string") files.push(path)
else {
directories.push(prefix + path);
getFilesAndDirectories(obj[path], prefix + path + "/"); //Call itself with child object
}
})
}
getFilesAndDirectories(response);
console.log(directories)
console.log(files)
You can do something like this:
const response = {
"file_a.js": "console.log('here')",
"dir_b": {
"something.txt": "Yada yada"
},
"dir_c": {
"dir_d": {
"file_f.txt": "This is some file"
},
"dir_g": {
"file_h.cpp": "#include<stdio.h>",
"dir_i": {
"j.java": "prntln"
},
},
},
"dir_none": {},
"dir_z": {
"dir_x.arr": "[ 1, 2, 3 ]",
"select.sql": "SELECT * FROM USERS"
}
}
const reduceFn = (a, [key, value]) => {
if (typeof value === 'string') {
a.files.push(key);
} else {
const dir = (a.path ? (a.path + "/") : "") + key;
a.dirs.push(dir);
Object.entries(value).reduce(reduceFn, {...a, path: dir});
}
return a;
}
const result = Object.entries(response).reduce(reduceFn, {
files: [],
dirs: []
});
console.log(result);
This example uses recursion, it reduces the entries of each directory object and uses an accumulator property path to keep track of the depth (the directory structure).
I'm looking for something kind of like Object.keys but that works for potentially nested objects. It also shouldn't include keys that have object/array values (it should only include keys with immediate string/number/boolean values).
Example A
Input
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP"
}
Expected output
[
"check_id",
"check_name",
"check_type"
]
Object.keys would work for flat cases like this, but not for nested cases:
Example B
Input
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
}
}
}
Expected output
[
"check_id",
"check_name",
"check_type",
"check_params.basic_auth",
"check_params.encryption.enabled"
]
Note that this does not include tags, check_params, check_params.params, or check_params.encryption since these values are arrays/objects.
The question
Is there a library that does this? How would you implement it so that it can work with any object, large and nested, or small?
You could use reduce like this:
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return res;
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Edit1: For the general case where you want to include arrays.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"nested": [
{ "foo": 0 },
{ "bar": 1 }
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
A generator makes quick work of this kind of problem -
function* deepKeys (t, pre = [])
{ if (Array.isArray(t))
return
else if (Object(t) === t)
for (const [k, v] of Object.entries(t))
yield* deepKeys(v, [...pre, k])
else
yield pre.join(".")
}
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(Array.from(deepKeys(input)))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
Or a pure functional expression which eagerly computes all keys -
const deepKeys = (t, pre = []) =>
Array.isArray(t)
? []
: Object(t) === t
? Object
.entries(t)
.flatMap(([k, v]) => deepKeys(v, [...pre, k]))
: pre.join(".")
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(deepKeys(input))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
You could check the keys and iterate otherwise push the path to the result set.
function getKeys(object) {
function iter(o, p) {
if (Array.isArray(o)) { return; }
if (o && typeof o === 'object') {
var keys = Object.keys(o);
if (keys.length) {
keys.forEach(function (k) { iter(o[k], p.concat(k)); });
}
return;
}
result.push(p.join('.'));
}
var result = [];
iter(object, []);
return result;
}
var object = { check_id: 12345, check_name: "Name of HTTP check", check_type: "HTTP", tags: ["example_tag"], check_params: { basic_auth: false, params: ["size"], encryption: { enabled: true } } };
console.log(getKeys(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use for...in and create recursive function.
var obj = {"check_id":12345,"check_name":"Name of HTTP check","check_type":"HTTP","tags":["example_tag"],"check_params":{"basic_auth":false,"params":["size",{"a":"b"}],"encryption":{"enabled":true}}}
var keys = []
function getKeys(data, k = '') {
for (var i in data) {
var rest = k.length ? '.' + i : i
if (typeof data[i] == 'object') {
if (!Array.isArray(data[i])) {
getKeys(data[i], k + rest)
}
} else keys.push(k + rest)
}
}
getKeys(obj)
console.log(keys)
var json = {
id: '1234',
test: 'terst',
user : {
name: '',
details: {
address: {
add2: {
prim: "",
sec: ""
},
add1: '',
}
}
},
salary: {
cur: 1234,
net: 89797
},
age: 12
}
let arr = [];
let initialObj = {};
function getKeys(obj, parentK=''){
initialObj = arr.length === 0 ? obj: initialObj;
const entries = Object.entries(obj);
for(let i=0; i<entries.length; i++) {
const key = entries[i][0];
const val = entries[i][1];
const isRootElement = initialObj.hasOwnProperty(key);
parentK = isRootElement ? key: parentK+'.'+key;
arr.push(parentK)
if(typeof val === 'object' && val!==null && !Array.isArray(val)){
getKeys(val, parentK);
}
}
}
getKeys(json)
console.log('arr final---', arr);
Further enhanced above recommendation to return all keys including array.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return [...res,`${el}: ${obj[el].toString()}`];
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res,...keyify(obj[el],`${prefix}${el}.`)];
}
return [...res,`${prefix}${el}: ${obj[el]}`];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Expected output:
[
'check_id: 12345',
'check_name: Name of HTTP check',
'check_type: HTTP',
'tags: example_tag',
'check_params.basic_auth: false',
'params: size',
'check_params.encryption.enabled: true',
'check_params.encryption.testNull: null'
]
Is this what you mean?
http://jsfiddle.net/robbiemilejczak/hfe12brb/1/
I couldn't do it with vanilla JS, and this is a pretty hacky solution that relies on lodash. Basically leverages lodashs _.forIn and _.isArray functions to iterate over an object. Also this will only go 1 layer deep, so objects inside of nested objects will be ignored. It does produce your expected output though, so I'd say it's a decent starting point.