I use fullcalendar jquery plugin from http://arshaw.com/fullcalendar/
and I use knockout js on my website.
I added the event array what is the source of the calendar. The user can modify the events (array).
Then I want to serialize the event array, send by ajax, but I can not, because the calendar modifies my array, and puts an cycle into the source array. How can I remove the changes. Why is there a cycle in my array? I read, may be there is an DOM object in this.
Chrome sendrequest error: TypeError: Converting circular structure to JSON
var a = [];
a.push({
title: "Event2",
start: "2013-09-05"
});
a.push({
title: "Event2",
start: "2013-09-15"
});
$("#calendar").fullCalendar({
events: a,
header: {
left: "title",
center: "",
right: "today prev,next"
},
editable: false
});
console.log(JSON.stringify(a));
TypeError: Converting circular structure to JSON
How can I fix it?
What is the cause of the cycle?
fiddle example, you can see my problem:
http://jsfiddle.net/erbsaag/XC3NH/1
The plugin is modifying your data.
If you run
console.log(a)
before your console.log you can see the issue. One solution is to only return the fields you need, and not return the fields which have cyclical recursions.
Example:
console.log(JSON.stringify(a.map(function(ai) { return {title: ai.title, start: ai.start}})));
Please refer to this question:
JSON.stringify, avoid TypeError: Converting circular structure to JSON
Here is a function adapting that answer into a simple reusable one-liner:
const jsonStringifySafe = (o) => {
// Almost as seen in stackoverflow.com/questions/11616630
var cache = [];
let retv = JSON.stringify(o, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
return;
}
// Store value in our collection
cache.push(value);
}
return value;
});
cache = null;
return retv;
}
usage:
console.log(jsonStringifySafe(a));
Example:
http://jsfiddle.net/felyper/XC3NH/17/
Related
I'm adding some APIs to a JavaScript project to replace what used to be multi-field manual data entry with lookups. (E.g., we want to stop asking you 20 questions about a car, and instead just ask your VIN and autopopulate the other 19 answers from a VIN decoder.)
I'm spending more time than I'd like mapping data from the response schema into the existing internal schema of my app. Lots of the work requires a human touch to suss out synonyms, like:
internal.postal_code = api.zipCode;
Some times I find myself writing a really gnarly if to avoid a sometimes-null or missing object half way down a deep tree, like
if(api.a && api.a.b && api.a.b.c){
internal.z = api.a.b.c.d;
}
Is there a good library that would let me write a simple map and do all this work for me? A map might look like:
map = {
'zipCode' : 'postal_code',
'a.b.c.d' : 'z'
};
mapperTool( api, internal, map );
(Note the internal object is stitched together from several APIs and pre-existing tools, so adding or overwriting properties on internal is better than outputting a new object.)
Try this:
function mapperTool(source, desc, map) {
Object.keys(map).forEach(function(key) {
var value = key.split('.').reduce(function(obj, name) {
if (obj && obj[name]) {
return obj[name];
}
}, source);
desc[map[key]] = value;
});
}
var map = {
'zipCode' : 'postal_code',
'a.b.c.d' : 'z'
};
var internal = {};
mapperTool({a:{b:{c:{d: 10}}},zipCode:20}, internal, map);
document.body.innerHTML = '<pre>' + JSON.stringify(internal, true, 4) + '</pre>';
As title, when I try to do:
myString = JSON.stringify($('#calendar').fullcalendar('clientEvents'));
it fails. I tried to alert myString but I see a series of [Object object], ... . But if i try to alert myArray[0].title for example, it returns correctly.
Where I'm doing wrong?
P.S. The goal is to obtain a string to save on a file via AJAX.
Your results tell you that the objects in the array that the fullCalendar clientEvents method gives you can't be directly converted to JSON. I get slightly different results on the http://fullcalendar.io page (I get an error about trying to convert a circular structure); I assume that's down to differences either in the FullCalendar version you're using vs. they're using, or differences in how your browser and mine deal with circular structures. Either way, the objects apparently can't be used as-is.
The goal is to obtain a string to save on a file via AJAX.
You can do that by using map on the array to get objects that can be converted to JSON successfully, whitelisting the properties you want (or blacklisting the ones you don't want).
Here's an example whitelisting the start, end, and title properties:
var json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(function(e) {
return {
start: e.start,
end: e.end,
title: e.title
};
}));
Heres one blacklisting source and any property starting with _:
var json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(function(e) {
var rv = {};
Object.keys(e)
.filter(function(k) {
return k != "source" && !k.startsWith("_");
})
.forEach(function(k) {
rv[k] = e[k];
});
return rv;
}));
...which worked for me on their site.
Here are ES2015 versions of both of those:
Whitelisting:
let json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(e => ({
start: e.start,
end: e.end,
title: e.title
})));
Blacklisting:
let json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(e => {
let rv = {};
Object.keys(e)
.filter(k => k != "source" && !k.startsWith("_"))
.forEach(k => {
rv[k] = e[k];
});
return rv;
}));
From the example JSON below, I would like to return the target.id value of an object where the source.id == 'x'.
So where source.id == 'startId' return target.id == '3eecd840-e6a8-423c-a892-7df9646fde5d'.
{
"line":[
{
"type":"link",
"source":{
"id":"startId",
"port":"out"
},
"target":{
"id":"3eecd840-e6a8-423c-a892-7df9646fde5d",
"port":"in"
},
"id":"87d88a26-3a28-4db0-8016-71c1c4665f14"
},
{
"type":"link",
"source":{
"id":"3eecd840-e6a8-423c-a892-7df9646fde5d",
"port":"outYes"
},
"target":{
"id":"49940c02-70f2-4c53-ab50-9cbf96903600",
"port":"in"
},
"id":"9f8c365e-9ca7-440f-a722-c4f340782c0c"
}
]
}
I've tried JSONPath, but I cannot work out the expression to use. $.line[*].source.id gives me a list of source id's and $.line[?(#.source.id=='startId')] returns an error.
I also understand that I could iterate through each object in code, but It wouldn't be very efficient if I have tens or hundreds of 'lines' to work through. If possible I would like a more 'direct' path to the object.
I'm using javascript to code the rest of the app, so javascript examples would be helpful (with or without JSONPath).
If you're getting json as string, then use var json = JSON.parse(jsonStr). Then you can do it with Array.filter
var result = json.line.filter(function(obj){
return obj.source.id == "startId"
});
Then you could get the values like this
var ids = result.map(function(o){ return o.target.id });
With a javascript json object like this:
var data = {
blog : {
title: "my blog",
logo: "blah.jpg",
},
posts : [
{
title: "test post",
content: "<p>testing posts</p><br><p>some html</p>"
},
]
}
var lookup = "blog.title" //this gets generated from a template file
Now I know you can do something like, but these don't quite do what I need:
console.log(data['blog']); //works
console.log(data.blog.title); //works
console.log(data['blog']['title']); //works, but i dont know the depth of the lookup
But I need to be able to do something like the code below because I can't hardcode the structure, it gets generated and stored in lookup each time. Do I have to build this functionality using string cutting and recursion?? I really hope not
console.log(data['blog.title']); //does not work
console.log(data[lookup]); //does not work
EDIT....
Okay, possibly found a workaround. I don't know if this is safe or recommended practice, so comments on that would be great. Or alternative methods. So combining this with the code above.
var evaltest = "var asdf ="+JSON.stringify(data)+";\n"
evaltest += "asdf."+lookup
console.log(eval(evaltest)) //returns correctly "my blog" as expected
You could use dottie https://www.npmjs.org/package/dottie, which allows you to deep traverse an object using strings
var values = {
some: {
nested: {
key: 'foobar';
}
}
}
dottie.get(values, 'some.nested.key'); // returns 'foobar'
dottie.get(values, 'some.undefined.key'); // returns undefined
you could use:
data['blog']['title']
I've experimented with a couple ways of doing this including eval and using a dictionary lookup with switch(exp.length). This is the final version (comments stripped) I created:
var deepRead = function (data, expression) {
var exp = expression.split('.'), retVal;
do {
retVal = (retVal || data)[exp.shift()] || false;
} while (retVal !== false && exp.length);
return retVal || false;
};
//example usage
data = {
a1: { b1: "hello" },
a2: { b2: { c2: "world" } }
}
deepRead(data, "a1.b1") => "hello"
deepRead(data, "a2.b2.c2") => "world"
deepRead(data, "a1.b2") => false
deepRead(data, "a1.b2.c2.any.random.number.of.non-existant.properties") => false
Here's the Gist with full commenting: gist.github.com/jeff-mccoy/9700352. I use this to loop over several thousand items and have had no issues with deep-nested data. Also, I'm not wrapping in try/catch anymore due to the (small) performance hit: jsPerf.
Actually I want to search an attribute's value in an json array for one of its child. Now one condition is that the attribute will not be there in all the child's of the array. This is my json array.
[{
"heading1":"heading1",
"heading2":"heading2",
"heading3":"heading3",
"heading4":"heading4",
"heading5":"heading5",
"heading6":"heading6"
},
{
"column1":65536,
"column2":"school",
"column3":"testing purpose",
"column4":"DESKTOP",
"column5":"ACTIVE",
"column6":true,
"column7":"a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
},
{
"column1":98305,
"column2":"Nikhil",
"column3":"Test",
"column4":"LAPTOP",
"column5":"ACTIVE",
"column6":true,
"column7":"a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
}]
So presently I am working with the each loop but like this
var obj = $.parseJSON(JSON.stringify(response));
$.each(obj, function () {
console.log("heading1", this['heading1']);
});
Here response comes from mserver and it is the json array
Now I want to know can I search for this attribute in the json array without using a loop in jQuery.
Based on your sample code what I understand you have is an array of objects and you want to find objects with one specific property and or value:
This will return true if the object has the property
var results= arr.filter(function(item){ return item.hasOwnProperty("column5"); });
Or you can perform additional action when you find the property:
arr.filter(function(item){
if (item.hasOwnProperty("column5")) {
return item["column5"] === 'demo 01'; //or item.column5 === 'demo 01'
}
return false;
});
This only works on IE9+ if you need this to run in older versions of IE, please follow the instructions under polyfill:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
The you can check like
var obj = $.parseJSON(response);
$.each(obj, function (index,value) {
if(typeof obj[index].heading2 !== "undefined")
{
alert(obj[index].heading2);
}
when in other object of array element not find then it returns undefined. and you can check like that.
you can check in this http://jsfiddle.net/gKRCH/
It's best to use a loop. But if the format of the JSON is regular, you could regex for the value in the response string.
I'm not recommending this method, just pointing out that it exists.
var value = "heading1";
if( (new RegExp('"' + value + '"')).test(response) ){
// Found value
};
Here, we take the required value, wrap it in quotation marks and search for it in the response.
This has several issues, such as:
It might find the pattern in a property name
If the value could contain regex special characters, they'll need escaping.
If your JSON contains values with escaped quotation marks, you could get a false positive from partial matches.
That's why it depends on you knowing the format of the data.
EDIT:
You can solve issue 2 by using this condition instead of regex. But it gives you less flexibility.
response.indexOf('"' + value + '"') !== -1
Try this,
$.each(object,function(key, value){
console.log(key);
console.log(value);
});
You can use this JS lib; DefiantJS (http://defiantjs.com). This lib extends the global object JSON with the method "search" - with which, you can perform XPath queries on JSON structures. Like the one you have exemplified.
With XPath expressions (which is standardised query language), you can find whatever you're looking for and DefiantJS will do the heavy-lifting for you - allowing your code to be neat and clean.
Here is the fiddle of this code:
http://jsfiddle.net/hbi99/q8xst/
Here is the code:
var data = [
{
"heading1": "heading1",
"heading2": "heading2",
"heading3": "heading3",
"heading4": "heading4",
"heading5": "heading5",
"heading6": "heading6"
},
{
"column1": 65536,
"column2": "school",
"column3": "testing purpose",
"column4": "DESKTOP",
"column5": "ACTIVE",
"column6": true,
"column7": "a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
},
{
"column1": 98305,
"column2": "Nikhil",
"column3": "Test",
"column4": "LAPTOP",
"column5": "ACTIVE",
"column6": true,
"column7": "a6cc82e0-a8d8-49b8-af62-cf8ca042c8bb"
}
],
res = JSON.search( data, '//*[column4="DESKTOP"]' );
console.log( res[0].column2 );
// school