Dojo: How to load an object (containing other objects) from JSON? - javascript

I have an object model that I want to be able to save. I am going to export it to JSON and then read it back in as JSON.
Saving to JSON is easy. Just use this: JSON.stringify(this).
Loading from JSON isn't as simple.
We can't just use this = JSON.parse(someJson) because the methods wont be attached.
Using something like lang.mixin(this, JSON.parse(someJson)) will get the functions but objects that are
Photo Class:
define([...], function(...){
return declare(null, {
name: ..., // String
url:..., // String
complexProperty:..., // Some other class
someFunction1: function(...){..},
someFunction2: function(...){..},
someFunction2: function(...){..}
}
));
Photo Album Class:
define([...], function(...){
return declare(null, {
photos: [], /* Array of type Photo (see above) */
someOtherProperty: ...,
someOtherProperty: ...,
someFunction1: function(...){..},
someFunction2: function(...){..},
someFunction2: function(...){..},
toJson: function(){
return JSON.stringify(this); // From dojo/json
}
loadFromJson: function(jsonIn){
// How to do this?
},
/* This doesn't work because methods will be overridden */
loadFromJson1: function(jsonIn){
this = JSON.parse(someJson);
},
/* This insures that my methods are kept intact but my childrens methods arn't (ie: the array of photos) */
loadFromJson2: function(jsonIn){
lang.mixin(this, JSON.parse(someJson));
},
/* This seems like an aweful lot of work. Any better ways to do this? */
loadFromJson3: function(jsonIn){
this.someOtherProperty = jsonIn.someOtherProperty;
this.someOtherProperty = jsonIn.someOtherProperty;
foreach(jsonIn.photos: photoJson){
var newPhoto = new Photo();
newPhoto.loadfromJson(photoJson);
this.photos.add(newPhoto);
}
... All other properties set recursively. All things in model now need this method ...
}
}
));

I think you would be better off returning a JSON object that contains just the data you need to serialize, not the whole class. Then your loadFromJson method would be a little easier to implement, and you wont be sending unnecessary data over the network. Example toJson():
toJson: function() {
return JSON.stringify({
photos: this.photos,
someImportantProp: this.someImportantProp,
anotherProp: this.anotherProp
});
}

JSON is not the same thing as a JavaScript object, in fact, it's only a subset. JSON only allows arrays, objects and of course basic types like Strings, booleans, numbers and null. You can find the entire specification here.
If you really want to keep the functions you can use the eval() function, but this is not really recommended, because it indeed parses those functions. If the evaluated content contains malicious input, then that is being executed as well.
For example:
eval("myObj = { getSum: function getSum(a, b) { return a + b; } }");
myObj.getSum(1, 2); // Returns 3
You can better attempt to save the state of the object (name and url for example) and rebuild it once you parse it again, that is what happens in other programming languages as well. For example, if you're serializing/deserializing an object in Java.

Related

Js saving dictionaries in localStorage not working [duplicate]

Is this possible to insert to localstorage or is there other way to store this?
$('#pass_to_score').on('click',function(){
var compressed = function(){
$('.whole_wrap_of_editcriteria').css('display','none');
$('#wrappler').css('display','block');
$('#li_addcriteria').css('display','none');
$('#li_menu1').addClass('active');
$('#home').removeClass('active');
$('#menu1').addClass('active');
$('#title_panel').html('Edit criteria scoring');
}
compressed();
localStorage.setItem('compressed', compressed());
//i also try set as JSON.stringify but its undefined
});
I don't know why you'd want that, I would not recommend it, but you can do it using toString.
Store it:
var myFunc = function (){
alert('Hello world!');
};
// Store it as a String
localStorage.setItem('compressedFunc', myFunc.toString());
Later, retrieve it:
var compressedFunc = localStorage.getItem('compressedFunc');
// Convert the String back to a function
var myFunc = eval('(' + compressedFunc + ')');
// Use it
myFunc();
If you have the function encoded in JavaScript, there would be no need to restore it ever from localStorage, as you already have it available.
You cannot JSON encode a function. You could save the source code of the function, and upon retrieval apply eval to it. But as all know, this has certain risks, captured in the phrase "eval is evil".
You could limit that risk a bit, if you would invent an object structure that closely describes what the function should do. In your case, every statement in the function is a method applied to a jQuery selector. This you could represent with the following object:
var compressed = [
{ selector: '.whole_wrap_of_editcriteria', method: 'css', args: ['display', 'none'] },
{ selector: '#wrappler', method: 'css', args: ['display', 'none'] },
{ selector: '#li_addcriteria', method: 'css', args: ['display','none'] },
{ selector: '#li_menu1', method: 'addClass', args: ['active'] },
{ selector: '#home', method: 'removeClass', args: ['active'] },
{ selector: '#menu1', method: 'addClass', args: ['active'] },
{ selector: '#title_panel', method: 'html', args: ['Edit criteria scoring'] }
];
Then your actual function, could take that object as its input, and process it, resulting in the same effect:
var applyInstructions = function(instructions) {
instructions.forEach( cmd => $(cmd.selector)[cmd.method](...cmd.args) );
}
Now, when you want to save this knowledge to localStorage, you only need to store the above constructed object, like so:
// save to localStorage:
localStorage.setItem('compressed', JSON.stringify(compressed));
And after retrieving it, you would execute the generic function on it:
// get from localStorage:
applyInstructions(JSON.parse(localStorage.getItem('compressed')));
This is similar to using eval, but it has the advantage that you can put limits to what can be executed (in applyInstructions). You could for instance check that the method attribute should only be one of the values css, addClass, removeClass, and html, although that last one could be a dangerous one to keep. text would be a better alternative, if possible.
Here is how that safer applyInstructions could look like:
var applyInstructions = function(instructions) {
if (instructions.some(
cmd => ['css','addClass','removeClass','textContent'].indexOf(cmd.method)==-1)) {
throw "Invalid instructions object";
}
instructions.forEach( cmd => $(cmd.selector)[cmd.method](...cmd.args) );
}

Is there a way to not include methods when converting an object to a json?

Right now, I'm trying to convert a object to a json, but the object has methods in it.
One common solution I've found is storing the function in the json, but that wouldn't work for me (mainly because that would totally break updating)
I think it will be easier to reinitialize the function every time the program is reloaded. What would be the best way to exclude functions when stringifying to json, but including the functions when the json is parsed?
Extra note: some of the functions are stored in arrays and other objects
Edit:
Current code to load json data:
var loadData = JSON.parse(window.localStorage.getItem("slotAuto"));
G = {
...G,
...loadData
};
Current code to save json data:
var toSave = JSON.stringify(G, G.ReplaceNull);
console.log(toSave);
window.localStorage.setItem("saveGame", toSave);
Replace Null function:
if (value == null) {
return undefined;
};
return value
Example object:
{
functionList: [function () {
console.log("do something")
}]
}
Example output:
{
"functionList": [null]
}

Mongoose/MongoDB result fields appear undefined in Javascript

Is there something that I'm missing that would allow item to log as an object with a parameter, but when I try to access that parameter, it's undefined?
What I've tried so far:
console.log(item) => { title: "foo", content: "bar" } , that's fine
console.log(typeof item) => object
console.log(item.title) => "undefined"
I'll include some of the context just in case it's relevant to the problem.
var TextController = function(myCollection) {
this.myCollection = myCollection
}
TextController.prototype.list = function(req, res, next) {
this.myCollection.find({}).exec(function(err, doc) {
var set = new Set([])
doc.forEach(function(item) {
console.log(item) // Here item shows the parameter
console.log(item.title) // "undefined"
set.add(item.title)
})
res.json(set.get());
})
}
Based on suggestion I dropped debugger before this line to check what item actually is via the node repl debugger. This is what I found : http://hastebin.com/qatireweni.sm
From this I tried console.log(item._doc.title) and it works just fine.. So, this seems more like a mongoose question now than anything.
There are questions similar to this, but they seem to be related to 'this' accessing of objects or they're trying to get the object outside the scope of the function. In this case, I don't think I'm doing either of those, but inform me if I'm wrong. Thanks
Solution
You can call the toObject method in order to access the fields. For example:
var itemObject = item.toObject();
console.log(itemObject.title); // "foo"
Why
As you point out that the real fields are stored in the _doc field of the document.
But why console.log(item) => { title: "foo", content: "bar" }?
From the source code of mongoose(document.js), we can find that the toString method of Document call the toObject method. So console.log will show fields 'correctly'. The source code is shown below:
var inspect = require('util').inspect;
...
/**
* Helper for console.log
*
* #api public
*/
Document.prototype.inspect = function(options) {
var isPOJO = options &&
utils.getFunctionName(options.constructor) === 'Object';
var opts;
if (isPOJO) {
opts = options;
} else if (this.schema.options.toObject) {
opts = clone(this.schema.options.toObject);
} else {
opts = {};
}
opts.minimize = false;
opts.retainKeyOrder = true;
return this.toObject(opts);
};
/**
* Helper for console.log
*
* #api public
* #method toString
*/
Document.prototype.toString = function() {
return inspect(this.inspect());
};
Make sure that you have defined title in your schema:
var MyCollectionSchema = new mongoose.Schema({
_id: String,
title: String
});
Try performing a for in loop over item and see if you can access values.
for (var k in item) {
console.log(item[k]);
}
If it works, it would mean your keys have some non-printable characters or something like this.
From what you said in the comments, it looks like somehow item is an instance of a String primitive wrapper.
E.g.
var s = new String('test');
typeof s; //object
s instanceof String; //true
To verify this theory, try this:
eval('(' + item + ')').title;
It could also be that item is an object that has a toString method that displays what you see.
EDIT: To identify these issues quickly, you can use console.dir instead of console.log, since it display an interactive list of the object properties. You can also but a breakpoint and add a watch.
Use findOne() instead of find().
The find() method returns an array of values, even if you have only one possible result, you'll need to use item[0] to get it.
The findOne method returns one object or none, then you'll be able to access its properties with no issues.
Old question, but since I had a problem with this too, I'll answer it.
This probably happened because you're using find() instead of findOne(). So in the end, you're calling a method for an array of documents instead of a document, resulting in finding an array and not a single document. Using findOne() will let you get access the object normally.
A better way to tackle an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id) // or title
}
})
and now it will work
You don't have whitespace or funny characters in ' title', do you? They can be defined if you've quoted identifiers into the object/map definition. For example:
var problem = {
' title': 'Foo',
'content': 'Bar'
};
That might cause console.log(item) to display similar to what you're expecting, but cause your undefined problem when you access the title property without it's preceding space.
I think using 'find' method returns an array of Documents.I tried this and I was able to print the title
for (var i = 0; i < doc.length; i++) {
console.log("iteration " + i);
console.log('ID:' + docs[i]._id);
console.log(docs[i].title);
}
If you only want to get the info without all mongoose benefits, save i.e., you can use .lean() in your query. It will get your info quicker and you'll can use it as an object directly.
https://mongoosejs.com/docs/api.html#query_Query-lean
As says in docs, this is the best to read-only scenarios.
Are you initializing your object?
function MyObject()
{
this.Title = "";
this.Content = "";
}
var myo1 = new MyObject();
If you do not initialize or have not set a title. You will get undefined.
When you make tue query, use .lean() E.g
const order = await Order.findId("84578437").lean()
find returns an array of object , so to access element use indexing, like
doc[0].title

delete JS Object attributes based on a JSON file

I have certain objects that I have to delete certain properties ie:
objA = { firstAttrA: 'fooA', secondAttrA: 'barA' }
objB = { firstAttrB: 'fooB', secondAttrB: 'barB' }
I want to pass these objects in a function that will delete the firstAttrA and firstAttrB based on the following properties file:
{
"objA":"firstAttrA",
"objB":"firstAttrB"
}
The method needs to be robust, I need to avoid excessive looping and anything that will affect performance since the amount of objects that will essentially be passed is great and their properties numerous.
Essentially i suppose I need to do a delete objA.firstAttrA; delete objB.firstAttrB; but driven by a JSON properties file.
Well if defined in the global scope then your method would look like this:
var objRef = {
"objA":"firstAttrA",
"objB":"firstAttrB"
};
for (var item in objRef) {
if (window.hasOwnProperty(item)) {
if (window[item].hasOwnProperty(objRef[item])) {
delete window[item][objRef[item]];
}
}
}

Creating methods on the fly

Hi I'm trying to author a jQuery plugin and I need to have methods accessible to elements after they are initialized as that kind of object, e.g.:
$('.list').list({some options}); //This initializes .list as a list
//now I want it to have certain methods like:
$('.list').find('List item'); //does some logic that I need
I tried with
$.fn.list = function (options) {
return this.each(function() {
// some code here
this.find = function(test) {
//function logic
}
}
}
and several other different attempts, I just can't figure out how to do it.
EDIT:
I'll try to explain this better.
I'm trying to turn a table into a list, basically like a list on a computer with column headers and sortable items and everything inbetween. You initiate the table with a command like
$(this).list({
data: [{id: 1, name:'My First List Item', date:'2010/06/26'}, {id:2, name:'Second', date:'2010/05/20'}]
});
.list will make the <tbody> sortable and do a few other initial tasks, then add the following methods to the element:
.findItem(condition) will allow you to find a certain item by a condition (like findItem('name == "Second"')
.list(condition) will list all items that match a given condition
.sort(key) will sort all items by a given key
etc.
What's the best way to go about doing this?
If you want these methods to be available on any jQuery object, you will have to add each one of them to jQuery's prototype. The reason is every time you call $(".list") a fresh new object is created, and any methods you attached to a previous such object will get lost.
Assign each method to jQuery's prototype as:
jQuery.fn.extend({
list: function() { .. },
findItem: function() { .. },
sort: function() { .. }
});
The list method here is special as it can be invoked on two occasions. First, when initializing the list, and second when finding particular items by a condition. You would have to differentiate between these two cases somehow - either by argument type, or some other parameter.
You can also use the data API to throw an exception if these methods are called for an object that has not been initialized with the list plugin. When ('xyz').list({ .. }) is first called, store some state variable in the data cache for that object. When any of the other methods - "list", "findItem", or "sort" are later invoked, check if the object contains that state variable in its data cache.
A better approach would be to namespace your plugin so that list() will return the extended object. The three extended methods can be called on its return value. The interface would be like:
$('selector').list({ ... });
$('selector').list().findOne(..);
$('selector').list().findAll(..);
$('selector').list().sort();
Or save a reference to the returned object the first time, and call methods on it directly.
var myList = $('selector').list({ ... });
myList.findOne(..);
myList.findAll(..);
myList.sort();
I found this solution here:
http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
This seems to do exactly what I need.
(function($) {
var TaskList = function(element, options)
{
var $elem = $(element);
var options = $.extend({
tasks: [],
folders: []
}, options || {});
this.changed = false;
this.selected = {};
$elem.sortable({
revert: true,
opacity: 0.5
});
this.findTask = function(test, look) {
var results = [];
for (var i = 0,l = options.tasks.length; i < l; i++)
{
var t = options['tasks'][i];
if (eval(test))
{
results.push(options.tasks[i]);
}
}
return results;
}
var debug = function(msg) {
if (window.console) {
console.log(msg);
}
}
}
$.fn.taskList = function(options)
{
return this.each(function() {
var element = $(this);
if (element.data('taskList')) { return; }
var taskList = new TaskList(this, options);
element.data('taskList', taskList);
});
}
})(jQuery);
Then I have
$('.task-list-table').taskList({
tasks: eval('(<?php echo mysql_real_escape_string(json_encode($tasks)); ?>)'),
folders: eval('(<?php echo mysql_real_escape_string(json_encode($folders)); ?>)')
});
var taskList = $('.task-list-table').data('taskList');
and I can use taskList.findTask(condition);
And since the constructor has $elem I can also edit the jQuery instance for methods like list(condition) etc. This works perfectly.
this.each isn't needed. This should do:
$.fn.list = function (options) {
this.find = function(test) {
//function logic
};
return this;
};
Note that you'd be overwriting jQuery's native find method, and doing so isn't recommended.
Also, for what it's worth, I don't think this is a good idea. jQuery instances are assumed to only have methods inherited from jQuery's prototype object, and as such I feel what you want to do would not be consistent with the generally accepted jQuery-plugin behaviour -- i.e. return the this object (the jQuery instance) unchanged.

Categories

Resources