Related
I created a submenu from the first keys of the "res" data before the dot. In this submenu, I showed the keys with the same name in the "res" data only once. ie: "user", "department" and "project" appear as just a submenu item. But when I click on them, other keys with the same name do not appear once. For example: When I click on the "project" item, 5 "type" submenus are opened. And I need to do the same for the "id" key, too. How can I solve this?
var res = {
"user.name.firstname": "firstname",
"user.name.lastname": "lastname",
"department.id1": 1,
"department.id2": 2,
"project.name": "project",
"project.type.name": "project1",
"project.type.id.first3": "321",
"project.type.id.last3" : "789",
"project.type.id.abc": "abc",
}
var myList = []
var myFinalList = []
Object.keys(res).forEach(key => {
var subMenus = key.split(".")
subMenus.forEach(subMenu => {
var newObject = {}
newObject["id"] = subMenu
newObject["content"] = subMenu
myList.push(newObject)
})
for (var i = myList.length - 1; i >= 0; i--) {
if (i - 1 !== -1) {
//console.log(i - 1)
myList[i - 1]["submenu"] = [myList[i]]
}
}
myFinalList.push(myList[0])
//console.log(myList)
myList = []
})
var mySet = new Set()
myFinalList.forEach(obj => {
mySet.add(obj["id"])
})
var veryFinalList = []
mySet.forEach(key => {
var eachKeyObject = myFinalList.filter(sub => sub.id === key)
var sourceObject = eachKeyObject[0]
if(eachKeyObject.length > 1){
for(var i = 1; i < eachKeyObject.length; i++){
sourceObject["submenu"] = sourceObject["submenu"].concat(eachKeyObject[i]["submenu"])
}
}
veryFinalList.push(sourceObject)
console.log(veryFinalList)
})
And my output should be like this:
output= {
"user": {
"name": {
"firstname": "firstname",
"lastname": "lastname",
}
},
"department": {
"id1":1,
"id2":2,
},
"project": {
"name": "project",
"type": {
"name": "project1",
"id": {
"first3": "321",
"last3" : "789",
"abc" : "abc"
}
}
}
}
Here you go:
var res = {
"user.name.firstname": "firstname",
"user.name.lastname": "lastname",
"department.id1": 1,
"department.id2": 2,
"project.name": "project",
"project.type.name": "project1",
"project.type.id.first3": "321",
"project.type.id.last3" : "789",
"project.type.id.abc": "abc",
}
let finalObj = { };
for(let key in res) {
let value = res[key];
let keyArr = key.split('.');
// Magic happens here (https://stackoverflow.com/a/68860247/2284136)
keyArr.reduce((acc, key, i) => {
if (i < keyArr.length - 1) {
if (!acc[key]) acc[key] = {};
}
else {
acc[key] = value;
}
return acc[key];
}, finalObj)
}
console.log(finalObj);
I used this answer for the reduce implementation, which allows us to put data deep into an object path. We just have to split the keys into and array first, like "user.name.firstname" => ["user", "name", "firstname"] and feed that to the reduce function with the value of res["user.name.firstname"].
Regarding your actual question about "converting a method to recursive function", I did try something like that at first, until I realized we don't need to do that here.
Also for a future reference, while recursive functions are great for some specific problems, the rule of thumb in general is to avoid having to use them if you don't absolutely have to. They are hard to read and understand, hard to debug and can lead to all kinds of wacky problems.
And regarding what you had tried to do to solve this. I think it's a great try and that is exactly what a great programmer should do when running into trouble! I love reading code like that because I can see the rabbit hole of frustration you were digging yourself into when trying to solve the problem. While it is indeed greatly frustrating, it also just as great learning experience. I personally think that when you find yourself from the bottom of that hole, still without a solution, that is the best time to ask for help, like you did!
This is my array:
const
array1 = [
{
"value": "0",
"name": "5",
"waste": "remove",
"city": "NY"
},
{
"value": "0",
"name": "51",
"waste": "remove",
}
]
So now, i wanted to remove certain and form a new array with objects: For example, i need to remove "Waste & value" and keep rest of the things, so i used this code:
var keys_to_keep = ['name', 'city']
const result = array2.map(e => {
const obj = {};
keys_to_keep.forEach(k => obj[k] = e[k])
return obj;
});
console.log(result)
And it gives a output as
[ { name: '5', city: 'NY' }, { name: '51', city: undefined } ]
Now as you can see city with undefined value, how to remove that ? i mean filter this and just show keys with value,
So my question is how to filter undefined and also is there any other better solution for removing unwanted object keys and showing new array with wanted keys ? or the method am using is performant enough ?
You can check if the value is undefined in your forEach:
const result = array2.map(e => {
const obj = {};
keys_to_keep.forEach(k => {
if (undefined !== e[k]) {
obj[k] = e[k]
}
)
return obj;
});
You can check if e[k] is defined before you add it to obj by checking whether the e object has the property k using .hasOwnProperty():
const array = [{ "value": "0", "name": "5", "waste": "remove", "city": "NY" }, { "value": "0", "name": "51", "waste": "remove", } ];
const keys_to_keep = ['name', 'city'];
const result = array.map(e => {
const obj = {};
keys_to_keep.forEach(k => {
if (e.hasOwnProperty(k))
obj[k] = e[k]
});
return obj;
});
console.log(result)
If the keys you want to remove aren't dynamic, you can also use destructuring assignment to pull out the properties you want to discard, and use the rest syntax to obtain an object without those properties:
const array = [{ "value": "0", "name": "5", "waste": "remove", "city": "NY" }, { "value": "0", "name": "51", "waste": "remove", } ];
const result = array.map(({value, waste, ...r}) => r);
console.log(result)
I am going to answer both the parts. So here are the steps to do that.
Use map() on the main array.
Get entries of each object using Object.entries().
Apply filter() on entires array are remove those entires for which key is not present in keys_to_keep
Now for the second part.
Using keys_to_keep create an object which contain undefined values for each key.
Use map() again on prev result and use Spread operator. First spread the object created above and then spread the original values. This way if any key is not found it will be set to undefined
const
array1 = [
{
"value": "0",
"name": "5",
"waste": "remove",
"city": "NY"
},
{
"value": "0",
"name": "51",
"waste": "remove",
}
]
var keys_to_keep = ['name', 'city']
let obj = Object.fromEntries(keys_to_keep.map(x => [x, undefined]));
const res = array1.map(obj =>
Object.fromEntries(
Object.entries(obj).filter(([k, v]) => keys_to_keep.includes(k))))
.map(x => ({...obj, ...x}))
console.log(res)
You can use .map to iterate over the objects, Object.entries to get the key-value pairs of each item, Object.fromEntries to group them into the resulting objects, and .filter to get only the entries with a key in keys_to_keep and a value that is not undefined:
const array1 = [
{ "value": "0", "name": "5", "waste": "remove", "city": "NY" },
{ "value": "0", "name": "51", "waste": "remove" }
];
var keys_to_keep = ['name', 'city'];
const result = array1.map(item =>
Object.fromEntries(
Object.entries(item).filter(([key, value]) =>
keys_to_keep.includes(key) && value !== undefined
)
)
);
console.log(result)
There is an array of objects
const data = [{
"name": "08/20/2018",
"id": "name_1"
}, {
"name": "12/23/2018",
"id": "name_2"
}]
and I would like to map this array of objects in order to get just array
["Date 1","08/20/2018","Date 2","12/23/2018"]
I'm trying using .map()
data.map((d, i) =>
`${'Date ' + i}`
d.name
)];
but cannot map name with the first (d) parameter.
Because the input items and output array items aren't one-to-one, you won't be able to use .map. Use reduce instead:
const data = [{
"name": "08/20/2018",
"id": "name_1"
}, {
"name": "12/23/2018",
"id": "name_2"
}];
const output = data.reduce((a, { name }, i) => {
a.push('Date ' + (i + 1), name);
return a;
}, []);
console.log(output);
Or .flatMap:
const data = [{
"name": "08/20/2018",
"id": "name_1"
}, {
"name": "12/23/2018",
"id": "name_2"
}];
const output = data.flatMap(({ name }, i) => (['Date ' + (i + 1), name]));
console.log(output);
(note that since arrays are zero-indexed, you'll have to use i + 1, not i, if you want the first item in the output array to start at 1 instead of 0)
You can't use map since that method produce a new array with the same number of items of the original ones.
However, you can use flatMap (where supported) to achieve the your desired result:
data.flatMap(({name}, i) => [`Date ${i + 1}`, name]);
console.log(data) // [ "Date 1", "08/20/2018", "Date 2", "12/23/2018" ]
Basically flatMap is like calling map and then flat; therefore if from the callback function we returns an array per item, this array will be flattened before returned.
Regular map call would have been produced [[ "Date 1", "08/20/2018"], ["Date 2", "12/23/2018"]] instead.
Try to combine map and flatmap methods in order to achieve desired result:
const data = [{
"name": "08/20/2018",
"id": "name_1"
}, {
"name": "12/23/2018",
"id": "name_2"
}];
const result = data.map((s, i)=> [`Date ${i}`, s.name]).flatMap(f=> f);
console.log(result)
or using flat method:
const data = [{
"name": "08/20/2018",
"id": "name_1"
}, {
"name": "12/23/2018",
"id": "name_2"
}];
const result = data.map((s, i)=> [`Date ${i}`, s.name]).flat(1);
console.log(result)
One line answer using ES2019 Array.flat :
data.map((item,index)=>([`Date${index+1}`,item.name])).flat();
But in my opinion, it is not optimized when there is huge data.
I appreciate above answers but if you still prefer to use .map() method to accomplish your work, you can do it.
Just with an additional use of concat() method with map() method. Let's see how.
I have used ...data,map() statement where ... is used for Array destructuring. More information can be found at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring.
const data = [
{
"name": "08/20/2018",
"id": "name_1"
},
{
"name": "12/23/2018",
"id": "name_2"
}
]
output = new Array() // or just []
output = output.concat(...data.map((obj, index) => [`Date ${index + 1}`, obj.name]))
console.log(output)
// [ 'Date 1', '08/20/2018', 'Date 2', '12/23/2018' ]
Screenshot
I'm using an api that return this object:
{
"0155894402285712": { "type": "GBUserFieldText", "value": "A0242", "name": "Codice+tessera" },
"0155894402283800": { "type": "GBUserFieldText", "value": "LZZMRN55L53C003Z", "name": "Codice+Fiscale" }
}
I need to extract the value A0242 and LZZMRN55L53C003Z but the only things that I know are the name "Codice+tessera" and "Codice+fiscale". How can I read these values? Maybe my question is stupid but really I'm losing my brain today...
Thanks
You can try this:
const data = {
"0155894402285712": {
"type": "GBUserFieldText",
"value": "A0242",
"name": "Codice+tessera"
},
"0155894402283800": {
"type": "GBUserFieldText",
"value": "LZZMRN55L53C003Z",
"name": "Codice+Fiscale"
}
};
// get an array of all the values of this data.
const arrayOfValues = Object.values(data);
// filter this array in order to find the one which the name you want
const selectedObj = arrayOfValues.find(obj => obj.name === 'Codice+tessera');
// get the value of this object.
const selectedValue = selectedObj.value;
console.log(selectedValue);
// You can also make a function findValueOf(name):
const findValueOf = name =>
arrayOfValues.find(obj => obj.name === name) &&
arrayOfValues.find(obj => obj.name === name).value;
// and use it for example:
console.log(findValueOf('Codice+tessera')); // "A0242"
console.log(findValueOf('Codice+Fiscale')); // "LZZMRN55L53C003Z"
You can use Object.values and then access value key's value
let obj = {
"0155894402285712": { "type": "GBUserFieldText", "value": "A0242", "name": "Codice+tessera" },
"0155894402283800": { "type": "GBUserFieldText", "value": "LZZMRN55L53C003Z", "name": "Codice+Fiscale" }
}
Object.values(obj).forEach(({value})=>{
console.log(value)
})
You can use Object.values to convert the object into an array. Use map to loop and get the value
var obj = {"0155894402285712":{"type":"GBUserFieldText","value":"A0242","name":"Codice+tessera"},"0155894402283800":{"type":"GBUserFieldText","value":"LZZMRN55L53C003Z","name":"Codice+Fiscale"}}
var resut = Object.values(obj).map(o => o.value);
console.log(resut);
If you want to match the name and value, you can use reduce
var obj = {"0155894402285712":{"type":"GBUserFieldText","value":"A0242","name":"Codice+tessera"},"0155894402283800":{"type":"GBUserFieldText","value":"LZZMRN55L53C003Z","name":"Codice+Fiscale"}}
var resut = Object.values(obj).reduce((c, v) => Object.assign(c, {[v.name]: v.value}), {});
console.log(resut);
If you are looking to find A0242 from the value "Codice+tessera" that you have, you need something different than the other answers:
var data = {
"0155894402285712": { "type": "GBUserFieldText", "value": "A0242", "name": "Codice+tessera" },
"0155894402283800": { "type": "GBUserFieldText", "value": "LZZMRN55L53C003Z", "name": "Codice+Fiscale" }
}
const values = ["Codice+tessera", "Codice+fiscale"]
const results = values.map(v => Object.values(data).find(datum => datum.name === v)).map(v => v.value)
console.log(results) // [ "A0242", "LZZMRN55L53C003Z" ]
I have the following Json
var myjson = [{
"files": [
{
"domain": "d",
"units": [
{
"key": "key1",
"type": "2"
},
{
"key": "key2",
"type": "2"
},
{
"key": "key3",
"type": "2"
}]
},
{
"domain": "d1",
"units": [
{
"key": "key11",
"type": "2"
},
{
"key": "key12",
"type": "2"
},
{
"key": "key13",
"type": "2"
}]
}]
},
{
"files": [
{
"domain": "d",
"units": [
{
......
I want to create an new array from this Json array. The length of array will be the number of "units" in this Json object.
So I need to extract "units" and add some data from parent objects.
units: [{
domain: "",
type: "",
key: ""
}, {
domain: "",
type: "",
key: ""
},
{
domain: "",
type: "",
key: ""
}
....
];
I guess i can probably do something like this:
var res = [];
myjson.forEach(function(row) {
row.files.forEach(function(tfile) {
tfile.units.forEach(function(unit) {
var testEntity = {
domain: tfile.domain,
type : unit.type,
key: unit.key
};
res.push(testEntity);
});
});
});
But it is difficult to read and looks not so good. I was thinking to do something like :
var RESULT = myjson.map(function(row) {
return row.files.map(function(tfile) {
return tfile.units.map(function(unit) {
return {
domain: tfile.domain,
type : unit.type,
key: unit.key
};
});
});
});
But This doesn't work and looks not better . Is there any way to do so it works, maybe in more declarative way. hoped Ramda.js could help.
It there any good approach in general to get data from any Nested json in readable way?
Implementing something like:
nestedjson.findAllOnLastlevel(function(item){
return {
key : item.key,
type: type.key,
domain : item.parent.domain}
});
Or somehow flatten this json so all properties from all parents object are moved to leafs children. myjson.flatten("files.units")
jsbin http://jsbin.com/hiqatutino/edit?css,js,console
Many thanks
The function you can use here is Ramda's R.chain function rather than R.map. You can think of R.chain as a way of mapping over a list with a function that returns another list and then flattens the resulting list of lists together.
// get a list of all files
const listOfFiles =
R.chain(R.prop('files'), myjson)
// a function that we can use to add the domain to each unit
const unitsWithDomain =
(domain, units) => R.map(R.assoc('domain', domain), units)
// take the list of files and add the domain to each of its units
const result =
R.chain(file => unitsWithDomain(file.domain, file.units), listOfFiles)
If you wanted to take it a step further then you could also use R.pipeK which helps with composing functions together which behave like R.chain between each of the given functions.
// this creates a function that accepts the `myjson` list
// then passes the list of files to the second function
// returning the list of units for each file with the domain attached
const process = pipeK(prop('files'),
f => map(assoc('domain', f.domain), f.units))
// giving the `myjson` object produces the same result as above
process(myjson)
Pure JS is very sufficient to produce the result in simple one liners. I wouldn't touch any library just for this job. I have two ways to do it here. First one is a chain of reduce.reduce.map and second one is a chain of reduce.map.map. Here is the code;
var myjson = [{"files":[{"domain":"d","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"d1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]},{"files":[{"domain":"e","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"e1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]}],
units = myjson.reduce((p,c) => c.files.reduce((f,s) => f.concat(s.units.map(e => (e.domain = s.domain,e))) ,p) ,[]);
units2 = myjson.reduce((p,c) => p.concat(...c.files.map(f => f.units.map(e => (e.domain = f.domain,e)))) ,[]);
console.log(units);
console.log(units2);
For ES5 compatibility i would suggest the reduce.reduce.map chain since there is no need for a spread operator. And replace the arrow functions with their conventional counterparts like the one below;
var myjson = [{"files":[{"domain":"d","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"d1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]},{"files":[{"domain":"e","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"e1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]}],
units = myjson.reduce(function(p,c) {
return c.files.reduce(function(f,s) {
return f.concat(s.units.map(function(e){
e.domain = s.domain;
return e;
}));
},p);
},[]);
console.log(units);
Something like this should work. .reduce is a good one for these kind of situations.
const allUnits = myjson.reduce((acc, anonObj) => {
const units = anonObj.files.map(fileObj => {
return fileObj.units.map(unit => {
return {...unit, domain: fileObj.domain})
})
return [...acc, ...units]
}, [])
Note that this relies on both array spreading and object spreading, which are ES6 features not supported by every platform.
If you can't use ES6, here is an ES5 implementation. Not as pretty, but does the same thing:
var allUnits = myjson.reduce(function (acc, anonObj) {
const units = anonObj.files.map(function(fileObj) {
// for each fileObject, return an array of processed unit objects
// with domain property added from fileObj
return fileObj.units.map(function(unit) {
return {
key: unit.key,
type: unit.type,
domain: fileObj.domain
}
})
})
// for each file array, add unit objects from that array to accumulator array
return acc.concat(units)
}, [])
Try this
var myjson = [{
"files": [{
"domain": "d",
"units": [{
"key": "key1",
"type": "2"
}, {
"key": "key2",
"type": "2"
}, {
"key": "key3",
"type": "2"
}]
},
{
"domain": "d1",
"units": [{
"key": "key11",
"type": "2"
}, {
"key": "key12",
"type": "2"
}, {
"key": "key13",
"type": "2"
}]
}
]
}];
//first filter out properties exluding units
var result = [];
myjson.forEach(function(obj){
obj.files.forEach(function(obj2){
result = result.concat(obj2.units.map(function(unit){
unit.domain = obj2.domain;
return unit;
}));
});
});
console.log(result);