admin管理员组文章数量:1290896
What's the quickest and easiest way to convert my json, containing the data of the objects, into actual objects with methods attached?
By way of example, I get data for a fruitbowl with an array of fruit objects which in turn contain an array of seeds thus:
{"fruitbowl": [{
"name": "apple",
"color": "red",
"seeds": []
},{
"name": "orange",
"color": "orange",
"seeds": [
{"size":"small","density":"hard"},
{"size":"small","density":"soft"}
]}
}
That's all nice and good but down on the client we do stuff with this fruit, like eat it and plant trees...
var fruitbowl = []
function Fruit(name, color, seeds){
this.name = name
this.color = color
this.seeds = seeds
this.eat = function(){
// munch munch
}
}
function Seed(size, density){
this.size = size
this.density = density
this.plant = function(){
// grow grow
}
}
My ajax's success routine currently is currently looping over the thing and constructing each object in turn and it doesn't handle the seeds yet, because before I go looping over seed constructors I'm thinking
Is there not a better way?
success: function(data){
fruitbowl.length = 0
$.each(data.fruitbowl, function(i, f){
fruitbowl.push(new Fruit(f.name, f.color, f.seeds))
})
I haven't explored looping over the objects as they are and attaching all the methods. Would that work?
What's the quickest and easiest way to convert my json, containing the data of the objects, into actual objects with methods attached?
By way of example, I get data for a fruitbowl with an array of fruit objects which in turn contain an array of seeds thus:
{"fruitbowl": [{
"name": "apple",
"color": "red",
"seeds": []
},{
"name": "orange",
"color": "orange",
"seeds": [
{"size":"small","density":"hard"},
{"size":"small","density":"soft"}
]}
}
That's all nice and good but down on the client we do stuff with this fruit, like eat it and plant trees...
var fruitbowl = []
function Fruit(name, color, seeds){
this.name = name
this.color = color
this.seeds = seeds
this.eat = function(){
// munch munch
}
}
function Seed(size, density){
this.size = size
this.density = density
this.plant = function(){
// grow grow
}
}
My ajax's success routine currently is currently looping over the thing and constructing each object in turn and it doesn't handle the seeds yet, because before I go looping over seed constructors I'm thinking
Is there not a better way?
success: function(data){
fruitbowl.length = 0
$.each(data.fruitbowl, function(i, f){
fruitbowl.push(new Fruit(f.name, f.color, f.seeds))
})
I haven't explored looping over the objects as they are and attaching all the methods. Would that work?
Share Improve this question edited May 14, 2010 at 23:35 Matt 75.3k26 gold badges155 silver badges180 bronze badges asked May 14, 2010 at 23:20 John MeeJohn Mee 52.2k38 gold badges155 silver badges196 bronze badges 1- For extra credit: I might need to identify my fruit and seeds with "instanceof" at some point. – John Mee Commented May 14, 2010 at 23:24
7 Answers
Reset to default 2Yes, it would work, but it's not desirable. Apart from appearing slightly hacky IMO, you're attaching methods to each instance of your fruit and seeds, where you should instead be using the prototype chain. If you're going to be using instanceof
in the future, this method won't work anyway.
What you're currently doing is the best solution; and you'll be able to use instanceof
.
If you're feeling adventurous, you can use JSONP instead of AJAX, with the JSONP response looking something like:
buildFruitbowl([new Fruit("orange", "blue", [new Seed("small", "hard"), new Seed("big", "soft")]), new Fruit("banana", "yellow", [new Seed("small", "hard"), new Seed("big", "soft")])]);
Which will save you having to do all your object looping, and you'll get your Fruit and Seeds how you want (and instanceof
support); however I would still stick to what you're doing already.
Best of look growing your bananas.
Pass the data to the object constructor then use jquery's "extend" to combine the data and methods:
function Fruit(data){
$.extend(this, data)
this.eat = function(){
// munch munch
}
}
...
$.each(data.fruitbowl, function(i, f){
fruitbowl.push(new Fruit(f))
})
You still have loops involved; and must manually code loops for the nested objects (like seeds), but still a very simple way to get past the problem.
You could modify the JSON structure to store the type information. If you have a lot of objects to serialize and deserialize back and forth, this would save time writing custom code for each object.
Also note, this modifies the JSON structure and adds a __type__
property to each custom object. I think this is a cleaner approach than keeping separate configuration files. So without further ado, this is how it basically works:
var fruitBowl = {..};
fruitBowl[0].eat();
fruitBowl[1].seeds[0].plant();
call serialize on the object to get a JSON representation
var json = fruitBowl.serialize();
call deserialize on the JSON encoded string to reconstruct the objects
var resurrected = json.deserialize();
now you can access properties and call methods on the objects:
resurrected[0].eat();
resurrected[1].seeds[0].plant();
It works for any levels of deeply nested objects, although it might be a little buggy for now. Also it is most likely not cross-browser (only tested on Chrome). Since the deserializer is not familiar with an object's constructor function, it basically creates each custom object without passing any parameters. I've setup a working demo on jsfiddle at http://jsfiddle.net/kSATj/1/.
The constructor function had to be modified to account for the two ways it's objects could be created
- Directly in Javascript
- Reconstructed from JSON
All constructors would need to accommodate creation from both ends, so each property needs to be assigned a default fallback value incase nothing was passed.
function SomeObject(a, b) {
this.a = a || false; // defaultValue can be anything
this.b = b || null; // defaultValue can be anything
}
// one type of initialization that you can use in your code
var o = new SomeObject("hello", "world");
// another type of initialization used by the deserializer
var o = new SomeObject();;
o.a = "hello";
o.b = "world";
For reference, the modified JSON looks like:
{"fruitbowl":
[
{
"__type__": "Fruit",
"name": "apple",
"color": "red",
"seeds": []
},
{
"__type__": "Fruit",
"name": "orange",
"color": "orange",
"seeds":
[
{
"__type__": "Seed",
"size": "small",
"density": "hard"
},
{
"__type__": "Seed",
"size": "small",
"density": "soft"
}
]
}
]
}
This is just a helper function to identify simple types:
function isNative(object) {
if(object == null) {
return true;
}
var natives = [Boolean, Date, Number, String, Object, Function];
return natives.indexOf(object.constructor) !== -1;
}
Serializes an object into JSON (with type info preserved):
Object.prototype.serialize = function() {
var injectTypes = function(object) {
if(!isNative(object)) {
object.__type__ = object.constructor.name;
}
for(key in object) {
var property = object[key];
if(object.hasOwnProperty(key) && !isNative(property)) {
injectTypes(property);
}
}
};
var removeTypes = function(object) {
if(object.__type) {
delete object.__type__;
}
for(key in object) {
var property = object[key];
if(object.hasOwnProperty(key) && !isNative(property)) {
removeTypes(property);
}
}
}
injectTypes(this);
var json = JSON.stringify(this);
removeTypes(this);
return json;
};
Deserialize (with custom objects reconstructed):
String.prototype.deserialize = function() {
var rawObject = JSON.parse(this.toString());
var reconstruct = function(object) {
var reconstructed = {};
if(object.__type__) {
reconstructed = new window[object.__type__]();
delete object.__type__;
}
else if(isNative(object)) {
return object;
}
for(key in object) {
var property = object[key];
if(object.hasOwnProperty(key)) {
reconstructed[key] = reconstruct(property);
}
}
return reconstructed;
}
return reconstruct(rawObject);
};
Using ES5 Object.create
Simply define your objects statically then use Object.create
to extend them.
It's as simple as Object.create(Bowl, transform(data));
// declare 3 Objects to use as prototypes for your data
var Fruit = {
eat: function() { }
}
var Seed = {
plant: function() { }
}
var Bowl = {};
// data object
var data = { ... };
// Transform JSON to a valid defineProperties hash.
Object.create(Bowl, transform(data));
You will need to define the transform function and more importantly tell it the object type of nested arrays of data.
// hash map of property names of arrays to the Object they should prototype from.
var collectionClassHash = {
fruitbowl: Fruit,
seeds: Seed
}
var transform = function(obj) {
// return value
var ret = {};
// for each key
Object.keys(obj).forEach(function(key) {
// value of key
var temp = obj[key];
// if array
if (Array.isArray(temp) {
// override value with an array of the correct objects
temp = obj[key].map(function(val) {
// recurse for nested objects
return Object.create(collectionClassHash[key], transform(val));
});
}
// define getter/setter for value
ret[key] = {
get: function() { return temp; },
set: function(v) { temp = v; }
}
});
return ret;
}
Using D Crockford's "json2" library, you can supply a "reviver" function to the parsing process. The reviver function is passed each key and each value, and should return the actual effective value to be used in the parsed result.
There's a corresponding optional parameter in the "stringify" method.
This actually took me a while to figure out, I'm really surprised there are not more pages on this.
As @Pointy pointed out, JSON has a reviver function that can be used to replace the parse result inline allowing you to avoid walking the tree a second time. The JSON page documents reviver (in my opinion a little weakly) - http://json.org/js.html.
Reviver is part of ECMA 5 and is supported in Firefox, WebKit (Opera/Chrome), and JSON2.js.
Here is a code example based on the JSON doc. You can see we are setting a type property on Dog and then using a reviver function that recognizes that type property.
function Dog(args) {
this.name = args.name;
this.bark = function() {
return "bark, bark, my name is " + this.name;
};
this.toJSON = function() {
return {
name: this.name,
type: 'Dog' // this.constructor.name will work in certain browsers/cases
}
}
};
var d = new Dog({name:'geti'});
var dAsJson = JSON.stringify(d);
var dFromJson = JSON.parse(dAsJson, function (key, value) {
var type;
if (value && typeof value === 'object') {
type = value.type;
if (typeof type === 'string' && typeof window[type] === 'function') {
return new (window[type])(value);
}
}
return value;
}
);
I have a couple concerns about their example. The first is that it depends on the constructor being global (on window). The second is a security concern in that rogue JSON can get us to call any constructor by adding a type property to their JSON.
I've chosen to have an explicit list of types and their constructors. This ensures only constructors I know are safe will be called and also allows me to use a custom type mapping approach if I like (rather than depending on the constructor name and it being in the global space). I also verify the JSON object has a type (some may not and they will be treated normally).
var jsonReviverTypes = {
Dog: Dog
};
var dAsJsonB = JSON.stringify(d);
var dFromJsonB = JSON.parse(dAsJsonB, function (key, value) {
var type;
if (value && typeof value === 'object' && value.type) {
type = value.type;
if (typeof type === 'string' && jsonReviverTypes[type]) {
return new (jsonReviverTypes[type])(value);
}
}
return value;
});
Note, FF 3.6 has a bug in the JSON.replacer method as @Sky pointed out and has documented here - http://skysanders.net/subtext/archive/2010/02/24/confirmed-bug-in-firefox-3.6-native-json-implementation.aspx. For the above solution I work around this by using toJSON on the object rather than using replacer.
John,
Hopefully not too late to chip in here. I had a very similar problem just last week and solved it with the following piece of js (it could easily be converted to jquery as well.).
Here's the base usage:
$(document).ready(function() {
var bowl = { "fruitbowl": [{
"name": "apple",
"color": "red",
"seeds": []
},
{
"name": "orange",
"color": "orange",
"seeds": [
{ "size": "small", "density": "hard" },
{ "size": "small", "density": "soft"}]
}
]
};
var serialized = jsonToObject.serialize(bowl);
var deserialized = jsonToObject.deserialize(serialized);
// basic tests on serialize/deserializing...
alert(deserialized.fruitbowl[0].name);
alert(deserialized.fruitbowl[1].seeds[0].density);
});
and here's the jsonToObject.js file:
jsonToObject = {
deserialize: function(_obj) {
if (typeof (JSON) === 'object' && typeof (JSON.parse) === 'function') {
// native JSON parsing is available.
//return JSON.parse(_obj);
}
// otherwise, try non-native methods
var jsonValue = new Function("return " + _obj)();
if (!jsonValue instanceof Object) {
jsonValue = eval("(" + _obj + ")");
}
return jsonValue;
},
serialize: function(_obj) {
// Let Gecko browsers do this the easy way - not working
if (_obj != undefined && typeof _obj.toSource !== 'undefined'
&& typeof _obj.callee === 'undefined') {
return _obj.toSource();
}
// Other browsers must do it the hard way
switch (typeof _obj) {
// numbers, booleans, and functions are trivial:
// just return the object itself since its default .toString()
// gives us exactly what we want
case 'number':
case 'boolean':
case 'function':
return _obj;
break;
// for JSON format, strings need to be wrapped in quotes
case 'string':
return '"' + _obj.replace(/"/mg, "'") + '"';
break;
case 'object':
var str;
if (_obj.constructor === Array || typeof _obj.callee !== 'undefined') {
str = '[';
var i, len = _obj.length;
for (i = 0; i < len - 1; i++) { str += this.serialize(_obj[i]) + ','; }
str += this.serialize(_obj[i]) + ']';
}
else {
str = '{';
var key;
for (key in _obj) { str += key + ':' + this.serialize(_obj[key]) + ','; }
str = str.replace(/\,$/, '') + '}';
}
return str;
break;
default:
return '""';
break;
}
}
}
hope this helps...
jim
[edit] - you could of course also give the two functions their prototype signatures in keeping with the excellent example above, ie..
String.prototype.deserialize = function() {...} Object.prototype.serialize = function() {...}
本文标签: javascriptEasiest way to convert json data into objects with methods attachedStack Overflow
版权声明:本文标题:javascript - Easiest way to convert json data into objects with methods attached? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738460013a2087978.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论