Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 5 years ago.
Improve this question
What is the best way to "flatten" JSON array of objects using pure JS or Lodash?
For example I have an array
[
{
"name": "Mat",
"age": "18",
"studies": [
{
"subject": "English",
"mark": 5
},
{
"subject": "Maths",
"mark": 4
}
],
},
{
"name": "Alice",
"age": 20,
"city": "New York"
}
]
And I want to get something like
[
{
"name": "Mat",
"age": "18",
"subject": "English",
"mark": 5
},
{
"name": "Mat",
"age": "18",
"subject": "Maths",
"mark": 4
},
{
"name": "Alice",
"age": 20,
"city": "New York"
}
]
EDIT: working code is something like
rows.forEach(row => {
let newRow = {}
_.forOwn(row, (value, key) => {
value.forEach(item => {
_.forOwn(item, (value, key) => {
newRow[key] = value
})
})
} else {
newRow[key] = value
}
})
})
It doesn't provide first-level properties like name and for now it was enough for my reasons but now I need to get all properties except for fields like studies in my example.
Modern syntax will let you do it like this:
var d = [{"name":"Mat","age":"18","studies":[{"subject":"English","mark":5},{"subject":"Maths","mark":4}]},{"name":"Alice","age":20,"city":"New York"}];
var r = d.reduce((a, {studies, ...rest}) =>
[...a, ...(studies || [{}]).map(s => ({...s, ...rest}))]
, []);
console.log(r);
Browser support is limited, so use a transpiler if needed.
This uses the new rest syntax for object literals in the second parameter to the .reduce() callback. So basically, studies array is put into its own variable and rest will be an object with all the remaining properties.
Then we use spread syntax for array literals to put the members of the current accumulator array as well as a mapping of the new objects from studies into the callback result.
Within the .map() callback, we use object literal spread syntax to assign the properties of the rest object from the .reduce() callback, as well as the properties of the current "study" object to a new object literal, which is returned.
If there were no studies, we substitute a blank array with an empty object so that it at least gets the rest properties.
If the key to flatten is dynamic, you can still do it, though it's a little longer.
var d = [{"name":"Mat","age":"18","studies":[{"subject":"English","mark":5},{"subject":"Maths","mark":4}]},{"name":"Alice","age":20,"city":"New York"}];
function flatten(key, data) {
return data.reduce((a, user) => {
const arr = user[key];
delete user[key];
return [...a, ...(arr || [{}]).map(s => ({...s, ...user}))]
}, []);
}
console.log(flatten("studies", d));
If the destructuring syntax allowed the square bracket expression evaluation like this {[key], ...user}, that would clean it up, but right now they don't
Related
This question already has answers here:
From an array of objects, extract value of a property as array
(24 answers)
Closed 1 year ago.
I currently have an array that has 2 levels. I am attempting to build two new arrays from the initial array but seem to be stuck with the method to use. I have attempted a forEach(), while loop, as well as push to no avail.
My current array structure:
[
{
"attributes": {
"Summary": 10,
"class": "Blue"
}
},
{
"attributes": {
"Summary": 63,
"class":"Red"
}
}
]
I am looking to build two arrays, namely one for summary values, and one for class values.
Are my approaches of a forEach or while loop on the correct path?
If you have an array and want to transform that into another array, the simplest way is to use map (which basically creates a new array containing the results of running each element through a function):
const arr = [
{
"attributes": {
"Summary": 10,
"class": "Blue"
}
},
{
"attributes": {
"Summary": 63,
"class":"Red"
}
}
];
const summaries = arr.map(e => e.attributes.Summary);
const classes = arr.map(e => e.attributes.class);
If you want to accomplish this using forEach, here's one way to do it:
const aryInitial = [
{
"attributes": {
"Summary": 10,
"class": "Blue"
}
},
{
"attributes": {
"Summary": 63,
"class":"Red"
}
}
];
let arySummary = [];
let aryClass = [];
aryInitial.forEach((obj)=>
{
arySummary.push(obj.attributes.Summary);
aryClass.push(obj.attributes.class);
});
console.log("Summary Array:",arySummary);
console.log("Class Array:",aryClass);
I am fetching some data via an API call (within my dataservicse.ts file) - and the response I get is a sort of complex JSON structure. I am able to fetch a specific part of the response as follows in my corresponding Component file as follows -
Here is one of the JSON object response that represents the general structure -
{
"address": {
"building": "1234",
"coord": [0, 0],
"street": "123 Main Street",
"zipcode": "00000"
},
"address2": "Test Suite 000",
"grades": [{
"grade": "A",
"score": 3
}, {
"grade": "B",
"score": 4
}, {
"grade": "A",
"score": 2
}],
"name": "John Doe",
"_id": "1212121"
}
Now - my goal is to acquire the 'name' attribute as well as the first 'grade' value within the grades attribute from each response object - and map them into separate Arrays so that I can display them in table columns using *ngFor.
This is my Component code
export class TestDataComponent implements OnInit {
name$: Object;
grade$: Object;
constructor(private data: DataService, private data2: DataService) { }
ngOnInit() {
//getAPIData is returning the API response from the dataservices.ts file
this.data.getAPIData().subscribe(
data=>console.log(data.response.map(name=>name.name))
); //this works fine - I get the names in an array
this.data2.getAPIData().subscribe(
data2=>console.log((data2.response.map(grade=>grade.grades)).map(grades
=> {grades.map((value, index) => value.grade})) //this returns an undefined value
);
}
Now - if I console.log((data2.response.map(grade=>grade.grades))
I get an Array of Array objects such as -
Array - [Array(3), Array(3), Array(2)]
and each of them consist of the 'grades' attribute Array of objects.
(taking the first array from above) -
Array(3)
0:{"grade": "A","score": 3}
1:{"grade": "B", "score": 4}
2:{"grade": "A", "score": 2}
Thus - I further map my initial response to achieve the 'grade' value. Also - I only want the first grade - thus I have a simple condition added as follows -
console.log((data2.response.map(grade=>grade.grades))
.map(grades
=> {grades.map((value, index) =>{if(index<1) value.grade})}))
As mentioned in the comment - I get an undefined value.
I undestand this issue may be fairly complex but I've tried my best to explain it as clearly as possible. My goal is to get the first 'grade' values from each object and display them in an Array - just as the names, so that I can use it to display it on a table.
I am fairly new to Angular6, just switching from Angular 1.6 - so I am pretty sure I am messing something up.
What would be the best way to get the grade values into an Array by nested mapping within subscribe? Or is there a better approach to the same ?
Also - for the sake of simplicity, ignore the fact that the first subscription is present (for the name attribute) - I showed it here so as to make it clear as to what I want to achieve.
Here is what I think you're asking for since you never gave a concrete example of what you're trying to map/reduce. Also, this is vanilla JavaScript but could easily be translated into RxJS.
// I am assuming that a call to `DataServce.getAPIData()` returns an array
const api_response = [{
"address": {
"building": "1234",
"coord": [0, 0],
"street": "123 Main Street",
"zipcode": "00000"
},
"address2": "Test Suite 000",
"grades": [{
"grade": "A",
"score": 3
}, {
"grade": "B",
"score": 4
}, {
"grade": "A",
"score": 2
}],
"name": "John Doe",
"_id": "1212121"
}];
// Map and pluck the values you need
const result = api_response.map(v => [v.name, v.grades[0].score])
// Or as an array of objects
const obj_result = result.map(([name, score]) => ({ name, score }))
// Is this what you want?
console.log('Array', result);
console.log('Objects', obj_result);
Quick Update
Thanks for accepting the answer. Just wanted to give a quick of what this might look like using RxJS. I say might because the code snippet below is untested.
this.nameAndScores$ = data.getAPIData().pipe(
map(({ response }) => response),
map(({ name, grades }) => ( { name, score: grades[0].score } ))
)
Assuming that nameAndScores$ is a property of your component instance, you can do *ngFor item in nameAndScores$ | async and avoid any explicit subscriptions in your code.
You are using curly braces with a fat arrow in second map function. While using curly braces, you should return a value using the return keyword.
This question already has answers here:
Sort array of objects by string property value
(57 answers)
Closed 4 years ago.
I am getting an array of "product"s from a resolver getting data from a json endpoint.
ngOnInit() {
this.products = this._route.snapshot.data.products;
console.log('products: ', this.products);
}
where one of the objects in this array is in the format
{
"id": 3645,
"date": "2018-07-05T13:13:37",
"date_gmt": "2018-07-05T13:13:37",
"guid": {
"rendered": ""
},
"modified": "2018-07-05T13:13:37",
"modified_gmt": "2018-07-05T13:13:37",
"slug": "vpwin",
"status": "publish",
"type": "matrix",
"link": "",
"title": {
"rendered": "VPWIN"
},
"content": {
"rendered": "",
"protected": false
},
"featured_media": 0,
"parent": 0,
"template": "",
"better_featured_image": null,
"acf": {
"domain": "SMB",
"ds_rating": "3",
"dt_rating": ""
},
...
},
What I want to do is sort this array by the field title.rendered
In olden times, in AngularJS, I would simply use an orderBy pipe in the template set to this field. Apparently, this is removed in Angular and from doing research it seems the preferred method is to sort the data itself, such as in ngOnInit.
But I can't figure out how to sort products by producs.title.rendered.
You can simply use Arrays.sort()
array.sort((a,b) => a.title.rendered.localeCompare(b.title.rendered));
Working Example :
var array = [{"id":3645,"date":"2018-07-05T13:13:37","date_gmt":"2018-07-05T13:13:37","guid":{"rendered":""},"modified":"2018-07-05T13:13:37","modified_gmt":"2018-07-05T13:13:37","slug":"vpwin","status":"publish","type":"matrix","link":"","title":{"rendered":"VPWIN"},"content":{"rendered":"","protected":false},"featured_media":0,"parent":0,"template":"","better_featured_image":null,"acf":{"domain":"SMB","ds_rating":"3","dt_rating":""},},{"id":3645,"date":"2018-07-05T13:13:37","date_gmt":"2018-07-05T13:13:37","guid":{"rendered":""},"modified":"2018-07-05T13:13:37","modified_gmt":"2018-07-05T13:13:37","slug":"vpwin","status":"publish","type":"matrix","link":"","title":{"rendered":"adfPWIN"},"content":{"rendered":"","protected":false},"featured_media":0,"parent":0,"template":"","better_featured_image":null,"acf":{"domain":"SMB","ds_rating":"3","dt_rating":""}},{"id":3645,"date":"2018-07-05T13:13:37","date_gmt":"2018-07-05T13:13:37","guid":{"rendered":""},"modified":"2018-07-05T13:13:37","modified_gmt":"2018-07-05T13:13:37","slug":"vpwin","status":"publish","type":"matrix","link":"","title":{"rendered":"bbfPWIN"},"content":{"rendered":"","protected":false},"featured_media":0,"parent":0,"template":"","better_featured_image":null,"acf":{"domain":"SMB","ds_rating":"3","dt_rating":""}}];
array.sort((a,b) => a.title.rendered.localeCompare(b.title.rendered));
console.log(array);
Try this
products.sort(function (a, b) {
return a.title.rendered - b.title.rendered;
});
OR
You can import lodash/underscore library, it has many build functions available for manipulating, filtering, sorting the array and all.
Using underscore: (below one is just an example)
import * as _ from 'underscore';
let sortedArray = _.sortBy(array, 'title');
Not tested but should work
products.sort((a,b)=>a.title.rendered > b.title.rendered)
I am working with an API right now and I am using details[5].Value to target information in the following format:
details:
"value":[
{
"ID": "6",
"Name": "Links",
"Value": "URL"
},
{
"ID": "7",
"Name": "Other",
"Value": "URL"
}
etc
]
The problem is that the location inside of the JSON response is likely to change in the future, making my code obsolete and as the url has the potential to change as well, I cannot target that.
I want a way to target the value of url, mostly, because of this, by the value of the "Name" property. However, if I use something like
_.where(details, { Name: "Links" }).Value
It comes back as undefined. I am not sure if there would be another way to get to the information?
There are a couple points of confusion here.
_.where returns an array:
Looks through each value in the list, returning an array of all the values that contain all of the key-value pairs listed in properties.
so your _.where(details, obj).Value will (almost) always give you undefined because an array is unlikely to have a Value property. _.findWhere on the other hand does return a single value:
Looks through the list and returns the first value that matches all of the key-value pairs listed in properties.
Secondly, your details appears to look like:
let details = {
value: [
{ ID: '6', Name: 'Links', Value: 'URL' },
{ ID: '7', Name: 'Other', Value: 'URL' },
...
]
}
so you don't want to search details, you want to search details.value.
Putting them together:
_(details.value).findWhere({ Name: 'Links' }).Value
or
_.findWhere(details.value, { Name: 'Links' }).Value
You could use Array.prototype.find (or Array.prototype.filter if you're looking for all matches) and write your own callback but you already have Underscore available so why bother? Furthermore, Backbone collections have findWhere and where methods and there are advantages to matching Backbone's overall terminology.
Take a look at this mini function. Let me know if there is something wrong
Update
This is the ES5 Version
function f(key, value, array){
return array.value.filter(function(sub_array){
return sub_array[key] == value;
});
}
This is the ES6 Golfed Version
f=(k,v,a)=>a.value.filter(_=>_[k]==v)
//This is your JSON
var details = {
value: [
{
"ID": "6",
"Name": "Links",
"Value": "URL"
},
{
"ID": "7",
"Name": "Other",
"Value": "URL"
}
]
}
// Short code
f=(k,v,a)=>a.value.filter(_=>_[k]==v)
// f is the function name
// Recives k = array key, v = value, a = array
// filter by the given key and value
// return the result as an array
console.log(f('Name', 'Links', details))
An alternative is using the Javascript built-in function find to get a specific object within an array.
This alternative allows you to pass either an object or a string.
If the byThis parameter is an object, the whole set of key-values must match with the key-values of every object within the target array.
Otherwise, if byThis is a string every object will be treated as string to make the necessary comparison.
let details = { "value": [{ "ID": "6", "Name": "Links", "Value": "URL" }, { "ID": "7", "Name": "Other", "Value": "URL" }]};
let findBy = (array, byThis) => {
return array.find(o => {
if (typeof byThis === 'object') return Object.keys(byThis).every(k => o[k] === byThis[k]);
else if (typeof byThis === 'string') return o.toString() === byThis;
});
}
let found = findBy(details.value, {Name: "Links"});
console.log(found);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'd like to know if one can use .map() to dynamically change the added value to JS objects.
For example, a static use of .map() allows to add a similar ID to all objects of the array.
friends = [
{
"age": 10,
"name": "Castillo"
},
{
"age": 11,
"name": "Daugherty"
},
{
"age": 12,
"name": "Travis"
}
]
// Static mapping --> adds 1 to all objects
friends_static=friends;
friends.map(elem => elem["id"] = 1);
console.log(friends_static)
This returns [{age=10, name="Castillo", id=1}, {age=11, name="Daugherty", id=1}, {age=12, name="Travis", id=1}]
Is it possible to add a unique ID which increments by 1 for each object in a similar way?
Cf. the illustrative JSfiddle and example code below. I know the 1++ is not legal, but just shows the idea I'm trying to realize.
//Dynamic mapping? --> should add 1,2,3...to objects incrementally
/*
friends_dynamic=friends;
friends.map(elem => elem["id"] = 1++);
console.log(friends_dynamic)
*/
This should return [{age=10, name="Castillo", id=1}, {age=11, name="Daugherty", id=2}, {age=12, name="Travis", id=3}]
You could just use the index provided to the Array#map callback:
friends.map((friend, index) => Object.assign({}, friend, { id: index + 1 }))
It's not a good idea to mutate objects in Array#map. The whole purpose of the method is to return new objects that are mapped from the original objects. Thus use Object.assign to avoid mutation.
Of course, if you wanted mutation, thus just use forEach without mapping to new values. It would be more "semantically correct" in that case.
Is this what you mean?
const friends = [
{
"age": 10,
"name": "Castillo"
},
{
"age": 11,
"name": "Daugherty"
},
{
"age": 12,
"name": "Travis"
}
]
friends.forEach((friend, index) => friend.id = index + 1);
console.log(friends)
if you only need an incremental value from 0 on, you can simply use a counter and increment it, like this:
let id = 1;
friends.map(elem => {elem.id = id++;});
Use a local variable and increment it. As per method definition
"The map() method calls the provided function once for each element in an array, in order". In Order would make sure that ids do not collide.
friends = [
{
"age": 10,
"name": "Castillo"
},
{
"age": 11,
"name": "Daugherty"
},
{
"age": 12,
"name": "Travis"
}
]
// Static mapping --> adds 1 to all objects
friends_static=friends;
var i = 1;
friends_static.map(elem => elem["id"] = i++);
console.log(friends_static)
//Dynamic mapping? --> should add 1,2,3...to objects incrementally
/*
friends_dynamic=friends;
friends_dynamic.map(elem => elem["id"] = 1++);
console.log(friends_dynamic)
*/