Parse a nested array objects, and create a new array - javascript

I have a nested array of objects that I am parsing in JavaScript, but I'm having difficulty creating the target array.
Here's the properly-rendered Kendo UI treeview with a sample array attached to it (i.e. the way the final dataSource array should look like):
http://plnkr.co/edit/YpuXJyWgGI7h1bWR0g70?p=preview
Kendo UI treeview widget
My source array has nested "children" arrays, where the leaf node is the "drms" array.
As I parse the nested children, I am trying to do the following :
the non-empty "children" array needs to be renamed to "drms"
the empty "children" array needs to be deleted
Here is a sample source array:
[ /* SOURCE ARRAY */
{
"category": "Market Risk",
"sysid": 1,
"children": [
{
"category": "General",
"sysid": 2,
"children": [],
"drms": [
{
"name": "1 Day VaR (99%)"
},
{
"name": "10 Day VaR (99%)"
}
]
},
{
"category": "Decomposition",
"sysid": 3,
"children": [],
"drms": [
{
"name": "1D VaR Credit"
},
{
"name": "1D VaR Equity"
}
]
},
{
"category": "Sensitivities",
"sysid": 4,
"children": [
{
"category": "Currency Pairs",
"sysid": 11,
"children": [
{
"category": "EUR/USD",
"sysid": 12,
"children": [],
"drms": [
{
"name": "Spot"
},
{
"name": "Spot - 0.01"
}
]
}
],
"drms": []
}
],
"drms": []
}
],
"drms": []
},
{
"category": "CCR",
"sysid": 6,
"children": [
{
"category": "General (CCR)",
"sysid": 7,
"children": [],
"drms": [
{
"name": "MTM"
},
{
"name": "PFE"
}
]
}
],
"drms": []
}
]
and the target array which I've modified manually in order to render the Kendo TreeView :
[
{
"category": "Market Risk",
"sysid": 1,
"drms": [
{
"category": "General",
"sysid": 2,
"drms": [
{
"name": "1 Day VaR (99%)",
"riskMeasure": "-PERCENTILE(SUM([99_HSVaR]:[1D]),1)",
"cubeVector": "[99_HSVaR]:[1D]"
},
{
"name": "10 Day VaR (99%)",
"riskMeasure": "-PERCENTILE(SUM([99_HSVaR]:[2W]),1)",
"cubeVector": "[99_HSVaR]:[2W]"
},
{
"name": "Day over Day VaR",
"riskMeasure": "-PERCENTILE(SUM(today),1)+PERCENTILE(SUM(yesterday),1)",
"cubeVector": "[BASELINE]:[99_HSVaR]:[2W] as today, [BASELINE-1]:[99_HSVaR]:[2W] as yesterday"
}
]
},
{
"category": "Decomposition",
"sysid": 3,
"drms": [
{
"name": "1D VaR Credit",
"riskMeasure": "SUM([99_HSVaR]:[1D CR])",
"cubeVector": "[99_HSVaR]:[1D CR]"
},
{
"name": "1D VaR Equity",
"riskMeasure": "SUM([99_HSVaR]:[1D EQ])",
"cubeVector": "[99_HSVaR]:[1D EQ]"
}
]
},
{
"category": "Sensitivities",
"sysid": 4,
"drms": [
{
"category": "Currency Pairs",
"sysid": 11,
"drms": [
{
"category": "EUR/USD",
"sysid": 12,
"children": [],
"drms": [
{
"name": "Spot",
"riskMeasure": "SUM([EUR_USD by EUR]:[Spot - 0.00])",
"cubeVector": "[EUR_USD by EUR]:[Spot - 0.00]"
},
{
"name": "Spot - 0.01",
"riskMeasure": "SUM([EUR_USD by EUR]:[Spot - 0.01])",
"cubeVector": "[EUR_USD by EUR]:[Spot - 0.01]"
}
]
}
],
}
]
}
],
},
{
"category": "CCR",
"sysid": 6,
"drms": [
{
"category": "General (CCR)",
"sysid": 7,
"drms": [
{
"name": "MTM",
"riskMeasure": "SUM(MTM:MTM)",
"cubeVector": "MTM:MTM"
},
{
"name": "PFE",
"riskMeasure": "PERCENTILE(SUM(x),95)",
"cubeVector": "[Simulated]:[MCS] IF value > 0 as x"
}
]
}
]
}
]
and my JavaScript routine which is not quite working yet. A bit of confusion while parsing the nested children:
function create_TempDrmTree() {
// convert raw def risk measures (drm) data into a tree format for the Kendo treeview widget.
var data = getTestDrmTree();
var drmsJson = [];
var i = 0;
_.each(data, function (item) {
drmsJson.push({ "category": item.category, drms: [] });
if (item.children.length > 0) {
pushDrms(item.children);
}
i++;
});
function pushDrms(children) {
_.each(children, function (item) {
if (item.children.length > 0) {
pushDrms(item.children);
}
else {
// no more children, so get the DRMs from item
// leaving tempty children[] causes an issue on Kendo treeview
delete item.children;
drmsJson[i]["drms"] = item;
}
});
}
return drmsJson;
}

Based on your original idea, I modified it slightly. This works perfectly for my scenario.
EDITED: from What is the most efficient way to clone an object?, we can easily create a brand new array and keep original one untouched.
function parseDrmTree(items, isCloned) {
if (isCloned !== true) {
// Create json string from original item, and then parse it.
// And only do this at the root.
items = JSON.parse(JSON.stringify(items));
isCloned = true;
}
// reparse the DRM source tree, renaming "children" array to "drms".
items.forEach(function (item, index) {
if (item.children.length > 0) {
// copy children[] to drms[]
item.drms = parseDrmTree(item.children, isCloned);
}
// children[] is empty; drms[] exist at this level
delete item.children;
}, this);
return items;
}
You don't have to give an isCloned value, just input the target array, the function will create a brand new array, and use it to create the desired structure, and the origin one is untouched.

Related

Recursive looping over array of objects using reduce

I have an array of objects with children. The goal is to remove every item from items arrays.
Is it possible to do without using forEach and map loops? How to use reduce in this case?
The problem is some arrays have items on one level and others have children array with items inside. Sample here:
{
"label": "child1",
"children": [
{
"label": "child2",
"items": [
"item1",
"item2"
]
},
{
"label": "child3",
"items": [
"item1",
"item2",
"item3"
]
}
]
}
As a result, I want to see a mutated array of objects with empty items arrays.
Here`s an object to be mutated:
[
{
"label": "parent",
"children": [
{
"label": "child1",
"children": [
{
"label": "child2",
"items": [
"item1",
"item2"
]
},
{
"label": "child3",
"items": [
"item1",
"item2",
"item3"
]
}
]
},
{
"label": "child4",
"items": []
},
{
"label": "child5",
"items": ["item1","item2"]
}
]
}
]
And here is my incomplete solution:
function flattenDeep(arr) {
return arr.reduce(
(acc, val) =>
Array.isArray(val)
? acc.concat(flattenDeep(val.children))
: acc.concat(val.children),
[]
);
}
Here's a way to empty all items arrays.
The idea is to use a predefined reducer method that can you can use recursively.
const reducer = (reduced, element) => {
// empty items array
if (element.items) {
element.items.length = 0;
}
// if element has children, recursively empty items array from it
if (element.children) {
element.children = element.children.reduce(reducer, []);
}
return reduced.concat(element); // or: [...reduced, element]
};
document.querySelector("pre").textContent =
JSON.stringify(getObj().reduce(reducer, []), null, " ");
// to keep relevant code on top of the snippet
function getObj() {
return [
{
"label": "parent",
"children": [
{
"label": "child1",
"children": [
{
"label": "child2",
"items": [
"item1",
"item2"
]
},
{
"label": "child3",
"items": [
"item1",
"item2",
"item3"
]
}
]
},
{
"label": "child4",
"items": []
},
{
"label": "child5",
"items": ["item1","item2"]
}
]
}
];
}
<pre></pre>

How to sort a nested array using lodash

I have a collection of objects in this array and I need to order them by the 'order' key (asc). Is there a way to sort the objects inside the array and then return the whole array? I am relying on the order as I'm using it in a v-for with a :key.
[
{
"id":0,
"type":"Header",
"order":1,
"props":{
"order":0,
"id":0,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
},
{
"id":1,
"type":"Header",
"order":0,
"props":{
"order":1,
"id":1,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
}
],
[
//Another collection of objects
]
I am currently doing this -
getters: {
sorted: state => {
return _.orderBy(state.experience_sections, function(block) {
if(block.experience_blocks[0]) {
return block.experience_blocks[0].order;
}
});
}
}
The solution above does not seem to order the objects by 'asc' order. Am I on the right track?
Thanks!
P.S. Stack is telling me that is a possible duplicate question but I'm at a loss after hours of searching. My apologies if I missed an already answered question.
Just in case you want plain javascript solution.. using Array.forEach
I have also extended your array to contain more data
var arr = [[
{
"id":0,
"type":"Header",
"order":1,
"props":{
"order":0,
"id":0,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
},
{
"id":1,
"type":"Header",
"order":0,
"props":{
"order":1,
"id":1,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
}
], [
{
"id":0,
"type":"Header",
"order":2,
"props":{
"order":0,
"id":0,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
},
{
"id":1,
"type":"Header",
"order":1,
"props":{
"order":1,
"id":1,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
}
]]
arr.forEach(d => d.sort((a,b) => a.order - b.order))
console.log(arr)
You should also consider orderBy method from lodash since you could easily change from asc to desc sort order if you would want to at a later date or have it via a variable being passed through the UI etc:
const data = [ [{ "id": 0, "type": "Header", "order": 1, "props": { "order": 0, "id": 0, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" }, { "id": 1, "type": "Header", "order": 0, "props": { "order": 1, "id": 1, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" } ], [{ "id": 0, "type": "Header", "order": 2, "props": { "order": 0, "id": 0, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" }, { "id": 1, "type": "Header", "order": 1, "props": { "order": 1, "id": 1, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" } ] ]
console.log('asc:', _.map(data, x => _.orderBy(x, 'order'))) // asc order
console.log('desc:', _.map(data, x => _.orderBy(x, 'order', 'desc'))) // desc
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Will sort each subarray in an array
const sortedArr = _.map(arr, subArray => _.sortBy(subArray, "order"));
Deep sorting using lodash
const sortedArray = _.orderBy(items, [(item) => {
const nestedObj = _.get(item, 'props');
item['props'] = _.orderBy(nestedObj,'order','desc');
return item['order'];
}], 'desc');

Filtering an array based on multiple arrays inputs

I have three arrays.
1. Existing viewers array - existingViewers
New viewers array - newViewers
Permitted Viewers array - permittedViewers
permittedViewers is used for rendering the drop-down. And I wish to filter the newViewers and existingViewers entries from the permittedViewers.
I am doing this as three steps. And I am afraid this is not the optimized way. Can someone suggest the ideal way of doing this?
The expected result is
[
{
"id": 4,
"name": "name4"
},
{
"id": 5,
"name": "name5"
},
{
"id": 6,
"name": "name6"
}
]
let existingViewers = [{
"viewerId": 1,
"name": "name1"
},
{
"viewerId": 2,
"name": "name2"
}
],
newViewers = [
{
"viewerId": 3,
"name": "name3"
}
],
permittedViewers = [{
"id": 1,
"name": "name1"
},
{
"id": 2,
"name": "name2"
},
{
"id": 3,
"name": "name3"
},
{
"id": 4,
"name": "name4"
},
{
"id": 5,
"name": "name5"
},
{
"id": 6,
"name": "name6"
}
]
let grouped = [...existingViewers, ...newViewers]
let viewerFilter = grouped.map(viewer => { return viewer.viewerId; });
let filteredPermittedViewers = permittedViewers.filter(viewer => !viewerFilter.includes(viewer.id));
console.log(filteredPermittedViewers)
I'd make a Set of the ids of the first two arrays, and then filter the third by whether the set includes the id. (Sets have O(1) lookup time)
let existingViewers=[{"viewerId":1,"name":"name1"},{"viewerId":2,"name":"name2"}],newViewers=[{"viewerId":3,"name":"name3"}],permittedViewers=[{"id":1,"name":"name1"},{"id":2,"name":"name2"},{"id":3,"name":"name3"},{"id":4,"name":"name4"},{"id":5,"name":"name5"},{"id":6,"name":"name6"}];
const ids = new Set([...existingViewers, ...newViewers].map(({ viewerId }) => viewerId));
const output = permittedViewers.filter(({ id }) => !ids.has(id));
console.log(output);
You can compress all three statements into a single statement -- just replace the variable name with the statement that creates it:
let existingViewers = [{
"viewerId": 1,
"name": "name1"
},
{
"viewerId": 2,
"name": "name2"
}
],
newViewers = [
{
"viewerId": 3,
"name": "name3"
}
],
permittedViewers = [{
"id": 1,
"name": "name1"
},
{
"id": 2,
"name": "name2"
},
{
"id": 3,
"name": "name3"
},
{
"id": 4,
"name": "name4"
},
{
"id": 5,
"name": "name5"
},
{
"id": 6,
"name": "name6"
}
]
let filteredPermittedViewers = permittedViewers.filter(viewer => ! [...existingViewers, ...newViewers].map(viewer => viewer.viewerId).includes(viewer.id));
console.log(filteredPermittedViewers)

Building new JSON from existing one

I want to build an new JSON from existing one. The source has sections and rubrics that I no longer need for a listing. The new object called 'items' should have an array of the items.
The final JSON should be sorted by attribute 'name' and look like
{
"items": [
{
"id": 10000006,
"name": "Boah"
},
{
"id": 10000013,
"name": "Gut"
},
{
"id": 10000003,
"name": "Ipsum"
},
{
"id": 10000001,
"name": "Lorem"
},
{
"id": 10000005,
"name": "Lorum"
},
{
"id": 10000004,
"name": "Name"
},
{
"id": 10000002,
"name": "Stet"
}
]
}
For building the new JSON I get this source:
{
"sections": [
{
"name": "FooBar",
"rubrics": [
{
"name": "Foo",
"items": [
{
"id": 10000001,
"name": "Lorem"
},
{
"id": 10000002,
"name": "Stet"
},
{
"id": 10000003,
"name": "Ipsum"
}
]
},
{
"name": "Bar",
"items": [
{
"id": 10000004,
"name": "Name"
},
{
"id": 10000005,
"name": "Lorum"
},
{
"id": 10000006,
"name": "Boah"
}
]
}
]
},
{
"name": "BlahBloob",
"rubrics": [
{
"name": "Bla",
"items": [
{
"id": 10000013,
"name": "Gut"
}
]
},
{
"name": "Bloob",
"items": [
{
"id": 10000014,
"name": "Name"
},
{
"id": 10000015,
"name": "Lorem"
}
]
}
]
}
]
}
What do you think? How can I do this with plain JavaScript or maybe TypeScript?
Thanks for reading and have time for my question. And thanks for reply in advance.
Here you go. You just need to iterate over each rubric of each section of your source to get the items. At the end, sort your list of items by items, and you're done.
This example uses ES6 syntax, but it's easy to convert it to ES5 if needed.
function extractItems(source) {
const items = [];
for (const section of source.sections) {
for (const rubric of section.rubrics) {
items.push(...rubric.items);
}
}
items.sort((a, b) => a.name.localeCompare(b.name));
return { items };
}
A more functional approach use map and reduce to pick the rubrics and merge them.
data.sections
.map(section => section.rubrics) // get rubrics
.reduce((a, b) => a.concat(b)) // merge rubrics
.map(rubric => rubric.items) // get items from each rubric
.reduce((a, b) => a.concat(b)) // merge items
.sort((a, b) => a.name.localeCompare(b.name)); // sort
function(oldObj) {
var newObj = {
"items": []
};
oldObj.sections.forEach(function(section) {
section.rubrics.forEach(function(rubric) {
rubric.items.forEach(function(item) {
newObj.items.push(item);
});
});
});
newObj.items = newObj.items.sort(function(a, b) {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return newObj;
}
And simply use JSON.parse() and JSON.stringify() to convert JSON to and from objects.
It might help you
var data ={
"sections": [
{
"name": "FooBar",
"rubrics": [{"name": "Foo", "items": [{"id": 10000001,"name": "Lorem"}, {"id": 10000002,"name": "Stet"}, {"id": 10000003,"name": "Ipsum"}]
}, {
"name": "Bar",
"items": [{
"id": 10000004,
"name": "Name"
}, {
"id": 10000005,
"name": "Lorum"
}, {
"id": 10000006,
"name": "Boah"
}]
}]
}, {
"name": "BlahBloob",
"rubrics": [{
"name": "Bla",
"items": [{
"id": 10000013,
"name": "Gut"
}]
}, {
"name": "Bloob",
"items": [{
"id": 10000014,
"name": "Name"
}, {
"id": 10000015,
"name": "Lorem"
}]
}]
}]
};
var itemObj = {};
var itemArr = [];
var sections = data.sections;
for(var i=0;i<sections.length;i++)
{
for(var j=0;j<sections[i].rubrics.length;j++){
for(var k=0;k<sections[i].rubrics[j].items.length;k++){
var itemObj;
itemObj['id'] = sections[i].rubrics[j].items[k].id;
itemObj['name'] = sections[i].rubrics[j].items[k].name;
itemArr.push(itemObj);
}
}
}
var finalObj = {"items":itemArr};
console.log(finalObj);
JSFiddle

Filter an array of objects / convert array into another

could you please help me to convert data in format like :
"tanks": [
{
"id": "1",
"name": {
"id": 1,
"tor": "000"
},
"type": {
"id": 1,
"system": "CV-001"
}
}
]
into
"tanks":[
{
"type": 1,
"name": 1
}
]
As you can see, type.id in the first array is the same as just type in the second. It is like I have to iterate through the array(as I have not only one Object in it) and left only needed fields in Objects, but I am stuck.
Hope it is a little informative for you.
You can do this with a simple Array.map()
var obj = {
tanks : [
{
"id": "1",
"name": {
"id": 1,
"tor": "000"
},
"type": {
"id": 1,
"system": "CV-001"
}
},
{
"id": "2",
"name": {
"id": 2,
"tor": "200"
},
"type": {
"id": 2,
"system": "CV-002"
}
}
]
};
obj.tanks = obj.tanks.map(function(item) {
return {
name : item.name.id,
type : item.type.id
};
});
console.log(obj);
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

Categories

Resources