Push nested JSON values to array - javascript

I have a JSON array of the following type:
"team": [
{
"paid": {
"refugee": 2018,
"local": 29000,
"international": 12000
}
},
{
"unpaid": {
"refugee": 2019,
"local": 39000,
"international": 19000
}
}
]
I would like to push the values of matching keys into an array, so that I end up with the following new arrays:
var refugees = [2018, 2019]
var local = [29000, 39000]
var international = [12000, 19000]
and so on..
What would be a simple method of doing this? I have succesfully used jQuery in the past for this but need a Javascript only solution:
$.each(team, function (i, v) {
var teams = v;
console.log(teams);
$.each(v, function (i, v) {
refugees.push(v.refugee);
local.push(v.local);
international.push(v.international);
});
});

Try this
var a={"team" : [
{
"paid": {
"refugee": 2018,
"local": 29000,
"international": 12000
}
},
{
"unpaid": {
"refugee": 2019,
"local": 39000,
"international": 19000
}
}
]}
var refugee=[];
var local=[];
var international=[];
a.team.map((e)=>{
if(e.paid)
{
refugee.push(e.paid.refugee);
local.push(e.paid.local);
international.push(e.paid.international)
}
else
{
refugee.push(e.unpaid.refugee);
local.push(e.unpaid.local);
international.push(e.unpaid.international)
}
})
console.log(local)
console.log(international)
console.log(refugee)

You can use reduce.
So here the idea is we are taking a the keys and mapping it to output object. we keep checking if the key is already their in output object we push the value to that particular key and if not we add a new property with value.
let obj = {"team":[{"paid":{"refugee":2018,"local":29000,"international":12000}},{"unpaid":{"refugee":2019,"local":39000,"international":19000}}]}
let op = obj.team.reduce((output,current)=>{
let temp = Object.values(current)[0]
let values = Object.keys(temp)
values.forEach(ele=>{
if(output[ele]){
output[ele].push(temp[ele])
} else {
output[ele] = [temp[ele]]
}
})
return output;
}, {})
console.log(op)

Something like this would work if you wanted a few one-liners:
let local = team.reduce((acc, item) => acc.concat(Object.values(item).map(val => val.local)), []);
let refugee = team.reduce((acc, item) => acc.concat(Object.values(item).map(val => val.refugee)), []);

Use Array#reduce, Object#values, Object#entries, spread syntax, destructuring and Map
const data={"team":[{"paid":{"refugee":2018,"local":29000,"international":12000}},{"unpaid":{"refugee":2019,"local":39000,"international":19000}}]}
const res = data.team.reduce((a,c)=>{
Object.values(c)
.map(Object.entries)
.flat()
.forEach(([k,v])=>{
const arr = a.get(k) || [];
arr.push(v)
a.set(k, arr);
})
return a;
}, new Map())
//get all
console.log([...res.values()]);
//get by type
console.log(res.get('refugee'));
console.log(res.get('local'));
console.log(res.get('international'));

Related

How to use the result of an iteration to re-iterate?

I need to create a new array from another with the condition:
for example from an array
mainArr: [
{
"id":1,
"name":"root"
},
{
"id":2,
"parentId":1,
"name":"2"
},
{
"id":148,
"parentId":2,
"name":"3"
},
{
"id":151,
"parentId":148,
"name":"4"
},
{
"id":152,
"parentId":151,
"name":"5"
}
]
I need to make an array ['1','2','148','151'] which means the path from "parentId"'s to "id":152 - (argument for this function).
I think main logic can be like this:
const parentsArr = [];
mainArr.forEach((item) => {
if (item.id === id) {
parentsArr.unshift(`${item.parentId}`);
}
and the result {item.parentId} should be used to iterate again. But I don't understand how to do it...
You could use a recursive function for this. First you can transform your array to a Map, where each id from each object points to its object. Doing this allows you to .get() the object with a given id efficiently. For each object, you can get the parentId, and if it is defined, rerun your traverse() object again searching for the parent id. When you can no longer find a parentid, then you're at the root, meaning you can return an empty array to signify no parentid object exist:
const arr = [{"id":1,"name":"root"},{"id":2,"parentId":1,"name":"2"},{"id":148,"parentId":2,"name":"3"},{"id":151,"parentId":148,"name":"4"},{"id":152,"parentId":151,"name":"5"}];
const transform = arr => new Map(arr.map((o) => [o.id, o]));
const traverse = (map, id) => {
const startObj = map.get(+id);
if("parentId" in startObj)
return [...traverse(map, startObj.parentId), startObj.parentId];
else
return [];
}
console.log(traverse(transform(arr), "152"));
If you want to include "152" in the result, you can change your recursive function to use the id argument, and change the base-case to return [id] (note that the + in front of id is used to convert it to a number if it is a string):
const arr = [{"id":1,"name":"root"},{"id":2,"parentId":1,"name":"2"},{"id":148,"parentId":2,"name":"3"},{"id":151,"parentId":148,"name":"4"},{"id":152,"parentId":151,"name":"5"}];
const transform = arr => new Map(arr.map((o) => [o.id, o]));
const traverse = (map, id) => {
const startObj = map.get(+id);
if("parentId" in startObj)
return [...traverse(map, startObj.parentId), +id];
else
return [+id];
}
console.log(traverse(transform(arr), "152"));
I would start by indexing the data by id using reduce
var byId = data.reduce( (acc,i) => {
acc[i.id] = i
return acc;
},{});
And then just go through using a loop and pushing the id to a result array
var item = byId[input];
var result = []
while(item.parentId) {
result.push(item.parentId)
item = byId[item.parentId];
}
Live example:
const input = 152;
const data = [ { "id":1, "name":"root" }, { "id":2, "parentId":1, "name":"2" }, { "id":148, "parentId":2, "name":"3" }, { "id":151, "parentId":148, "name":"4" }, { "id":152, "parentId":151, "name":"5" } ]
var byId = data.reduce( (acc,i) => {
acc[i.id] = i
return acc;
},{});
var item = byId[input];
var result = []
while(item.parentId) {
result.push(item.parentId)
item = byId[item.parentId];
}
console.log(result.reverse());
Try changing this line
parentsArr.unshift(`${item.parentId}`);
To this
parentsArr.push(`${item.parentId}`);
Then try
console.log(parentsArr);
This is what I ended up with. Basically a mix of Janek and Nicks answers. It's just 2 steps:
transform code to a map.
extract the ancester_id's with a little function
let data = [
{"id":1,"name":"root"},
{"id":2,"parentId":1,"name":"2"},
{"id":148,"parentId":2,"name":"3"},
{"id":151,"parentId":148,"name":"4"},
{"id":152,"parentId":151,"name":"5"}
];
data = data.reduce( (acc, value) => {
// could optionally filter out the id here
return acc.set(value.id, value)
}, new Map());
function extract_ancestors( data, id ) {
let result = [];
while( data.get( id ).parentId ) {
id = data.get( id ).parentId;
result.push(id)
}
return result;
}
// some visual tests
console.log( extract_ancestors( data, 152 ) );
console.log( extract_ancestors( data, 148 ) );
console.log( extract_ancestors( data, 1 ) );
PS: My OOP tendencies start to itch so much from this haha.

Javascript get key of nested JSON object?

I have a json response that looks like the image below. I want to get all dates from the json and store in an array.
function buyOption(){
var ticker = document.getElementById('ticker').value;
fetch("https://stock-and-options-trading-data-provider.p.rapidapi.com/options/JPM", {
.then(response => response.json())
.then(data => {
dataset = data;
console.log(dataset['options'])
loadTable()
})
.catch(err => {
console.log(err);
});
function loadTable(){
expiration_dates = []
dates = dataset['options']
// console.log(JSON.parse(dates))
var keys = [];
for(var k in dates) keys.push(k);
console.log(keys)// returns ["0","1","2",3","5",6","9","10","11"]
console.log(dates[0].value) // returns undefined
}
}
goal is to have expiration_dates = ["2020-08-21","2020-08-28"]
You can try this. This will give you only the expiration dates.
var obj = {
"options": [{
"10-2-2001": "",
"someOtherProp": ""
}, {
"20-2-2001": "",
"someOtherProp": ""
}]
}
var expDates = obj.options.map(o=>Object.keys(o)[0])
console.log(expDates)
Refs:
Array.map()
Object.keys()
Try this
let result = dataSet.options.map(x => Object.keys(x));
console.log(result.flat(1))
A simple array map should do the trick and use Object.keys() array to get first key from each object in your data array
const dates = dataset['options'].map(o => Object.keys(o)[0])
console.log(dates)
<script>
const dataset = {
options: [{
'2013-12-22': {
puts: [],
calls: []
}},
{'2013-02-15': {
puts: [],
calls: []
}},
{ '2018-01-01': {
puts: [],
calls: []
}}
]
}
</script>
Something like
const options=dates.options.map(o=>
Object.keys(o).filter(k=>k.match(/^2\d{3}-\d{2}-\d{2}$/))[0]);
The idea is to loop over all options, get all keys for each of the objects and filter out the keys matching the Regexp, which is a date format, starting with 2. From the filtered keys-array I am only interested in the first element ([0]).
for(k in dates) {
keys.push((v=>{
for(let i in v) return i;
})(dates[k]));
}
Try it

Group list of objects by JSON key

I am trying to reformat my list of objects by grouping a certain key pair in javascript.
Data Format
[{
"sccode": "sccode1",
"val": "1ADA"
}, {
"sccode": "sccode2",
"val": "1ADB"
}, {
"sccode": "sccode1",
"val": "1ADC"
}]
Expected Result
[{
"scCode": "sccode1",
"valCodes": ["1ADA", "1ADC"]
},
{
"scCode": "sccode2",
"valCodes": ["1ADB"]
}
]
I believe I could loop through the array and match my keys, but is there a quick way to reformat this without having to explicitly loop through? I've tried using a reduce function below, but it gives undefined errors with find, which i think has something to do with my formatting.
Tried (?) Code
const resp = data.reduce((acc, ele) => {
const ant = acc.find(x => x.sccode === ele.sccode);
}, []);
Would this do?
const src = [{"sccode":"sccode1","val":"1ADA"},{"sccode":"sccode2","val":"1ADB"},{"sccode":"sccode1","val":"1ADC"}],
result = src.reduce((r,{sccode,val}) => {
const match = r.find(({scCode}) => scCode == sccode)
match ?
match.valCodes.push(val) :
r.push({scCode:sccode, valCodes: [val]})
return r
}, [])
console.log(result)
.as-console-wrapper{min-height:100%;}
Try the following, I use a map to store a partial state to improve performances preventing to search sccode in an array for every initial object.
let partial = [{
"sccode": "sccode1",
"val": "1ADA"
}, {
"sccode": "sccode2",
"val": "1ADB"
}, {
"sccode": "sccode1",
"val": "1ADC"
}].reduce((map, obj) => {
if (!map[obj.sccode]) {
map[obj.sccode] = [obj.val];
} else {
map[obj.sccode].push(obj.val);
}
return map;
}, {})
Object.keys(partial).map(sccode => ({
sccode, valCodes: partial[sccode]
}));
try loaddash/groupby
let groupByResult = groupBy(data, function (n) {
return n.sccode
});
Check this code:
array.reduce(function(res, value){
if(!res[value.sccode]) {
res[value.sccode] = value;
res[value.sccode]['valCodes'] = []
result.push(res[value.sccode]);
}
res[value.sccode]['valCodes'].push(value.val);
return res;
} ,{});
I tested here and it works fine!

Firebase orderByChild Ignored

How do I sort the following structure in Firebase by sortOrder?
categories {
{
"b": {
"name": "Banana",
"sortOrder": 2
},
"a": {
"name": "Apple",
"sortOrder": 1
}
}
}
From the documentation it looks as simple as:
ref('categories').orderByChild('sortOrder').once('value') ...
However, the first node returned is banana. It doesn't matter what string value I use. For example, the following returns the same results:
ref('categories').orderByChild('xxx').once('value') ...
Full function:
public list(): Observable<Category[]> {
let ref = firebase.database().ref('categories').orderByChild('sortOrder');
return Observable.fromPromise(<Promise<any>>ref.once('value'))
.flatMap(snapshot => {
let objects = snapshot.val();
let categories: Array<Category> = new Array();
for (let key in objects) {
let category: Category = objects[key];
category.code = key;
categories.push(category);
}
return Observable.of(categories);
}
);
}
The problem is that when you access the children via the snapshot's value's keys, the order is indeterminate.
You need to use the snapshot's forEach method:
return Observable.fromPromise(<Promise<any>>ref.once('value'))
.flatMap(snapshot => {
let categories: Array<Category> = new Array();
snapshot.forEach(childSnapshot => {
let category: Category = childSnapshot.val();
category.code = childSnapshot.key;
categories.push(category);
});
return Observable.of(categories);
}
);
Also, you could just use map and return categories.

Efficiently rename/re-map javascript/json object keys within array of objects

I have some structured JSON data like so. Let's assume this is interchangeable, via JSON.parse():
[
{
"title": "pineapple",
"uid": "ab982d34c98f"
},
{
"title": "carrots",
"uid": "6f12e6ba45ec"
}
]
I need it to look like this, remapping title to name, and uid to id with the result:
[
{
"name": "pineapple",
"id": "ab982d34c98f"
},
{
"name": "carrots",
"id": "6f12e6ba45ec"
}
]
The most obvious way of doing it is like this:
str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots", "uid": "6f12e6ba45ec"}]';
var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots", "uid": "6f12e6ba45ec"}]';
var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
$('body').append("<pre>"+JSON.stringify(arr, undefined, 4)+"</pre>");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
...or using something more complex (albeit not more efficient) like this.
This is all fine and dandy, but what if there were 200,000 objects in the array? This is a lot of processing overhead.
Is there a more efficient way to remap a key name? Possibly without looping through the entire array of objects? If your method is more efficient, please provide proof/references.
As I already mentioned in the comments, if you can make certain assumptions about the values of the objects, you could use a regular expression to replace the keys, for example:
str = str.replace(/"title":/g, '"name":');
It's not as "clean", but it might get the job done faster.
If you have to parse the JSON anyway, a more structured approach would be to pass a reviver function to JSON.parse and you might be able to avoid an additional pass over the array. This probably depends on how engine implement JSON.parse though (maybe they parse the whole string first and then make a second pass with the reviver function, in which case you wouldn't get any advantage).
var arr = JSON.parse(str, function(prop, value) {
switch(prop) {
case "title":
this.name = value;
return;
case "uid":
this.id = value;
return;
default:
return value;
}
});
Benchmarks, using the Node.js script below to test 3 times:
1389822740739: Beginning regex rename test
1389822740761: Regex rename complete
// 22ms, 22ms, 21ms
1389822740762: Beginning parse and remap in for loop test
1389822740831: For loop remap complete
// 69ms, 68ms, 68ms
1389822740831: Beginning reviver function test
1389822740893: Reviver function complete
// 62ms, 61ms, 60ms
It appears as if the regex (in this case) is the most efficient, but be careful when trying to parse JSON with regular expressions.
Test script, loading 100,230 lines of the OP's sample JSON:
fs = require('fs');
fs.readFile('test.json', 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
console.log(new Date().getTime() + ": Beginning regex rename test");
var str = data.replace(/"title":/g, '"name":');
str = str.replace(/"uid":/g, '"id":');
JSON.parse(str);
console.log(new Date().getTime() + ": Regex rename complete");
console.log(new Date().getTime() + ": Beginning parse and remap in for loop test");
var arr = JSON.parse(data);
for (var i = 0; i < arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
console.log(new Date().getTime() + ": For loop remap complete");
console.log(new Date().getTime() + ": Beginning reviver function test");
var arr = JSON.parse(data, function (prop, value) {
switch (prop) {
case "title":
this.name = value;
return;
case "uid":
this.id = value;
return;
default:
return value;
}
});
console.log(new Date().getTime() + ": Reviver function complete");
});
Asked this question a long time ago, and since then, I've grown acustomed to using Array.prototype.map() to get the job done, more for stability and cleanliness of code than performance. While it's certainly not the most performant, it looks great:
var repl = orig.map(function(obj) {
return {
name: obj.title,
id: obj.uid
}
})
If you need a more flexible (and ES6-compatible function), try:
let replaceKeyInObjectArray = (a, r) => a.map(o =>
Object.keys(o).map((key) => ({ [r[key] || key] : o[key] })
).reduce((a, b) => Object.assign({}, a, b)))
e.g.
const arr = [{ abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }]
const replaceMap = { "abc": "yyj" }
replaceKeyInObjectArray(arr, replaceMap)
/*
[
{
"yyj": 1,
"def": 40,
"xyz": 50
},
{
"yyj": 1,
"def": 40,
"xyz": 50
},
{
"yyj": 1,
"def": 40,
"xyz": 50
}
]
*/
Here's another take on the OP's suggestion to use map() for clarity (not performance).
var newItems = items.map(item => ({
name: item.title,
id: item.uid
}));
This uses ES6 arrow functions and the shortcut syntaxes that are possible when there's only one parm passed to the function and only one statement in the body of the function.
Depending on your history with lambda expressions in various languages, this form may or may not resonate with you.
Be careful when returning an object literal in the arrow function shortcut syntax like this. Don't forget the additional parens around the object literal!
If you want to make it a little more reusable. Maybe this is a decent approach.
function rekey(arr, lookup) {
for (var i = 0; i < arr.length; i++) {
var obj = arr[i];
for (var fromKey in lookup) {
var toKey = lookup[fromKey];
var value = obj[fromKey];
if (value) {
obj[toKey] = value;
delete obj[fromKey];
}
}
}
return arr;
}
var arr = [{ apple: 'bar' }, { apple: 'foo' }];
var converted = rekey(arr, { apple: 'kung' });
console.log(converted);
Using ES6:
const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
return arr.map(s => {
return Object.keys(s).reduce((prev, next) => {
if(next === oldField) {
prev[newField] = s[next]
} else {
prev[next] = s[next]
}
return prev
}, {})
})
}
Using ES7:
const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
return arr.map(s => {
return Object.keys(s).reduce((prev, next) => {
return next === oldField
? {...prev, [newField]: s[next]}
: {...prev, [next]: s[next]}
}, {})
})
}
You can use an npm package named node-data-transform.
Your data :
const data = [
{
title: 'pineapple',
uid: 'ab982d34c98f',
},
{
title: 'carrots',
uid: '6f12e6ba45ec',
},
];
Your mapping :
const map = {
item: {
name: 'title',
id: 'uid',
},
};
And use the package :
const DataTransform = require("node-json-transform").DataTransform;
const dataTransform = DataTransform(data, map);
const result = dataTransform.transform();
console.log(result);
Result :
[
{
name: 'pineapple',
id: 'ab982d34c98f'
},
{
name: 'carrots',
id: '6f12e6ba45ec'
}
]
Maybe it's not the best way for performance, but it's quite elegant.
var jsonObj = [/*sample array in question*/ ]
Based on different benchmarks discussed below, fastest solution is native for:
var arr = [];
for(var i = 0, len = jsonObj .length; i < len; i++) {
arr.push( {"name": jsonObj[i].title, "id" : jsonObj[i].uid});
}
I think alternatively without using a frameworks this will be option 2:
var arr = []
jsonObj.forEach(function(item) { arr.push({"name": item.title, "id" : item.uid }); });
There is always debate between using navite and non-navite functions. If I remember correctly lodash argued they were faster than underscore because the use non-native functions for key operations.
However different browsers will produce sometimes very different results. I always looked for the best average.
For benchmarks you can take a look at this:
http://jsperf.com/lo-dash-v1-1-1-vs-underscore-v1-4-4/8
function replaceElem(value, replace, str) {
while (str.indexOf(value) > -1) {
str = str.replace(value, replace);
}
return str;
}
call this from main
var value = "tittle";
var replace = "name";
replaceElem(value, replace, str);

Categories

Resources