I'm having an issue where a nested array associated inside an object causes a fetch declaration to fail. The actual JSON itself is valid, and when the array itself is commented, it works fine. I'd like to parse the entire object, and eventually access the array.
Here's the object (the nefarious array is inside the "Page 2" item):
{
"identifier":"pageTitle",
"label":"pageTitle",
"items":[
{
"pageTitle":"Page 1",
"pageUrl":"#"
},
{
"pageTitle":"Page 2",
"subTopics":[
{
"subTitle":"Subpage 1"
}
],
"pageUrl":"index.html"
},
{
"pageTitle":"Page 3",
"pageUrl":"#"
}
]
}
and the fetch code (fairly simple)
try {
var JSONObject = new dojo.data.ItemFileReadStore({url: "object.json"});
//Fetch nav objects and start building
JSONObject.fetch({
query: {"pageTitle":"*"},
onBegin: clearNav,
onComplete: buildNav,
onError: failureNav
});
} catch(xcept) {
console.warn("Failed to create JSONObject", xcept);
}
And related functions
function clearNav(items, req) {
console.log("clearNav()");
dojo.query("nav.navigation ul").forEach(function(items, idx) {
items.innerHTML="";
});
console.warn("Navigation destroyed...");
}
function buildNav(items, req) {
console.log("buildNav()");
console.debug("items", items);
var theList = dojo.query("nav.navigation ul")[0];
dojo.forEach(items, function(item, idx) {
var newLI = document.createElement("li");
console.warn("url", document.URL);
if (document.URL.search(item.pageUrl)) {
newLI.setAttribute("class","ac");
}
newLI.innerHTML = "<a href=\""+item.pageUrl+"\" >"+item.pageTitle+"</a>";
theList.appendChild(newLI);
});
}
function failureNav(e) {
console.debug("Error", e);
}
You have two choices:
either format your JSON nested object ("subTopicts") to conform to having the same identifier name of the main one;
{
"identifier":"pageTitle",
"label":"pageTitle",
"items":[
{
"pageTitle":"Page 1",
"pageUrl":"#"
},
{
"pageTitle":"Page 2",
"subTopics":[
{
"pageTitle":"Page 1.1",
"subTitle":"Subpage 1"
}
],
"pageUrl":"index.html"
},
{
"pageTitle":"Page 3",
"pageUrl":"#"
}
]
}
or let Dojo ignore that nested object, creating the ItemFileReadStore with parameter herarchical : "false"
var JSONObject = new dojo.data.ItemFileReadStore({
url: "object.json",
hierarchical: false
});
You find this documented in the doc.
Related
This question already has answers here:
How can I access and process nested objects, arrays, or JSON?
(31 answers)
Closed 10 months ago.
I'm trying to manipulate an array like this:
data = [
{
"id":"1",
"items":[
{
"title":"item 1"
},
{
"title":"item 2"
}
]
},
{
"id":"2",
"items":[
{
"title":"item2 1"
},
{
"title":"item2 2"
}
]
}
]
I need, for example, to push another array:
[
{
"title":"item new 1"
},
{
"title":"item new 2"
}
]
inside data[0].items and obtain:
data = [
{
"id":"1",
"items":[
{
"title":"item new 1"
},
{
"title":"item new 2"
}
]
},
{
"id":"2",
"items":[
{
"title":"item2 1"
},
{
"title":"item2 2"
}
]
}
]
...how can I do this maintaining immutability, for example with Lodash?
Not understand anding how to change only a specific sub object in a data structure.
Somebody have suggestions?
Thanks
Presented below is one possible way to immutably add given array into a particular index "items" prop.
Code Snippet
const immutableAdd = (aIdx, addThis, orig) => {
const newData = structuredClone(orig);
newData[aIdx].items = addThis;
return newData;
};
const data = [{
"id": "1",
"items": [{
"title": "item 1"
},
{
"title": "item 2"
}
]
},
{
"id": "2",
"items": [{
"title": "item2 1"
},
{
"title": "item2 2"
}
]
}
];
const addThisArr = [{
"title": "item new 1"
},
{
"title": "item new 2"
}
];
console.log('immutableAdd result: ', immutableAdd(0, addThisArr, data));
console.log('original data: ', data);
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Use structuredClone() to deep-clone existing data array
Navigate to the aIdx of the cloned-array
Assign the given array (to be added) into aIdx's items prop
NOTE
This solution does not use lodash as it is not mandatory (to use lodash) to perform immutable operations.
If you want to maintain the immutability of original data, just map the content of original data to the new data as you want, and wrap your logic into a pure function to improve readability.
const dataOriginal = [{
"id": "1",
"items": [{
"title": "item 1"
},
{
"title": "item 2"
}
]
},
{
"id": "2",
"items": [{
"title": "item2 1"
},
{
"title": "item2 2"
}
]
}
]
const dataNew = createDataWithSomethingNew(dataOriginal, [{
"title": "item new 1"
},
{
"title": "item new 2"
}
])
function createDataWithSomethingNew(data, props) {
return data.map(function changeItemsOfId1ToProps(value) {
if (value.id === '1') {
return {
id: value.id,
items: props
}
} else {
return value
}
})
}
lodash has a method _.update can modify object with the correct path in string provided.
Another method _.cloneDeep can copy you object deeply. So that change in the pre-copied object will not affect the cloned object.
Finally use a deep freeze function to call Object.freeze recursively on the cloned object to make it immutable.
var data = [
{
"id":"1",
"items":[
{
"title":"item 1"
},
{
"title":"item 2"
}
]
},
{
"id":"2",
"items":[
{
"title":"item2 1"
},
{
"title":"item2 2"
}
]
}
]
var clonedData = _.cloneDeep(data) // copy the full object to avoid modify the source data
// update the data of that path '[0].items' in clonedData
_.update(clonedData, '[0].items', function(n) {
return [
{
"title":"item new 1"
},
{
"title":"item new 2"
}
]
})
// provide object immutability
const deepFreeze = (obj1) => {
_.keys(obj1).forEach((property) => {
if (
typeof obj1[property] === "object" &&
!Object.isFrozen(obj1[property])
)
deepFreeze(obj1[property])
});
Object.freeze(obj1)
};
deepFreeze(clonedData)
data[2] = {id: 3} // data will be changed
data[1].items[2] = {title: "3"} // and also this one
clonedData[2] = {id: 3} // nothing will be changed
clonedData[1].items[2] = {title: "3"} // and also this one
console.log(`data:`, data);
console.log(`clonedData:`, clonedData);
Runkit Example
I have an object like this:
{
"responses": {
"firstKey": {
"items": {
"name": "test name one"
}
},
"anotherKey": {
"items": {
"name": "test name two"
}
},
"oneMoreKey": {
"items": {
"name": "John"
}
}
}
}
I need to find all 'name' keys and replace its value only if it starts with 'test name' then return new JSON object:
{
"responses": {
"firstKey": {
"items": {
"name": "N/A"
}
},
"anotherKey": {
"items": {
"name": "N/A"
}
},
"oneMoreKey": {
"items": {
"name": "John"
}
}
}
}
The problem is that the keys are not consistent through the objects, i.e. 'firstKey', 'secondKey'... I tried ForEach but it seems to be too cumbersome... So I need either lodash or vanila JavaScript to replace the values.
The javascript object should be iterated and then each value of name can be checked and replaced. There are checks such as hasOwnProperty() that can be used to make sure you are not iterating objects that are missing "items" or "name" for better error handling.
var data = {
"responses": {
"firstKey": {
"items": {
"name": "test name one"
}
},
"anotherKey": {
"items": {
"name": "test name two"
}
},
"oneMoreKey": {
"items": {
"name": "John"
}
}
}
};
Given the JSON above you can use a simple for statement to iterate and then check each name for some value and replace.
for(var key in data.responses){
if ((data.responses[key].items.name).match(/test name/)){
data.responses[key].items.name = "N/A";
}
}
To check your replacements you can log data to the console.
console.log(JSON.stringify(data));
It can also be done during parsing :
var json = `{
"responses": {
"firstKey": {
"items": {
"name": "test name one"
}
},
"anotherKey": {
"items": {
"name": "test name two"
}
},
"oneMoreKey": {
"items": {
"name": "John"
}
}
}
}`
var obj = JSON.parse(json, (k, v) => k == 'name' && /^test name/.test(v) ? 'N/A' : v)
console.log( obj )
A javascript object is for all intents and purposes a tree — though it can be, and may well be, a directed graph — that quite possibly may be cyclic meaning a node in the graph points back to own of its own parents. Following a cycle can result in never-ending recursion or loop.
You want to use something like traverse to do what you're talking about. It takes care of all the stuff that makes traversing a graph hassle — dealing with cycles in the graph and the like.
https://www.npmjs.com/package/traverse
https://github.com/substack/js-traverse
const traverse = require('traverse');
. . .
var scrubbed = traverse(obj).map( function(value) {
const isTestName = this.key === 'name'
&& value
&& /^test name/i.test(value)
;
if (isTestName) {
this.update('N/A');
}
});
NOTE: The callback function given to travese can't be an arrow function (() => {...} as that function's this context is the traverse context for the current node being inspected.
That traverse context also gives you access to the entire path from the root down to the current node, along with an upward link to the parent node's traverse context.
Do something like this. Convert to string replace using regex (add key to regex as well) and then convert back.
var data = {
"responses": {
"firstKey": {
"items": {
"name": "test name one"
}
},
"anotherKey": {
"items": {
"name": "test name two"
}
},
"oneMoreKey": {
"items": {
"name": "John"
}
}
}
};
var originalMsg = JSON.stringify(data);
console.log(data)
console.log(originalMsg)
var updatedMsg = originalMsg.replace(/test name [a-z]*/g, "N/A");
console.log(updatedMsg)
var newObj = JSON.parse(updatedMsg);
console.log(newObj);
I'm working on an app in javascript, and I have this json object (this is a simplified example of the actual json object)
{
"data": [
"user": {
"pictures"{
"sizes"[
0: {
"link": "http://www"
},
1: {
"link": "http://"
}
]
}
}
]
}
And I want to get the value of link, so i tried data.user.pictures.sizes[0].link, and it returned an error. How to get the right value?
edit: I'm using a map function to loop around the data object, so i can display values in an html page. it works for most of the other items, except for pictures sizes, i cant seem to get the value of the second item in the sizes array.
From the data, it shows that 'data' contains array and sizes contains array that contains object of properties 0 and 1 , so do this
data[0].user.pictures.sizes[0].0.link
First of all you need to have colons to "pictures" and "sizes" :)
Secondly, it depends what your structure is.
for example:
if you need to use arrays as they appear in your example:
var a = {
"data": [
{
"user": {
"pictures": {
"sizes": [
{
0: {
"link": "http://www"
}
},
{
1: {
"link": "http://"
}
}
]
}
}
}
]
}
console.log(a.data[0].user.pictures.sizes[0][0].link);
or you just need a json without arrays in it:
var b = {
"data": {
"user": {
"pictures": {
"sizes": {
0: {
"link": "http://www"
},
1: {
"link": "http://"
}
}
}
}
}
}
console.log(b.data.user.pictures.sizes[0].link);
or you can even mix them:
var c = {
"data": {
"user": {
"pictures": {
"sizes": [
{
"link": "http://www"
},
{
"link": "http://"
}
]
}
}
}
}
console.log(c.data.user.pictures.sizes[0].link);
notice the subtle difference of "sizes" in b and c and the double array of a. This is because json with index as keys is the same as an array. check this example:
var example = [
{
0: "example00",
1: "example01",
2: "example02"
},
{
0: "example10",
1: "example11",
2: "example12"
},
]
console.log(example[0][1]); // == example01
you can see that you have 2 arrays within the example array
hope that helps :)
Your JSON is wrong, it should be as below :
var js = {
"data": {
"user":{
"pictures":{
"sizes":[
{
"link":"http://www"
},
{
"link":"http://"
}
]
}
}
}
}
To get the value:
js.data.user.pictures.sizes[0].link
I have a Javascript object with a format like below
"items":
{
"Groups":[
{
"title":"group 1",
"SubGroups":[
{
"title":"sub1",
"id" : "1",
"items":[
{
"title":"Ajax request 1",
},
{
"title":"Ajax request 2",
}
]
},
{
"title":"sub2",
"id" : "2",
"items":[
{
"title":"Ajax request 3",
},
{
"title":"Ajax request 4",
}
]
}
]
}
]
There are n 'Groups', n 'subGroups' and n 'items'.
What I want to do firstly is get all the items from a particular group based on id. This is achieved using:
_.each(items.Groups, function(o) {
result = _.where(o.SubGroups, {
'id': '1'
});
});
which returns
"items":[{"title":"Ajax request 1",},{"title":"Ajax request 2",}]
Then I want to get the rest of the data, excluding the items and parent group I have just retrieved.
I tried this:
_.each(items.Groups, function(o) {
arr = _.without(o.SubGroups, _.findWhere(o.SubGroups, {id: '2'}));
});
But this only returns me the items like this:
{
"title":"sub2",
"id" : "2",
"items":[{"title":"Ajax request 3"},{"title":"Ajax request 4",}]
}
whereas what I need is this:
"items":
{
"Groups":[
{
"title":"group 1",
"SubGroups":[
{
"title":"sub2",
"id" : "2",
"items":[
{
"title":"Ajax request 3",
},
{
"title":"Ajax request 4",
}
]
}
]
}
]
Just try this:
_.each(items.Groups, function(o) {
arr = _.without(o, _.findWhere(o.SubGroups, {id: '2'}));
});
o should be enough => you want to get Groups and not SubGroups.
Following is a pure JS implementation:
JSFiddle.
var data = {
"Groups": [{
"title": "group 1",
"SubGroups": [{
"title": "sub1",
"id": "1",
"items": [{
"title": "Ajax request 1",
}, {
"title": "Ajax request 2",
}]
}, {
"title": "sub2",
"id": "2",
"items": [{
"title": "Ajax request 3",
}, {
"title": "Ajax request 4",
}]
}]
}]
}
var items = [];
var group = [];
data.Groups.forEach(function(o) {
var _tmp = JSON.parse(JSON.stringify(o));
_tmp.SubGroups = [];
o.SubGroups.forEach(function(s) {
if (s.id == "1") {
items.push(s.items);
} else {
_tmp.SubGroups.push(s);
group.push(_tmp)
}
});
});
function printObj(label, obj) {
document.write(label + "<pre>" + JSON.stringify(obj, 0, 4) + "</pre>")
}
printObj("group", group);
printObj("items", items);
Using underscore and using your logic to filter all subgroups:
//array to store subgroup with ID 1
var results = [];
var d = _.each(data.items.Groups, function(o) {
result = _.where(o.SubGroups, {
'id': '1'
});
//add to results array
results.push(result);
});
//make a clone of the earlier object so that you get the parent structure.
var data1 = _.clone(data);
//set the filtered results to the group
data1.items.Groups = results;
//your data as you want
console.log(data1)
Working code here
I'm building the JSON object using JavaScript. How would I inset the following data to the bottom of the stack:
"hello": { "label":"Hello", "url":"#hello" }
in to the following variable:
var ListData = {
"main": {
"label":"Main",
"url":"#main"
},
"project": {
"label":"Project",
"url":"#project"
},
"settings": {
"label":"Settings",
"url":"#settings",
"subnav":[
{
"label":"Privacy",
"url":"#privacy"
},
{
"label":"Security",
"url":"#security"
},
{
"label":"Advanced",
"url":"#advanced"
}
]
}
};
So the variable looks like:
var ListData = {
"main": {
"label":"Main",
"url":"#main"
},
"project": {
"label":"Project",
"url":"#project"
},
"settings": {
"label":"Settings",
"url":"#settings",
"subnav":[
{
"label":"Privacy",
"url":"#privacy"
},
{
"label":"Security",
"url":"#security"
},
{
"label":"Advanced",
"url":"#advanced"
}
]
},
"hello": {
"label":"Hello",
"url":"#hello"
}
};
I used the following code but it doesn't seem to work:
var NewData = '"hello": { "label":"Hello", "url":"#hello" }';
ListData.push(NewData);
You can insert it directly with an object literal:
ListData.hello = { label: "Hello", url: "#hello" };
If you are using jQuery, you can use the .extend() jQuery API like:
$.extend(ListData, {"hello": { "label":"Hello", "url":"#hello" }});
I have one more solution using underscore.js module,
var _ = require("underscore");
var src = {
"main": {
"label": "Main",
"url": "#main"
},
"project": {
"label": "Project",
"url": "#project"
},
"settings": {
"label": "Settings",
"url": "#settings",
"subnav": [
{
"label": "Privacy",
"url": "#privacy"
},
{
"label": "Security",
"url": "#security"
},
{
"label": "Advanced",
"url": "#advanced"
}
]
}
};
var dest = {"hello": { "label":"Hello", "url":"#hello" }};
var data = _.extend(src, dest);
console.log(JSON.stringify(data));
Required op :
{"main":{"label":"Main","url":"#main"},"project":{"label":"Project","url":"#project"},"settings":{"label":"Settings","url":"#settings","subnav":[{"label":"Privacy","url":"#privacy"},{"label":"Security","url":"#security"},{"label":"Advanced","url":"#advanced"}]},"hello":{"label":"Hello","url":"#hello"}}
Keeping with you object literal statements just add another object to your ListData object.
ListData.hello = { "label":"Hello", "url":"#hello" };
push is only for Javascript Arrays.
A JavaScript Object Literal is a comma-separated list of name/value pairs wrapped by a pair of curly braces.
To append the property name of encampment name with a value of Valley Forge to the bottom of the stack, simply add the property name after the JSON object with a dot syntax. Then specify the value. (See 'Append data' below)
You can also delete the appended name/value pair from the object literal. (See 'Delete data below')
// Start with some JSON
var myJson = { "name":"George Washington", "rank":"General", "serial":"102" };
// Append data
myJson.encampment = "Valley Forge";
// Delete data
delete myJson.encampment