Javascript Array to Object - javascript

I have an array that looks like so:
files = [
'Dashboard/Logs/Errors',
'Dashboard/Logs/Other',
'Accounts/Main',
]
I want to make it look like this:
navigation = [
{
"title": "Dashboard",
"dropdown": [
{
"title": "Logs",
"dropdown": [
{
"title": "Errors",
},
{
"title": "Other",
}
]
}
]
},
{
"title": "Accounts",
"dropdown": [
{
"title": "Main",
}
]
}
]
I have the following so far:
var navigation = [];
for (var i = 0; i < files.length; i++) {
var parts = files[i].split('/');
navigation.push({title: parts[0]});
for (var j = 1; j < parts.length; j++) {
}
}
I am having difficulties figuring out a decent way to do this. What I have so far already doesn't work because it creates two objects under navigation each with title: "Dashboard". Any ideas for a clever approach? Thanks :)

This should produce the desired output:
var files = [
'Dashboard/Logs/Errors',
'Dashboard/Logs/Other',
'Accounts/Main',
];
var navigation = [];
// Iterates through a navigation array and returns the object with matching title, if one exists.
var getNavigationObject = function(nav, title) {
for (var i = 0; i < nav.length; i++) {
if (nav[i].title == title) {
return nav[i];
}
}
};
// Adds a file to the nav.
// The input is an array of file components (i.e. file.split('/'))
// This works by recursively adding each component of a file.
var addToNav = function (nav, components) {
var n = getNavigationObject(nav, components[0]);
if (!n) {
n = {
title: components[0]
};
nav.push(n);
}
if (components.length > 1) {
n.dropdown = n.dropdown || [];
addToNav(n.dropdown, components.slice(1));
}
};
// Actually call `addToNav` on each file.
files.forEach(function(e) {
addToNav(navigation, e.split('/'));
});
// Produces the result in string form.
JSON.stringify(navigation, null, 2)
This works by recursively checking if a given element already matches the component of the file. If it does, it recurs into that component's "dropdown". Otherwise, it creates it.

This is an approach with a temporary object and some array methods with no search overhead.
var files = ['Dashboard/Logs/Errors', 'Dashboard/Logs/Other', 'Accounts/Main'],
navigation = function (data) {
var r = [], o = {};
data.forEach(function (a) {
var s = r;
a.split('/').reduce(function (p, b) {
if (p.children) {
p.value.dropdown = p.value.dropdown || [];
s = p.value.dropdown;
p = p.children;
}
if (!(b in p)) {
p[b] = { value: { title: b }, children: {} };
s.push(p[b].value);
}
return p[b];
}, o);
});
return r;
}(files);
document.write('<pre>' + JSON.stringify(navigation, 0, 4) + '</pre>');

Related

compare two arrays and remove duplicates [duplicate]

I created an array of objects like so:
[
{
"lat": 12.123,
"lng": 13.213,
"city": "New York"
},
{
"lat": 3.123,
"lng": 2.213,
"city": "New York"
},
{
"lat": 1.513,
"lng": 1.113,
"city": "London"
}
]
I'm trying to create a new array that filters the places to only contains objects that don't have the same city property (lat/lng duplicates are ok). Is there a built in JS or Jquery function to achieve this?
I'd probably use a flags object during the filtering (edit: I wouldn't anymore, see the note at the end of the answer about ES2015's Set), like this:
var flags = {};
var newPlaces = places.filter(function(entry) {
if (flags[entry.city]) {
return false;
}
flags[entry.city] = true;
return true;
});
That uses Array#filter from ECMAScript5 (ES5), which is one of the ES5 additions that can be shimmed (search for "es5 shim" for several options).
You can do it without filter, of course, it's just a bit more verbose:
var flags = {};
var newPlaces = [];
var index;
for (index = 0; index < places.length; ++index) {
if (!flags[entry.city]) {
flags[entry.city] = true;
newPlaces.push(entry);
}
});
Both of the above assume the first object with a given city should be kept, and all other discarded.
Note: As user2736012 points out below, my test if (flags[entry.city]) will be true for cities with names that happen to be the same as properties that exist on Object.prototype such as toString. Very unlikely in this case, but there are four ways to avoid the possibility:
(My usual preferred solution) Create the object without a prototype: var flags = Object.create(null);. This is a feature of ES5. Note that this cannot be shimmed for obsolete browsers like IE8 (the single-argument version of Object.create can be except when that argument's value is null).
Use hasOwnProperty for the test, e.g. if (flags.hasOwnProperty(entry.city))
Put a prefix on that you know doesn't exist for any Object.prototype property, such as xx:
var key = "xx" + entry.city;
if (flags[key]) {
// ...
}
flags[key] = true;
As of ES2015, you could use a Set instead:
const flags = new Set();
const newPlaces = places.filter(entry => {
if (flags.has(entry.city)) {
return false;
}
flags.add(entry.city);
return true;
});
Shortest, but not best performance (see update bellow) solution for es6 :
function unique(array, propertyName) {
return array.filter((e, i) => array.findIndex(a => a[propertyName] === e[propertyName]) === i);
}
performance: https://jsperf.com/compare-unique-array-by-property
You can filter using a Set by only including elements with a property value that has not yet been added to the Set (after which it should be added to the Set). This can be accomplished in one line using the logical and operator (&&). Using this data structure has the advantage of sublinear lookup times (often O(1)).
Below is a general function to obtain a unique array of objects based on a specific property (prop) from an array of objects (arr). Note that in the case of duplicates, only the first object with the property value will be retained.
const getUniqueBy = (arr, prop) => {
const set = new Set;
return arr.filter(o => !set.has(o[prop]) && set.add(o[prop]));
};
Demo:
var places = [{
lat: 12.123,
lng: 13.213,
city: 'New York'
}, {
lat: 3.123,
lng: 2.213,
city: 'New York'
}, {
lat: 3.123,
lng: 4.123,
city: 'Some City'
}];
const getUniqueBy = (arr, prop) => {
const set = new Set;
return arr.filter(o => !set.has(o[prop]) && set.add(o[prop]));
};
console.log(getUniqueBy(places, 'city'));
https://lodash.com/docs#uniqBy
https://github.com/lodash/lodash/blob/4.13.1/lodash.js#L7711
/**
* This method is like `_.uniq` except that it accepts `iteratee` which is
* invoked for each element in `array` to generate the criterion by which
* uniqueness is computed. The iteratee is invoked with one argument: (value).
*
* #static
* #memberOf _
* #since 4.0.0
* #category Array
* #param {Array} array The array to inspect.
* #param {Array|Function|Object|string} [iteratee=_.identity]
* The iteratee invoked per element.
* #returns {Array} Returns the new duplicate free array.
* #example
*
* _.uniqBy([2.1, 1.2, 2.3], Math.floor);
* // => [2.1, 1.2]
*
* // The `_.property` iteratee shorthand.
* _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
* // => [{ 'x': 1 }, { 'x': 2 }]
*/
I expanded a bit on #IgorL solution, but extended prototype and gave it a selector function instead of a property to make it a little more flexible:
Array.prototype.unique = function(selector) {
return this.filter((e, i) => this.findIndex((a) => {
if (selector) {
return selector(a) === selector(e);
}
return a === e;
}) === i);
};
Usage:
// with no param it uses strict equals (===) against the object
let primArr = ['one','one','two','three','one']
primArr.unique() // ['one','two','three']
let a = {foo:123}
let b = {foo:123}
let fooArr = [a,a,b]
fooArr.unique() //[a,b]
// alternatively, you can pass a selector function
fooArr.unique(item=>item.foo) //[{foo:123}] (first "unique" item returned)
Definitely NOT the most performant way to do this but as long as the selector is simple and the array isn't massive, it should work fine.
In Typescript
Array.prototype.unique = function<T>(this: T[], selector?: (item: T) => object): T[] {
return this.filter((e, i) => this.findIndex((a) => {
if (selector) {
return selector(a) === selector(e);
}
return a === e;
}) === i);
};
My suggestion :
Array.prototype.uniqueCity = function() {
var processed = [];
for (var i=this.length-1; i>=0; i--){
if (processed.indexOf(this[i].city)<0) {
processed.push(this[i].city);
} else {
this.splice(i, 1);
}
}
}
in use :
places.uniqueCity();
or
Array.prototype.uniqueObjectArray = function(field) {
var processed = [];
for (var i=this.length-1; i>=0; i--) {
if (this[i].hasOwnProperty(field)) {
if (processed.indexOf(this[i][field])<0) {
processed.push(this[i][field]);
} else {
this.splice(i, 1);
}
}
}
}
places.uniqueObjectArray('city');
With the above you can sort the array by any of the fields in the objects, even if they are not present for some of the objects.
or
function uniqueCity(array) {
var processed = [];
for (var i=array.length-1; i>=0; i--){
if (processed.indexOf(array[i].city)<0) {
processed.push(array[i].city);
} else {
array.splice(i, 1);
}
}
return array;
}
places = uniqueCity(places);
You could use a Map so the entries with the same key property (in your case 'city') only appear once
module.exports = (array, prop) => {
const keyValueArray = array.map(entry => [entry[prop], entry]);
const map = new Map(keyValueArray);
return Array.from(map.values());
};
More info about Map and array objects here
Basic example on Codepen
Another option:
const uniqueBy = prop => list => {
const uniques = {}
return list.reduce(
(result, item) => {
if (uniques[item[prop]]) return result
uniques[item[prop]] = item
return [...result, item]
},
[],
)
}
const uniqueById = uniqueBy('id')
uniqueById([
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 1, name: 'one' },
{ id: 3, name: 'three' }
])
You can paste it on your console to see it working.
It should work for the scenario presented and a few others.
We can create the list of unique objects by any property using JavaScript Map.
For example :
var places = [{ 'lat': 12.123, 'lng': 13.213, 'city': "New York"},
{ 'lat': 3.123, 'lng': 2.213, 'city': "New York"},
{ 'lat': 43.123, 'lng': 12.213, 'city': "London"}];
var cityMap = new Map();
places.forEach(p=> cityMap.set(p.city, p));
console.log([...cityMap.values()]);
Execute code snippet to see the result.
As pointed out in the comments, you could use an object as a map, which will allow you to avoid duplicates, you can then enumerate the properties of the object.
working fiddle: http://jsfiddle.net/gPRPQ/1/
var places = [];
var a = {};
a.lat = 12.123;
a.lng = 13.213;
a.city = "New York";
places.push(a);
var b = {};
b.lat = 3.123;
b.lng = 2.213;
b.city = "New York";
places.push(b);
var unique = {}
for (var i = 0; i < places.length; i++) {
var place = places[i];
unique[place.city] = place;
}
for (var name in unique) {
var place = unique[name];
console.log(place);
}
var places = [];
var a = {};
a.lat = 12.123;
a.lng = 13.213;
a.city = "New York";
places.push(a);
var b = {};
b.lat = 3.123;
b.lng = 2.213;
b.city = "New York";
places.push(b);
getUniqAR(places,'city'); //Return Uniq Array by property
function getUniqAR(Data,filter){
var uniar =[];
Data.forEach(function(item,ind,arr){
var dupi=false;
if(!uniar.length) uniar.push(item) //push first obj into uniq array
uniar.forEach(function(item2, ind2,arr){
if(item2[filter] == item[filter]){ //check each obj prop of uniq array
dupi=true; //if values are same put duplicate is true
}
})
if(!dupi){ uniar.push(item)} //if no duplicate insert to uniq
})
console.log(uniar)
return uniar;
}
In simple Javascript code to remove duplicate cities from places array list is
var places = [{ 'lat': 12.123, 'lng': 13.213, 'city': "New York"},
{ 'lat': 3.123, 'lng': 2.213, 'city': "New York"},
{ 'lat': 43.123, 'lng': 12.213, 'city': "London"}];
var unique = [];
var tempArr = [];
places.forEach((value, index) => {
if (unique.indexOf(value.city) === -1) {
unique.push(value.city);
} else {
tempArr.push(index);
}
});
tempArr.reverse();
tempArr.forEach(ele => {
places.splice(ele, 1);
});
console.log(places);
Generic Typescript answer based on https://stackoverflow.com/a/18773857/49564 above:
export function isDistinct<T>(mapper: (value: T) => string): (value: T) => boolean {
const keys: { [index: string]: boolean } = {};
return (entry: T) => {
const key = mapper(entry);
if (keys[key] !== undefined) {
return false;
}
return keys[key] = true;
};
}
// Usage example:
const items = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 1 } ];
const unique = items.filter(isDistinct(i => i.id));
I think you want this,
NOTE: No library is required.
let array = [{ id: 1}, {id: 2}, {id: 3}];
function addUniqeObj(data) {
let index = -1;
for(let i = 0, i < array.length; i++) {
if(array[i].id === data.id) {
index = i;
}
}
if(index > -1) {
array[index] = data;
} else {
array.push(data)
}
}
Another variation of the rafaelbiten approach:
const dedupExample = [
{id: 1, c: 'whatever'},
{id: 1, c: '1whatever'},
{id: 2, c: '2whatever'},
{id: 2, c: '2whatever'},
{id: 3, c: '2whatever'},
]
const getUniqueBy = (prop, list) => {
const objUniq = list.reduce((res, item) => ({ ...res, [item[prop]]: item }), {})
return Object.keys(objUniq).map(item => objUniq[item])
}
const uniq = getUniqueBy('id', dedupExample)
console.info('info', { uniq })
/* [
{id: 1, c: 'whatever'},
{id: 2, c: '2whatever'},
{id: 3, c: '2whatever'},
] */
const distinctArrayByCity= [
...new Map(array.map((item) => [item.city, item])).values(),
];
This thread may be old but thought I should share it. It is based on Pure JavaScript and removes Duplicate Objects based on the Properties Specified.
function removeDuplicates(originalArray, properties) {
var newArray = [];
var index = 0;
var lookupObject = {};
var totalProperties = properties.length;
for (var i = 0; i < originalArray.length; i++) {
var exists = false;
for (var a = 0; a < newArray.length; a++) {
var propsFound = 0;
for (var b = 0; b < totalProperties; b++) {
if (originalArray[i][properties[b]] == newArray[a][properties[b]]) {
propsFound++;
}
}
//If there is a match then break the for loop
if (propsFound == totalProperties) {
exists = true;
break;
}
} //End of New Array
if (!exists) {
newArray[index] = originalArray[i];
index++;
}
} //End of originalArray
return newArray;
}
You can view the fiddle here

Add New Key Value Pair

How can I manipulate my source array using javascript in order to get the following:
[ {Metric: totalRevenue, 2019-12-28: 91722000000, 2019-09-30: 63936000000}, {Metric: costOfRevenue, 2019-12-28: 56773000000, 2019-09-30: 39771000000}, ...etc. ]
Where Each financial statement item is considered a new metric that has a new key value that combines fiscalDate value and the value for that Line Item.
Example JSON here:
iex = [
{
reportDate: "2019-12-31",
fiscalDate: "2019-12-28",
currency: "USD",
totalRevenue: 91722000000,
costOfRevenue: 56773000000,
grossProfit: 34949000000,
researchAndDevelopment: 4451000000,
sellingGeneralAndAdmin: 5197000000,
operatingExpense: 66421000000,
operatingIncome: 25301000000,
otherIncomeExpenseNet: 617000000,
ebit: 25301000000,
interestIncome: 785000000,
pretaxIncome: 25918000000,
incomeTax: 3682000000,
minorityInterest: 0,
netIncome: 22236000000,
netIncomeBasic: 22236000000,
},
{
"reportDate": "2019-09-30",
"fiscalDate": "2019-09-28",
"currency": "USD",
"totalRevenue": 63936000000,
"costOfRevenue": 39771000000,
"grossProfit": 24165000000,
"researchAndDevelopment": 4110000000,
"sellingGeneralAndAdmin": 4578000000,
"operatingExpense": 48459000000,
"operatingIncome": 15477000000,
"otherIncomeExpenseNet": 650000000,
"ebit": 15477000000,
"interestIncome": 810000000,
"pretaxIncome": 16127000000,
"incomeTax": 2441000000,
"minorityInterest": 0,
"netIncome": 13686000000,
"netIncomeBasic": 13686000000
}
];
Image of Data Source: IEX data array
Code I've tried below:
function generateChartData(iex) {
var chartData = [],
categories = {};
for (var i = 0; i < iex.length; i++) {
var totalRevenue = iex[i].totalRevenue;
var fiscalDate = iex[i].fiscalDate;
// add new data point
if (categories[fiscalDate] === undefined) {
categories[fiscalDate] = {
Metric: totalRevenue,
};
chartData.push(categories[fiscalDate]);
}
// add value to existing data point
categories[fiscalDate][fiscalDate] = totalRevenue;
}
return chartData;
}
I have sorted the method, now can be done one loop.
function generateChartData(iex) {
return iex.reduce(
(m, item) => {
const fiscalDate = item["fiscalDate"];
if (!m[0][fiscalDate]) m[0][fiscalDate] = 0;
if (!m[1][fiscalDate]) m[1][fiscalDate] = 0;
if (!m[2][fiscalDate]) m[2][fiscalDate] = 0;
m[0][fiscalDate] += item.totalRevenue;
m[1][fiscalDate] += item.costOfRevenue;
m[2][fiscalDate] += item.grossProfit;
return m;
},
[
{ Metric: "totalRevenue" },
{ Metric: "costOfRevenue" },
{ Metric: "grossProfit" },
]
);
}
const iex = [{"reportDate":"2019-12-31","fiscalDate":"2019-12-28","currency":"USD","totalRevenue":91722000000,"costOfRevenue":56773000000,"grossProfit":34949000000,"researchAndDevelopment":4451000000,"sellingGeneralAndAdmin":5197000000,"operatingExpense":66421000000,"operatingIncome":25301000000,"otherIncomeExpenseNet":617000000,"ebit":25301000000,"interestIncome":785000000,"pretaxIncome":25918000000,"incomeTax":3682000000,"minorityInterest":0,"netIncome":22236000000,"netIncomeBasic":22236000000},{"reportDate":"2019-09-30","fiscalDate":"2019-09-28","currency":"USD","totalRevenue":63936000000,"costOfRevenue":39771000000,"grossProfit":24165000000,"researchAndDevelopment":4110000000,"sellingGeneralAndAdmin":4578000000,"operatingExpense":48459000000,"operatingIncome":15477000000,"otherIncomeExpenseNet":650000000,"ebit":15477000000,"interestIncome":810000000,"pretaxIncome":16127000000,"incomeTax":2441000000,"minorityInterest":0,"netIncome":13686000000,"netIncomeBasic":13686000000}]
console.log(
generateChartData(iex)
);
Another way:
function generateChartData(keys, iex) {
const [k1, k2, k3] = keys;
const def = keys.map((k) => ({ Metric: k }));
return iex.reduce((m, item) => {
const fiscalDate = item.fiscalDate;
if (!m[0][fiscalDate]) m[0][fiscalDate] = 0;
if (!m[1][fiscalDate]) m[1][fiscalDate] = 0;
if (!m[2][fiscalDate]) m[2][fiscalDate] = 0;
m[0][fiscalDate] += item[k1];
m[1][fiscalDate] += item[k2];
m[2][fiscalDate] += item[k3];
return m;
}, def);
}
const iex = [{"reportDate":"2019-12-31","fiscalDate":"2019-12-28","currency":"USD","totalRevenue":91722000000,"costOfRevenue":56773000000,"grossProfit":34949000000,"researchAndDevelopment":4451000000,"sellingGeneralAndAdmin":5197000000,"operatingExpense":66421000000,"operatingIncome":25301000000,"otherIncomeExpenseNet":617000000,"ebit":25301000000,"interestIncome":785000000,"pretaxIncome":25918000000,"incomeTax":3682000000,"minorityInterest":0,"netIncome":22236000000,"netIncomeBasic":22236000000},{"reportDate":"2019-09-30","fiscalDate":"2019-09-28","currency":"USD","totalRevenue":63936000000,"costOfRevenue":39771000000,"grossProfit":24165000000,"researchAndDevelopment":4110000000,"sellingGeneralAndAdmin":4578000000,"operatingExpense":48459000000,"operatingIncome":15477000000,"otherIncomeExpenseNet":650000000,"ebit":15477000000,"interestIncome":810000000,"pretaxIncome":16127000000,"incomeTax":2441000000,"minorityInterest":0,"netIncome":13686000000,"netIncomeBasic":13686000000}]
console.log(
generateChartData(["totalRevenue", "costOfRevenue", "grossProfit"], iex)
);

Reformatting array of arrays to nested json in Javascript [duplicate]

I have an array like
[
"parent1|child1|subChild1",
"parent1|child1|subChild2",
"parent|child2|subChild1",
"parent1|child2|subChild2",
"parent2|child1|subChild1",
"parent2|child1|subChild2",
"parent2|child2|subChild1",
.
.
.
]
Wherein my first string before | is the parent and the second string before | is the child and the third string after the second | is the subchild
How can I convert this array into an object like
[
{
"id": "parent1",
"children":[
{
"id": "child1",
"children":[
{
"id": "subChild1"
}
]
}
]
}
]
Parent -> child -> subchild object
Based on Sebastian's answer I tried below using typescript
private genTree(row) {
let self = this;
if (!row) {
return;
}
const [parent, ...children] = row.split('|');
if (!children || children.length === 0) {
return [{
id: parent,
children: []
}];
}
return [{
id: parent,
children: self.genTree(children.join('|'))
}];
}
private mergeDeep(children) {
let self = this;
const res = children.reduce((result, curr) => {
const entry = curr;
const existing = result.find((e) => e.id === entry.id);
if (existing) {
existing.children = [].concat(existing.children, entry.children);
} else {
result.push(entry);
}
return result;
}, []);
for (let i = 0; i < res.length; i++) {
const entry = res[i];
if (entry.children && entry.children.length > 0) {
entry.children = self.mergeDeep(entry.children);
}
};
return res;
}
private constructTree(statKeyNames){
let self = this;
const res = this.mergeDeep(statKeyNames.map(self.genTree).map(([e]) => e));
console.log(res);
}
but this gives me:
Cannot read property 'genTree' of undefined" error
Update:
As per Sebastian's comment changed self.genTree to this.genTree.bind(this) and it worked without any issues
You could use a mapper object which maps each object to it's unique path (You could map the object with each id, but id is not unique here). Then reduce each partial item in the array. Set the root object as the initialValue. The accumulator will be the parent object for the current item. Return the current object in each iteration.
const input = [
"parent1|child1|subChild1",
"parent1|child1|subChild2",
"parent1|child2|subChild1",
"parent1|child2|subChild2",
"parent2|child1|subChild1",
"parent2|child1|subChild2",
"parent2|child2|subChild1"
],
mapper = {},
root = { children: [] }
for (const str of input) {
let splits = str.split('|'),
path = '';
splits.reduce((parent, id, i) => {
path += `${id}|`;
if (!mapper[path]) {
const o = { id };
mapper[path] = o; // set the new object with unique path
parent.children = parent.children || [];
parent.children.push(o)
}
return mapper[path];
}, root)
}
console.log(root.children)
You have to use recursion for that. Take a look here:
const arr = [
"parent1|child1|subChild1",
"parent1|child1|subChild2",
"parent|child2|subChild1",
"parent1|child2|subChild2",
"parent2|child1|subChild1",
"parent2|child1|subChild2",
"parent2|child2|subChild1"
];
function genTree(row) {
const [parent, ...children] = row.split('|');
if (!children || children.length === 0) {
return [{
id: parent,
children: []
}];
}
return [{
id: parent,
children: genTree(children.join('|'))
}];
};
function mergeDeep(children) {
const res = children.reduce((result, curr) => {
const entry = curr;
const existing = result.find((e) => e.id === entry.id);
if (existing) {
existing.children = [].concat(existing.children, entry.children);
} else {
result.push(entry);
}
return result;
}, []);
for (let i = 0; i < res.length; i++) {
const entry = res[i];
if (entry.children && entry.children.length > 0) {
entry.children = mergeDeep(entry.children);
}
};
return res;
}
const res = mergeDeep(arr.map(genTree).map(([e]) => e));
console.log(JSON.stringify(res, false, 2));
I used two helpers here: genTree(row) which recursively generates a simple tree from each row, and mergeDeep(children) which reduces the first-level trees in the result of arr.map(genTree).map(([e]) => e), and then iterates over the array and recursively does the same thing to all children of each entry.

How to parse JSON having nested arrays in javascript or jquery

I want to parse JSON like below
{
"nodeId":3892718504,
"root":true,
"subs":[
{
"nodeId":3892717286
},
{
"nodeId":3892716092,
"subs":[
{
"nodeId":3892715856,
"subs":[
{
"nodeId":3892718592,
"subs":[
{
"nodeId":3892717580
}
]
}
]
}
]
},
{
"nodeId":3892717497
}
]
}
Each node can have subs and those subs can have nodes that can have their own subs. all I want is an array having all nodeId, how can I parse this JSON such that an array called nodes_list is populated with all nodeId.
I can use javascript or jquery.
I'm trying the following approach to get an array of nodeId
jQuery.each(response.topology, function(i,obj) {
if(i == "nodeId") {
node_list.push(obj)
}
if(i == "subs"){
jQuery.each(i, function(key,value) {
if(i == "nodeId") {
node_list.push(obj)
}
}
}
});
I just need a little hint on how it can be in an iterative manner.
This can be done with function generators.
Perhaps not the most enjoyable approach, but I'm pretty sure the other solutions will already imply using other ways, so here is a solution using generators.
PS: Beware of browser support: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
const input = {
"nodeId":3892718504,
"root":true,
"subs":[
{
"nodeId":3892717286
},
{
"nodeId":3892716092,
"subs":[
{
"nodeId":3892715856,
"subs":[
{
"nodeId":3892718592,
"subs":[
{
"nodeId":3892717580
}
]
}
]
}
]
},
{
"nodeId":3892717497
}
]
};
function* nodeLookup(obj) {
if (obj.nodeId) yield obj.nodeId;
if (obj.subs) for (var i = 0; i < obj.subs.length; i++) yield *nodeLookup(obj.subs[i]);
};
const node_ids = [...nodeLookup(input)];
console.log(node_ids);
Just use recursion to iterate over subs
var nodeIds = [];
if (data.nodeId) nodeIds.push(data.nodeId);
function fetchNodeIds (subs) {
if (!subs.length) return cb([]);
var abc = [];
subs.forEach(function (sub) {
abc.push(sub.nodeId);
if (sub.subs && sub.subs.length) abc = abc.concat(fetchNodeIds(sub.subs))
});
return abc;
}
nodeIds = nodeIds.concat(fetchNodeIds(data.subs));
console.log('--All nodeIds--', nodeIds)
It's straightforward to do recursively:
const gatherIds = ({nodeId, subs}, results = []) => subs
? [...results, nodeId, ...(subs .flatMap (sub => gatherIds (sub, results) ))]
: [...results, nodeId]
const response = {"nodeId": 3892718504, "root": true, "subs": [{"nodeId": 3892717286}, {"nodeId": 3892716092, "subs": [{"nodeId": 3892715856, "subs": [{"nodeId": 3892718592, "subs": [{"nodeId": 3892717580}]}]}]}, {"nodeId": 3892717497}]}
console .log (
gatherIds (response)
)
If your target environments don't support flatmap, it's easy enough to shim.

Better way to map a deep object to new object

This code works for converting the JSON to an object where each name object turns into the key for either its value, or if it instead has its own element object breaks that out and does the same to its contents.
Is there a better way to do this that would also allow for more extensiblity of the JSON schema?
Is there a way I can get it all down to a simpler function that I can pass the first element and have it convert it down to whatever depth the schema goes?
const fs = require('fs');
{
let scheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version='1.0'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root;
let depth = 0;
var compiled = {
[scheme.ele.name]: scheme.ele.ele.map(function(i) {
if (typeof i.ele != 'undefined') {
return {
[i.name]: i.ele.map(function(k) {
if (typeof k.ele != 'undefined') {
return {
[k.name]: k.ele.map(function(p) {
if (typeof p.ele != 'undefined') {
return {
[p.name]: p.ele
};
} else {
return {
[p.name]: p.value
};
}
})
};
} else {
return {
[k.name]: k.value
};
}
})
};
} else {
return {
[i.name]: i.value
};
}
})
};
}
console.log(JSON.stringify(compiled, 0, 2));
I should add, this is intended to eventually also apply validation and grab real data when it gets to the string objects.
The output looks like this:
{
"REPORT": [
{
"SEGMENT0": [
{
"NUMBER1": ""
},
{
"NUMBER2": ""
}
]
},
{
"SEGMENT1": [
{
"RECORD1": [
{
"NUMBER1": ""
},
{
"NUMBER2": ""
}
]
}
]
},
{
"SEGMENT2": []
},
{
"SEGMENT3": []
},
{
"SEGMENT4": []
},
{
"SEGMENT5": []
}
]
}
You could destructure the object, get name, ele and value and return a new object with name as key and either an array by mapping the objects of ele or the value.
const
getData = ({ name, ele, value }) => ({
[name]: Array.isArray(ele)
? ele.map(getData)
: value
});
var scheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version=\'1.0\'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root,
result = getData(scheme.ele);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina's answer is cleaner but this looks a bit more like your code so I figured I'd post it anyway.
let scheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version=\'1.0 \'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":"1"}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":"2"},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root;
let newScheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version=\'1.0 \'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":"1"},{"name":"NUMBER2","value":"3"}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":"4"},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root;
//Yay, recursion!
function mapObj(a, o = {}) {
let array = o[a.name] || [];
for (let i = 0; i < a.ele.length; i++) {
let b = a.ele[i];
array[i] = b.ele ?
mapObj(b, array[i]) : {
[b.name]: b.value
};
}
o[a.name] = array;
return o;
}
let obj = mapObj(scheme.ele);
console.log(obj);
console.log(mapObj(newScheme.ele, obj));

Categories

Resources