In Javascript, if we have a scenario in which there is a large object and we need to access its properties then which approach is better?
Suppose we have this object:
let largeObj = {
items: {
item: [
{
id: "0001",
type: "donut",
name: "Cake",
ppu: 0.55,
batters: {
batter: [
{
id: "1001",
type: "Regular"
},
{
id: "1002",
type: "Chocolate"
},
{
id: "1003",
type: "Blueberry"
},
{
id: "1004",
type: "Devil's Food"
}
]
},
topping: [
{
id: "5001",
type: "None"
},
{
id: "5002",
type: "Glazed"
},
{
id: "5005",
type: "Sugar"
},
{
id: "5007",
type: "Powdered Sugar"
},
{
id: "5006",
type: "Chocolate with Sprinkles"
},
{
id: "5003",
type: "Chocolate"
},
{
id: "5004",
type: "Maple"
}
]
}
]
}
}
and we want to create firstBatterAndTopping object which has first batter and first topping data. Now, if we access properties of this object in below three ways, which approach is better in terms of performance. Is there any difference in these or these will perform the same. Or is there any other scenario like this in which this will impact performance?
let firstBatterAndTopping = {
batterId: largeObj.items.item[0].batters.batter[0].id,
batterType: largeObj.items.item[0].batters.batter[0].type,
toppingId: largeObj.items.item[0].topping[0].id,
toppingType: largeObj.items.item[0].topping[0].type
}
or
let firstItem = largeObj.items.item[0];
let firstBatterAndTopping = {
batterId: firstItem.batters.batter[0].id,
batterType: firstItem.batters.batter[0].type,
toppingId: firstItem.topping[0].id,
toppingType: firstItem.topping[0].type
}
or
let firstItem = largeObj.items.item[0];
let firstBatter = firstItem.batters.batter[0];
let firstTopping = firstItem.topping[0]
let firstBatterAndTopping = {
batterId: firstBatter.id,
batterType: firstBatter.type,
toppingId: firstTopping.id,
toppingType: firstTopping.type
}
Try immer out! Is a javascript library very useful for managing complex objects with a lot of nests.
Following a simple scenario:
import produce from "immer"
const baseState = [
{
title: "Learn TypeScript",
done: true
},
{
title: "Try Immer",
done: false
}
]
const nextState = produce(baseState, draftState => {
draftState.push({title: "Tweet about it"})
draftState[1].done = true
})
// the new item is only added to the next state,
// base state is unmodified
expect(baseState.length).toBe(2)
expect(nextState.length).toBe(3)
// same for the changed 'done' prop
expect(baseState[1].done).toBe(false)
expect(nextState[1].done).toBe(true)
// unchanged data is structurally shared
expect(nextState[0]).toBe(baseState[0])
// ...but changed data isn't.
expect(nextState[1]).not.toBe(baseState[1])
I have a list of nested items in the JSON object. I have to filter only root nodes (parent).
The below is the JSON object.
const myJSON = [
{
__typename: 'Query', node:
{
__typename: 'Item', childItems: [Object], id: 'a', label: 'node1', url: '#', parent: null
}
},
{
__typename: 'Query', node:
{
__typename: 'Item', childItems: [Object], id: 'b', label: 'node2', url: '#', parent: null
}
},
{
__typename: 'Query', node:
{
__typename: 'Item', childItems: [Object], id: 'a', label: 'node3', url: '#', parent: 'node1'
}
}
]
this is my javascript code and the object is retrieved inside the object variable.
I want to filter only labels of parent nodes from the above object.
My desire output should be:
node1
node2
node3
node4
In order to obtain only the desired properties after the .filter() method, you can use the .map() method to transform the final array.
Note that I changed item.node.parent == null to !item.node.parent. Like this it doesn't look just for those which are null, but for those which are falsy. Change it again to null if that is the behaviour you are expecting.
As you can see in the snippet, using map can tell which property of the array I want to keep
EDIT: Answering your comment, of course you can select more than one property using the .map() method, as long as you format it as an object. The function filterParentAndObtainLabelAndUrl(input) returns label and url. As you can see, you can easily add as many as you want
const filterParentAndObtainLabelValue = (input) => {
return input.filter(element => !element.node.parent)
.map(element => element.node.label);
}
const filterParentAndObtainLabelAndUrl = (input) => {
return input.filter(element => !element.node.parent)
.map(element => {
return ({
label: element.node.label,
url: element.node.url
})
})
}
const inputJSON = [{ "__typename": "Query", "node": { "__typename": "Item", "childItems": [null], "id": "a", "label": "node1", "url": "#", "parent": null } }, { "__typename": "Query", "node": { "__typename": "Item", "childItems": [null], "id": "b", "label": "node2", "url": "#", "parent": null } }, { "__typename": "Query", "node": { "__typename": "Item", "childItems": [null], "id": "a", "label": "node3", "url": "#", "parent": "node1" } }]
console.log('Just the label: ', filterParentAndObtainLabelValue(inputJSON))
console.log('Label and url: ', filterParentAndObtainLabelAndUrl(inputJSON))
I am trying to create a category tree using the array of json objects below.
I want to set a category as a child of another category if its parent equals the id of the other, and I want the posts also to be a children of that category instead of having a separate field for posts, I'll add a flag field that if it is a category or not isParent.
It looks like its working alright, but as you may see, if a category has both category and post as child, it'll only show the categories. Another problem with that is if the post has a null value on its array, it will still push them as children.
What are the mistakes in my code, or is there a simpler or better solution to this?
var tree = unflatten(getData());
var pre = document.createElement('pre');
console.log(tree);
pre.innerText = JSON.stringify(tree, null, 4);
document.body.appendChild(pre);
function unflatten(array, parent, tree) {
tree = typeof tree !== 'undefined' ? tree : [];
parent = typeof parent !== 'undefined' ? parent : {
id: 0
};
_.map(array, function(arr) {
_.set(arr, 'isParent', true);
});
var children = _.filter(array, function(child) {
return child.parent == parent.id;
});
if (!_.isEmpty(children)) {
if (parent.id == 0) {
tree = children;
} else {
parent['children'] = children;
}
_.each(children, function(child) {
var posts = _.map(child.posts, function(post) {
return _.set(post, 'isParent', false);
});
child['children'] = posts;
delete child.posts;
unflatten(array, child);
});
}
return tree;
}
function getData() {
return [{
"id": "c1",
"parent": "",
"name": "foo",
"posts": [{
"id": "p1"
}]
}, {
"id": "c2",
"parent": "1",
"name": "bar",
"posts": [{
"id": "p2"
}]
}, {
"id": "c3",
"parent": "",
"name": "bazz",
"posts": [
null
]
}, {
"id": "c4",
"parent": "3",
"name": "sna",
"posts": [{
"id": "p3"
}]
}, {
"id": "c5",
"parent": "3",
"name": "ney",
"posts": [{
"id": "p4"
}]
}, {
"id": "c6",
"parent": "5",
"name": "tol",
"posts": [{
"id": "p5"
}, {
"id": "p6"
}]
}, {
"id": "c7",
"parent": "5",
"name": "zap",
"posts": [{
"id": "p7"
}, {
"id": "p8"
}, {
"id": "p9"
}]
}, {
"id": "c8",
"parent": "",
"name": "quz",
"posts": [
null
]
}, {
"id": "c9",
"parent": "8",
"name": "meh",
"posts": [{
"id": "p10"
}, {
"id": "p11"
}]
}, {
"id": "c10",
"parent": "8",
"name": "ror",
"posts": [{
"id": "p12"
}, {
"id": "p13"
}]
}, {
"id": "c11",
"parent": "",
"name": "gig",
"posts": [{
"id": "p14"
}]
}, {
"id": "c12",
"name": "xylo",
"parent": "",
"posts": [{
"id": "p15"
}]
}, {
"id": "c13",
"parent": "",
"name": "grr",
"posts": [{
"id": "p16"
}, {
"id": "p17"
}, {
"id": "p14"
}, {
"id": "p18"
}, {
"id": "p19"
}, {
"id": "p20"
}]
}]
}
<script src="//cdn.jsdelivr.net/lodash/3.10.1/lodash.min.js"></script>
Expected Output
So the expected output will be more like:
[
{
id: 'c1',
isParent: true,
children: [
{
id: 'c2',
isParent: true,
children: []
},
{
id: 'p1'
isParent: false
}
]
}
]
And so on..
Your code is very imperative. Try focusing on the "big picture" of data flow instead of writing code by trial-and-error. It's harder, but you get better results (and, in fact, usually it's faster) :)
My idea is to first group the categories by their parents. This is the first line of my solution and it actually becomes much easier after that.
_.groupBy and _.keyBy help a lot here:
function makeCatTree(data) {
var groupedByParents = _.groupBy(data, 'parent');
var catsById = _.keyBy(data, 'id');
_.each(_.omit(groupedByParents, ''), function(children, parentId) {
catsById['c' + parentId].children = children;
});
_.each(catsById, function(cat) {
// isParent will be true when there are subcategories (this is not really a good name, btw.)
cat.isParent = !_.isEmpty(cat.children);
// _.compact below is just for removing null posts
cat.children = _.compact(_.union(cat.children, cat.posts));
// optionally, you can also delete cat.posts here.
});
return groupedByParents[''];
}
I recommend trying each part in the developer console, then it becomes easy to understand.
I have made a small fidde that I think that is what you want.
http://jsfiddle.net/tx3uwhke/
var tree = buildTree(getData());
var pre = document.getElementById('a');
var jsonString = JSON.stringify(tree, null, 4);
console.log(jsonString);
pre.innerHTML = jsonString;
document.body.appendChild(pre);
function buildTree(data, parent){
var result = [];
parent = typeof parent !== 'undefined' ? parent : {id:""};
children = _.filter(data, function(value){
return value.parent === parent.id;
});
if(!_.isEmpty(children)){
_.each(children, function(child){
if (child != null){
result.push(child);
if(!_.isEmpty(child.posts)){
var posts = _.filter(child.posts, function(post){
return post !== null && typeof post !== 'undefined';
});
if(!_.isEmpty(posts)){
_.forEach(posts, function(post){
post.isParent = false;
});
}
result = _.union(result, posts);
delete child.posts;
}
ownChildren = buildTree(data, child);
if(!_.isEmpty(ownChildren)){
child.isParent = true;
child.children = ownChildren;
}else{
child.isParent = false;
}
}
});
}
return result;
}
EDIT: made a new fiddle to contain the isParent part you can find it here
While this problem looks simple, I can remember to have struggled achieving it in a simple way. I therefore created a generic util to do so
You only have to write maximum 3 custom callbacks methods.
Here is an example:
import { flattenTreeItemDeep, treeItemFromList } from './tree.util';
import { sortBy } from 'lodash';
const listItems: Array<ListItem> = [
// ordered list arrival
{ id: 1, isFolder: true, parent: null },
{ id: 2, isFolder: true, parent: 1 },
{ id: 3, isFolder: false, parent: 2 },
// unordered arrival
{ id: 4, isFolder: false, parent: 5 },
{ id: 5, isFolder: true, parent: 1 },
// empty main level folder
{ id: 6, isFolder: true, parent: null },
// orphan main level file
{ id: 7, isFolder: false, parent: null },
];
const trees = treeItemFromList(
listItems,
(listItem) => listItem.isFolder, // return true if the listItem contains items
(parent, leafChildren) => parent.id === leafChildren.parent, // return true if the leaf children is contained in the parent
(parent, folderChildren) => parent.id === folderChildren.parent // return true if the children is contained in the parent
);
console.log(trees);
/*
[
{
children: [
{
children: [{ data: { id: 3, isFolder: false, parent: 2 }, isLeaf: true }],
data: { id: 2, isFolder: true, parent: 1 },
isLeaf: false,
},
{
children: [{ data: { id: 4, isFolder: false, parent: 5 }, isLeaf: true }],
data: { id: 5, isFolder: true, parent: 1 },
isLeaf: false,
},
],
data: { id: 1, isFolder: true, parent: null },
isLeaf: false,
},
{ children: [], data: { id: 6, isFolder: true, parent: null }, isLeaf: false },
{
data: {
id: 7,
isFolder: false,
parent: null,
},
isLeaf: true,
},
]
*/
I did not check with your example as all cases are different, you however need to implement only 3 methods to let the algorithm build the tree for you:
If the item is a folder or a leaf (in your case just check if the children contain any non falsy item) i.e. listItem.posts.some((value)=>!!value)
if a parent contains the leaf child, (parent, child) => !!parent.posts.filter((val)=>!!val).find(({id})=>child.id === id)
if a parent contains the folder: optional if this is the same logic as for a leaf child.
I have JSON response as below -
{
"success": true,
"data": {
"data": [
{
"resultsMap": {
"Title": "Test1",
"Name": "Test1"
},
"id": 1
},
{
"resultsMap": {
"Title": "Test2",
"Name": "Test2"
},
"id": 2
}
],
"total": 2
}
}
I am using a custom reader to extract the data. Problem is I loose the initial JSON response from the server from which I need to extract the "total". Can someone help me in getting the "total" from the json response?
var newStore = Ext.create('Ext.data.BufferedStore', {
pageSize: 2000,
fields:fields,
//leadingBufferZone:50,
//trailingBufferZone:50,
//autoLoad: {start: 0, limit: 2000},
remoteSort: true,
sorters: [{
property : 'name',
direction: 'asc'
}],
proxy: {
type: 'ajax',
url: 'getData.json',
reader: {
type: 'nestedjsonreader',
rootProperty: 'data',
totalProperty: function(data) {
//console.log(data);
return data.totalCount;
},
},
extraParams: {
id:ID
}
},
listeners: {
'load' : function(store, records, success, options){
//the complete response
console.log(store.getProxy().getReader().rawData);
}
}
});
Ext.define('Portal.model.NestedJsonReader', {
extend: 'Ext.data.reader.Json',
alias: 'reader.nestedjsonreader',
readRecords: function(data) {
var arr = data.data.data;
var data = [];
if(arr!=undefined){
for(var i=0;i<arr.length;i++){
var obj = arr[i].resultsMap;
data.push(obj);
}
}
return this.callParent( [ data ]);
}
})
Create a custom property in your store as resp_total and assign data.total to it.
I had a sencha touch code which connects to a WebService using Ext.Ajax.request. On success function, I want the response to be displayed in a list or store it in a Store.
Please help me how to do that. Below is my code.
var View = function() {
Ext.getBody().mask('Loading...', 'x-mask-loading', false);
Ext.Ajax.request({
url: 'URL',
method: 'GET',
success: function(response, opts)
{
var obj = Ext.util.JSON.decode(response.responseText);
Ext.getCmp('content').update(response.responseText);
}
});
};
create a Ext.List with store attribute,
myStore = new Ext.data.Store({
model: Ext.regModel('', {
fields: [
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string'},
{ name: 'age', type: 'string'}
]
})
});
myList = new Ext.List({
store: myStore,
itemTpl: '<div>{name}</div>' +'<div>{age}</div>'
});
Then fill the myStore store with results of Agax request. onsuccess event in ajax call should be as follows,
var jsonData = Ext.util.JSON.decode(response.responseText);
myStore.add(jsonData['myresultlist']);
myStore.sync();
Then make sure You are returning a valid json from the server side as follows,
{
"myresultlist": [
{
"id": "1",
"name": "anne",
"age": "21"
},
{
"id": "2",
"name": "jack",
"age": "26"
},
{
"id": "3",
"name": "Tom",
"age": "21"
}
],
"success": "true",
"info": "My Results List!"
}