I am in the final stages of a game development and i have a bunch of objects like this;
roomBedroom = function () {
this.title = "Bedroom";
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
}
var bedroom = new roomBedroom();
What I want to do now is place all of my game objects into an array;
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);
The plan is to then save the savedGameObjects array and then recall it when the user loads the game again.
If I replace savedGameObjects['bedroom'] = bedroom; with savedGameObjects['bed'] = 'slappy'; it works but not when I have the object.
I really need to save the objects in their current state. I'd rather not go through each object saving key pieces of information one by one.
This feels like a bit of a hack, but its the best I can come up with right now
Your serialization/deserializtion utility
This is going to attach obj.constructor.name to obj.__prototype before serialization. Upon deserializing, the prototype will be put back in place.
(function(global) {
function serialize(obj) {
obj.__prototype = obj.constructor.name;
return JSON.stringify(obj);
};
function deserialize(json) {
var obj = JSON.parse(json);
obj.__proto__ = global[obj.__prototype].prototype;
return obj;
}
global.serialize = serialize;
global.deserialize = deserialize;
})(window);
A sample "class"
(function(global) {
function Foo() {
this.a = "a";
this.b = "b";
}
Foo.prototype.hello = function() {
console.log("hello");
}
global.Foo = Foo;
})(window);
Let's try it out
var foo = new Foo();
var json = serialize(foo);
console.log(json);
var newFoo = deserialize(json);
console.log('a', newFoo.a); // a
console.log('b', newFoo.b); // b
newFoo.hello(); // hello
Watch out for some gotchas
If you use an expression to define your "class", you will have a nameless constructor
var Foo = function() {};
var foo = new Foo();
foo.constructor.name; // ""
As opposed to a named function
function Foo() {}
var foo = new Foo();
foo.constructor.name; // Foo
In order for serialize and deserialize to work, you will need to use named functions
Another gotcha
The deserialize method expects your "classes" to exist on the in the same namespace (window in this case). You could encapsulate your game object classes in another way, just make sure that you reconfigure the deserialize method so that it can find the prototypes as needed.
Making this better
Instead of attaching serialize to the global window, you could have serialize live on (e.g.) the GameObject.prototype then your individual classes could inherit from GameObject. Serializing an object would then be as simple as
var json = foo.serialize();
// {"a":"a","b":"b","__prototype":"Foo"}
You could then define deserialize as GameObject.deserialize and restoring foo would be
var foo = GameObject.deserialize(json);
An alternative solution
Instead of implementing a custom serializer and deserializer, you could make very clever use of the Factory Method Pattern.
This might be a little verbose, but it does give you individual control over how a game object should be deserialized/restored.
var savedData = // your normal JSON here
var player = Player.create(savedData.player);
var items = [];
for (var i=0, i<savedData.items.length; i++) {
items.push(Item.create(savedData.items[i]));
}
var map = Map.create(savedData.map);
This was a pretty interesting problem and I'm sure you're not the first to encounter it. I'm really curious to see what other people come up with.
If I run the following code in a browser there is no problem getting the JSON string of the bedroom object, not sure what the problem is.
Note that JSON is data and bedroom is an object, bedroom may have behaviour like turnOffLight() that JSON doesn't have.
roomBedroom = function () {
this.title = "Bedroom";
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
}
var bedroom = new roomBedroom();
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
//logs {"bedroom":{"title":"Bedroom","description":
// "I'm in abedroom","noun":"bed","entities":[]}}
console.log(JSON.stringify(savedGameObjects));
So if you want to re create object instances from JSON data then you can change your constructor:
roomBedroom = function (args) {
//following fails fast and loud, you could silently
//fail by setting args to {}
if(typeof args!=="object")
throw new Error("Have to create roomBedroom by passing an object");
//or do args={} to silently fail
this.title = args.title||"Bedroom";
this.description = args.description||"I'm in a bedroom";
this.noun = args.noun||"bed";
//if entities are objects with behavior
// you have to re create them here passing the JSON data
// as I've done with roomBedroom
this.entities = args.entities||new Array();
}
var jsonString='{"bedroom":{"title":"Bedroom",'+
'"description":"I\'m in a bedroom",'+
'"noun":"bed","entities":[]}}';
var bedroom = new roomBedroom({});
bedroom.entities.push({hi:"there"});
bedroom.title="Master Bedroom";
//serialize bedroom to a json string
var jsonString = JSON.stringify(bedroom);
//create a roomBedroom instance named br2 using
// the serialized string
var br2=new roomBedroom(JSON.parse(jsonString));
//compare if they are the same
console.log(JSON.stringify(bedroom)===JSON.stringify(br2));//true
I have an approach that might work for you. You can see it in action on JSFiddle.
The main point is to use the reviver parameter to JSON.parse to reconstruct your object when it's parsed.
I do this with a general-purpose reviver that can be configured for multiple different types, although here the only one used is the RoomBedroom constructor. This implementation assumes that you have simple copy constructors that create new objects using a reference to an existing one. (For other, more sophisticated possibilities, see an answer to another question I gave in February.) To make it easy to have a copy constructor, I have one more function that accepts a very simple constructor function and a set of default values and builds a copy constructor function for you.
var MultiReviver = function(types) {
return function(key, value) {
var type;
for (var i = 0; i < types.length; i++) {
type = types[i];
if (type.test(value)) {
return new type.constructor(value);
}
}
return value;
};
};
var makeCloningConstructor = (function() {
var clone = function(obj) {return JSON.parse(JSON.stringify(obj));};
var F = function() {};
return function(Constructor, defaults) {
var fn = function(obj) {
Constructor.call(this);
var self = this;
var config = obj || {};
Object.keys(defaults).forEach(function(key) {
self[key] = clone(defaults[key]);
});
Object.keys(config).forEach(function(key) {
self[key] = clone(config[key]);
});
};
F.prototype = Constructor.prototype;
fn.prototype = new F();
fn.constructor = Constructor;
return fn;
};
})();
// Note: capitalize constructor functions
var RoomBedroom = makeCloningConstructor(function RoomBedroom() {}, {
title: "Bedroom",
description: "I'm in a bedroom",
noun: "bed",
entities: [] // Note: use `[]` instead of `new Array()`.
});
RoomBedroom.prototype.toggleLight = function() {
this.lightOn = !this.lightOn;
};
RoomBedroom.prototype.checkLights = function() {
return "light is " + (this.lightOn ? "on" : "off");
};
var bedroom = new RoomBedroom();
bedroom.windowCount = 3; // add new property
bedroom.noun = "king-sized bed"; // adjust property
bedroom.toggleLight(); // create new propery, use prototype function
console.log(bedroom.checkLights());
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);
var reviver = new MultiReviver([{
constructor: RoomBedroom,
test: function(obj) {
var toString = Object.prototype.toString, str = "[object String]",
arr = "[object Array]";
return toString.call(obj.title) == str &&
toString.call(obj.description) == str &&
toString.call(obj.noun) == str &&
toString.call(obj.entities) == arr;
}
}]);
var retrievedGameObjects = JSON.parse(jsonGame, reviver);
// data comes back intact
console.log(JSON.stringify(retrievedGameObjects, null, 4));
// constructor is as expected
console.log("Constructor: " + retrievedGameObjects.bedroom.constructor.name);
// prototype functions work
console.log(retrievedGameObjects.bedroom.checkLights());
I don't know if it's precisely what you were looking for, but I think it's at least an interesting approach.
the faster route
It is better — from an optimisation point of view — to do as Adeneo states, which is power each of your Game Objects by an exportable simple object i.e:
roomBedroom = function(){
this.data = {};
this.data.title = 'Bedroom'
/// and so on...
}
These can then be easily stored and re-imported just by JSON.Stringifying and overwriting the data property. For example, you could set-up the system that Maček mentions (+1) which is to give each of your game objects serialize and deserialize functions:
roomBedroom.prototype.serialize = function(){
return JSON.stringify( this.data );
};
roomBedroom.prototype.deserialize = function( jstr ){
this.data = JSON.parse(jstr);
};
the quicker way
However, you can make a simple addition to what you already have using the following:
First enhance your Game Objects with an objectName property. This is because constructor.name and function.name are unreliable and do strange things the further back in time you go, far better to use a string you have set in stone.
var roomBedroom = function ( title ) {
this.objectName = "roomBedroom";
this.title = title;
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
};
Then the additional code to help with storage:
var storage = {};
/// add your supported constructors to this list, there are more programmatic
/// ways to get at the constructor but it's better to be explicit.
storage.constructors = {
'roomBedroom' : roomBedroom
};
/// take an instance and convert to simple object
storage.to = function( obj ){
if ( obj.toStorage ) {
return obj.toStorage();
}
else {
var keep = {};
for ( var i in obj ) {
if ( obj.hasOwnProperty(i) && !obj[i].call ) {
keep[i] = obj[i];
}
}
return keep;
}
}
/// take simple object and convert to an instance of constructor
storage.from = function( obj ){
var n = obj && obj.objectName, c = storage.constructors[n];
if ( n && c ) {
if ( c.fromStorage ) {
return c.fromStorage( obj );
}
else {
var inst = new c();
for ( var i in obj ) {
if ( obj.hasOwnProperty(i) ) {
inst[i] = obj[i];
}
}
return inst;
}
}
else {
throw new Error('`' + n + '` undefined as storage constructor');
}
}
Once you have that you can use it like so:
var savedGameObjects = {};
savedGameObjects['bedroom'] = storage.to(new roomBedroom("bedroom"));
savedGameObjects['bedroom2'] = storage.to(new roomBedroom("bedroom2"));
var jsonGame = JSON.stringify(savedGameObjects);
console.log(jsonGame);
savedGameObjects = JSON.parse(jsonGame);
for( var i in savedGameObjects ) {
savedGameObjects[i] = storage.from(savedGameObjects[i]);
console.log(savedGameObjects[i]);
}
extras
You can also be specific about the way objects get stored/unstored by supplying toStorage and fromStorage methods on your constructed instances and constructors respectively. For example, you could use the following if you only wanted to store titles of roomBedrooms. Obviously this is an unrealistic use-case, you'd more often use this to avoid storing cached or computed sub-objects and properties.
roomBedroom.prototype.toStorage = function( obj ){
var ret = {};
ret.title = obj.title;
return ret;
};
roomBedroom.fromStorage = function( obj ){
var inst = new roomBedroom();
inst.title = obj.title;
return inst;
};
The above also means you can take advantage of improving your Game Object construction by providing parameters, rather than iterating over properties which can be slow and error-prone.
roomBedroom.fromStorage = function( obj ){
return new roomBedroom( obj.title );
};
Or even:
roomBedroom.fromStorage = function( obj ){
return new roomBedroom( obj ); // <-- the constructor processes the import.
};
fiddle
http://jsfiddle.net/XTUdp/
disclaimer
The above code relies on the existence of hasOwnProperty which is not present cross-browser yet, a polyfill should be used until it is... or, if you aren't doing anything complicated with prototype inheritance you don't need to worry and can remove it from the code.
you can declare a big variable like
var world = {};
and each small variable declare as
var bedroom = world.bed = (world.bed || new roomBedroom());
remember never change bedroom to another object, i think this will work fine, but looks too long winded
Related
This object constructor is the definition of a person:
var Person = function( age, name ){
this.age = age;
this.namge = name;
};
this is a line of code that will give the prototype of Person an array called "active"
Person.prototype.active = [];
The reason I am adding this to the prototype, is so that there is only one active array that every person, meaning Jim in this case: var Jim = new Person() ), SHARES the exact same active array.
From that point I want to add in every newly created person into the active array.
This is how I would do it:
var Jim = new Person(age, name);
Jim.active.push( Jim );
var Tim = new Person(age, name);
Tim.active.push( Tim );
What I expected from this, is for Tim.active[0] to be Jim and for
Jim.active[1] to be Tim.
The problem is, is that I want the active.push[ self id ] to be called
when a new Person is created, without the second line doing it. My solution for this would be modifying the Person constructor too look like this:
var Person = function( age, name ){
this.age = age;
this.namge = name;
this.active.push( this );// The constructor now adds itself to the array during initiation
};
As you can see, it puhes itself into the active array. The problem is that I want my object to do exactly this, but I want the constructor to begin like the first one I provided with this.active.push appended later in the code.
How do I expect this to be solved? My thoughts were that since the active array could be initiated anytime inside the program, that the functions constructor could somehow append this.active.push() to the end of it at right after Person.prototype.active = [] is executed.
Something that may look like:
Person.prototype.active = [];
Person.prototype.append( function(){this.active.push(this)} );
The second line would morph the first object constructor to look like the second one.
Not sure why you want this approach, perhaps it can be refactored? Probably better to have some sort of PersonMediator "class" in my opinion. But, if you want to have the prototype include an active array that has conditions, then attach some way to manage those conditions to the Person object.
var Person = function( age, name ){
this.age = age;
this.namge = name;
//checks for activation flag
this.ready();
};
Person.prototype.active = [];
Person.activate = function(){ Person.prototype.activate = true; };
Person.deactivate = function(){ Person.prototype.activate = false; };
Person.prototype.activate = false;
Person.prototype.ready = function(){
//depending on activate flag, appends to active array
if( this.activate ) this.append();
};
Person.prototype.append = function(){
//array append
this.active.push(this);
};
var Jim = new Person(10,'Jim');//not added, default flag false
//activate flag for appending
Person.activate();
var Tim = new Person(20,'Tim');//now added to the active array
console.log(Jim.active);//shows only Tim
console.log(Tim.active);//shows only Tim
That said, this is how I would manage this.
var Person = function( age, name ){
this.age = age;
this.namge = name;
};
var PersonMediator = function(){
this.alive = [];
this.removed = [];
};
PersonMediator.prototype.create = function(age, name){
var person = new Person(age,name);
this.alive.push(person);
return person;
};
PersonMediator.prototype.remove = function(person){
for(var i = 0; i < this.alive.length; i++){
if( this.alive[i] === person ){
this.alive.splice(i,1);
}
}
this.removed.push(person);
};
var pm = new PersonMediator();
var Jim = pm.create(10,'Jim');
var Tim = pm.create(20,'Tim');
console.log(pm.alive);
If you simply want to store the instantiated objects to an array, you can use an inheritance pattern like so:
var myArray = [];
function SuperClass() {
myArray.push(this);
};
function Block(name) {
this.name = name;
SuperClass.apply(this, arguments);
};
function Player(name) {
this.name = name;
SuperClass.apply(this, arguments);
};
var baller = new Player('Jim');
var blocker = new Block('Joe');
console.log(myArray); // [Player, Block]
Every time a new instance of Player or Block is instantiated, it will add it to the array.
Another pattern you may consider is having a set prototype object that you can add your prototypical methods and shared data to, and then assigning it as the prototype in your constructor function
var personPrototype = {
alive: []
}
var blockPrototype = {
alive: []
}
var Person = function(x, y){
//this creates a new object with the prototype of our personPrototype object
var o = Object.create(personPrototype);
//then we can configure / add instance-specific attributes and return the object
o.x = x;
o.y = y;
return o;
}
var Block = function(x, y){
var o = Object.create(blockPrototype);
o.x = x;
o.y = y;
return o;
}
var me = new Person(1, 2);
var square = new Block(10, 20);
// > []
me.alive
By using Object.create to create a dummy object we can effectively assign it a proper prototype. Then we can add whatever instance attributes we'd like, and return that object. This means that the personPrototype will be a proper prototype. Each instance can use instance.alive or instance.alive.push directly, since it automatically walks up the prototype chains
It also allows you to easily add any new data to the prototype of all your instances. You don't need to iterate through each instance and add prototypical methods. You can just add the method to the prototype to start and it will be accessible on all instances by default.
I find the whole approach - trying to track/store [Person] instances - questionable.
But if I was in the position of being told to do so, I'd go for an approach that is build upon a
factory module. Thus being at least able of assuring "read only access" to the list of all instances
that ever got created by the factory.
A possible implementation than might look similar to the following example:
var Person = (function (global, Object, Array, Math) {
var
array_from = ((typeof Array.from == "function") && Array.from) || (function (array_prototype_slice) {
return function (listType) {
return array_prototype_slice.call(listType);
};
}(Array.prototype.slice)),
personModule = {},
personList = [],
Person = function (config) { // constructor.
var person = this;
person.name = config.name;
person.age = config.age;
person.constructor = Object;
return person;
},
isPerson = function (type) {
return (type instanceof Person);
},
createPerson = function (age, name) { // factory.
/*
- sanitizing/validation of arguments etc. should be done right here.
- create instances only if all the conditions are fulfilled.
*/
var person = new Person({
name: name,
age : age
});
personList.push(person);
return person;
}/*,
removePerson = function (type) {
if (isPerson(type)) {
// remove person from the internal list.
}
}*/
;
//personModule.remove = removePerson;
personModule.create = createPerson;
personModule.isPerson = isPerson;
personModule.all = function () { // expose internal instance list.
return array_from(personList);
};
personModule.all.size = function () {
return personList.length;
};
(function () { // make [personModule.all] enumerable.
var
parse_float = global.parseFloat,
math_floor = Math.floor
;
this.first = function () {
return (this()[0]);
};
this.last = function () {
var list;
return ((list = this())[list.length - 1]);
};
this.item = function (idx) {
return (this()[math_floor(parse_float(idx, 10))]);
};
}).call(personModule.all);
return personModule;
}((window || this), Object, Array, Math));
var Jim = Person.create("Jim", 21);
var Tim = Person.create("Tim", 19);
console.log("Jim", Jim);
console.log("Tim", Tim);
console.log("Person.isPerson(Jim) ? ", Person.isPerson(Jim));
console.log("Person.isPerson(Tim) ? ", Person.isPerson(Tim));
console.log("Person.isPerson({name: 'Tim', age: 21}) ? ", Person.isPerson({name: 'Tim', age: 21}));
console.log("Person.all.size() : ", Person.all.size());
console.log("Person.all() : ", Person.all());
console.log("Person.all.first() : ", Person.all.first());
console.log("Person.all.last() : ", Person.all.last());
console.log("(Person.all()[1] === Person.all.item(1)) ? ", (Person.all()[1] === Person.all.item(1)));
console.log("(Person.all.first() === Person.all.item(0)) ? ", (Person.all.first() === Person.all.item(0)));
Suppose I create a custom object/javascript "class" (airquotes) as follows:
// Constructor
function CustomObject(stringParam) {
var privateProperty = stringParam;
// Accessor
this.privilegedGetMethod = function() {
return privateProperty;
}
// Mutator
this.privilegedSetMethod = function(newStringParam) {
privateProperty = newStringParam;
}
}
Then I want to make a list of those custom objects where I can easily add or remove things from that list. I decide to use objects as a way to store the list of custom objects, so I can add custom objects to the list with
var customObjectInstance1 = new CustomObject('someString');
var customObjectInstance2 = new CustomObject('someOtherString');
var customObjectInstance3 = new CustomObject('yetAnotherString');
myListOfCustomObjects[customObjectInstance1] = true;
myListOfCustomObjects[customObjectInstance2] = true;
myListOfCustomObjects[customObjectInstance3] = true;
and remove custom objects from the list with
delete myListOfCustomObjects[customObjectInstance1];
but if i try to iterate through the list with
for (i in myListOfCustomObjects) {
alert(i.privilegedGetMethod());
}
I would get an error in the FireBug console that says "i.privilegedGetMethod() is not a function". Is there a way to fix this problem or an idiom in javascript to do what I want? Sorry if this is a dumb question, but I'm new to javascript and have scoured the internet for solutions to my problem with no avail. Any help would be appreciated!
P.S. I realize that my example is super simplified, and I can just make the privateProperty public using this.property or something, but then i would still get undefined in the alert, and I would like to keep it encapsulated.
i won't be the original object as you were expecting:
for (i in myListOfCustomObjects) {
alert(typeof i); // "string"
}
This is because all keys in JavaScript are Strings. Any attempt to use another type as a key will first be serialized by toString().
If the result of toString() isn't somehow unique for each instance, they will all be the same key:
function MyClass() { }
var obj = {};
var k1 = new MyClass();
var k2 = new MyClass();
obj[k1] = {};
obj[k2] = {};
// only 1 "[object Object]" key was created, not 2 object keys
for (var key in obj) {
alert(key);
}
To make them unique, define a custom toString:
function CustomObject(stringParam) {
/* snip */
this.toString = function () {
return 'CustomObject ' + stringParam;
};
}
var obj = {};
var k1 = new CustomObject('key1');
var k2 = new CustomObject('key2');
obj[k1] = {};
obj[k2] = {};
// "CustomObject key1" then "CustomObject key2"
for (var key in obj) {
alert(key);
}
[Edit]
With a custom toString, you can set the object as the serialized key and the value to keep them organized and still continue to access them:
var customObjectInstance1 = new CustomObject('someString');
var customObjectInstance2 = new CustomObject('someOtherString');
var customObjectInstance3 = new CustomObject('yetAnotherString');
myListOfCustomObjects[customObjectInstance1] = customObjectInstance1;
myListOfCustomObjects[customObjectInstance2] = customObjectInstance2;
myListOfCustomObjects[customObjectInstance3] = customObjectInstance3;
for (i in myListOfCustomObjects) {
alert(myListOfCustomObjects[i].privilegedGetMethod());
}
The for iteration variable is just the index, not the object itself. So use:
for (i in myListOfCustomObjects) {
alert(myListOfCustomObjects[i].privilegedGetMethod());
}
and, in my opinion, if you use an Object as an array index / hash, it just would be converted to the string "Object", which ends up in a list with a single entry, because all the keys are the same ("Object").
myListOfCustomObjects =[
new CustomObject('someString'),
new CustomObject('someOtherString'),
new CustomObject('yetAnotherString')
]
you will get access to any element by index of array.
I know how to parse a JSON String and turn it into a JavaScript Object.
You can use JSON.parse() in modern browsers (and IE9+).
That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)?
For example, suppose you have:
function Foo()
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse({"a":4, "b": 3});
//Something to convert fooJSON into a Foo Object
//....... (this is what I am missing)
alert(fooJSON.test() ); //Prints 12
Again, I am not wondering how to convert a JSON string into a generic JavaScript Object. I want to know how to convert a JSON string into a "Foo" Object. That is, my Object should now have a function 'test' and properties 'a' and 'b'.
UPDATE
After doing some research, I thought of this...
Object.cast = function cast(rawObj, constructor)
{
var obj = new constructor();
for(var i in rawObj)
obj[i] = rawObj[i];
return obj;
}
var fooJSON = Object.cast({"a":4, "b": 3}, Foo);
Will that work?
UPDATE May, 2017: The "modern" way of doing this, is via Object.assign, but this function is not available in IE 11 or older Android browsers.
The current answers contain a lot of hand-rolled or library code. This is not necessary.
Use JSON.parse('{"a":1}') to create a plain object.
Use one of the standardized functions to set the prototype:
Object.assign(new Foo, { a: 1 })
Object.setPrototypeOf({ a: 1 }, Foo.prototype)
See an example below (this example uses the native JSON object). My changes are commented in CAPITALS:
function Foo(obj) // CONSTRUCTOR CAN BE OVERLOADED WITH AN OBJECT
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
// IF AN OBJECT WAS PASSED THEN INITIALISE PROPERTIES FROM THAT OBJECT
for (var prop in obj) this[prop] = obj[prop];
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
// INITIALISE A NEW FOO AND PASS THE PARSED JSON OBJECT TO IT
var fooJSON = new Foo(JSON.parse('{"a":4,"b":3}'));
alert(fooJSON.test() ); //Prints 12
Do you want to add JSON serialization/deserialization functionality, right? Then look at this:
You want to achieve this:
toJson() is a normal method.
fromJson() is a static method.
Implementation:
var Book = function (title, author, isbn, price, stock){
this.title = title;
this.author = author;
this.isbn = isbn;
this.price = price;
this.stock = stock;
this.toJson = function (){
return ("{" +
"\"title\":\"" + this.title + "\"," +
"\"author\":\"" + this.author + "\"," +
"\"isbn\":\"" + this.isbn + "\"," +
"\"price\":" + this.price + "," +
"\"stock\":" + this.stock +
"}");
};
};
Book.fromJson = function (json){
var obj = JSON.parse (json);
return new Book (obj.title, obj.author, obj.isbn, obj.price, obj.stock);
};
Usage:
var book = new Book ("t", "a", "i", 10, 10);
var json = book.toJson ();
alert (json); //prints: {"title":"t","author":"a","isbn":"i","price":10,"stock":10}
var book = Book.fromJson (json);
alert (book.title); //prints: t
Note: If you want you can change all property definitions like this.title, this.author, etc by var title, var author, etc. and add getters to them to accomplish the UML definition.
A blog post that I found useful:
Understanding JavaScript Prototypes
You can mess with the __proto__ property of the Object.
var fooJSON = jQuery.parseJSON({"a":4, "b": 3});
fooJSON.__proto__ = Foo.prototype;
This allows fooJSON to inherit the Foo prototype.
I don't think this works in IE, though... at least from what I've read.
Am I missing something in the question or why else nobody mentioned reviver parameter of JSON.parse since 2011?
Here is simplistic code for solution that works:
https://jsfiddle.net/Ldr2utrr/
function Foo()
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse(`{"a":4, "b": 3}`, function(key,value){
if(key!=="") return value; //logic of course should be more complex for handling nested objects etc.
let res = new Foo();
res.a = value.a;
res.b = value.b;
return res;
});
// Here you already get Foo object back
alert(fooJSON.test() ); //Prints 12
PS: Your question is confusing: >>That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)?
contradicts to the title, where you ask about JSON parsing, but the quoted paragraph asks about JS runtime object prototype replacement.
The currently accepted answer wasn't working for me. You need to use Object.assign() properly:
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
greet(){
return `hello my name is ${ this.name } and i am ${ this.age } years old`;
}
}
You create objects of this class normally:
let matt = new Person('matt', 12);
console.log(matt.greet()); // prints "hello my name is matt and i am 12 years old"
If you have a json string you need to parse into the Person class, do it like so:
let str = '{"name": "john", "age": 15}';
let john = JSON.parse(str); // parses string into normal Object type
console.log(john.greet()); // error!!
john = Object.assign(Person.prototype, john); // now john is a Person type
console.log(john.greet()); // now this works
An alternate approach could be using Object.create. As first argument, you pass the prototype, and for the second one you pass a map of property names to descriptors:
function SomeConstructor() {
};
SomeConstructor.prototype = {
doStuff: function() {
console.log("Some stuff");
}
};
var jsonText = '{ "text": "hello wrold" }';
var deserialized = JSON.parse(jsonText);
// This will build a property to descriptor map
// required for #2 argument of Object.create
var descriptors = Object.keys(deserialized)
.reduce(function(result, property) {
result[property] = Object.getOwnPropertyDescriptor(deserialized, property);
}, {});
var obj = Object.create(SomeConstructor.prototype, descriptors);
I like adding an optional argument to the constructor and calling Object.assign(this, obj), then handling any properties that are objects or arrays of objects themselves:
constructor(obj) {
if (obj != null) {
Object.assign(this, obj);
if (this.ingredients != null) {
this.ingredients = this.ingredients.map(x => new Ingredient(x));
}
}
}
For the sake of completeness, here's a simple one-liner I ended up with (I had no need checking for non-Foo-properties):
var Foo = function(){ this.bar = 1; };
// angular version
var foo = angular.extend(new Foo(), angular.fromJson('{ "bar" : 2 }'));
// jquery version
var foo = jQuery.extend(new Foo(), jQuery.parseJSON('{ "bar" : 3 }'));
I created a package called json-dry. It supports (circular) references and also class instances.
You have to define 2 new methods in your class (toDry on the prototype and unDry as a static method), register the class (Dry.registerClass), and off you go.
While, this is not technically what you want, if you know before hand the type of object you want to handle you can use the call/apply methods of the prototype of your known object.
you can change this
alert(fooJSON.test() ); //Prints 12
to this
alert(Foo.prototype.test.call(fooJSON); //Prints 12
I've combined the solutions that I was able to find and compiled it into a generic one that can automatically parse a custom object and all it's fields recursively so you can use prototype methods after deserialization.
One assumption is that you defined a special filed that indicates it's type in every object you want to apply it's type automatically (this.__type in the example).
function Msg(data) {
//... your init code
this.data = data //can be another object or an array of objects of custom types.
//If those objects defines `this.__type', their types will be assigned automatically as well
this.__type = "Msg"; // <- store the object's type to assign it automatically
}
Msg.prototype = {
createErrorMsg: function(errorMsg){
return new Msg(0, null, errorMsg)
},
isSuccess: function(){
return this.errorMsg == null;
}
}
usage:
var responseMsg = //json string of Msg object received;
responseMsg = assignType(responseMsg);
if(responseMsg.isSuccess()){ // isSuccess() is now available
//furhter logic
//...
}
Type assignment function (it work recursively to assign types to any nested objects; it also iterates through arrays to find any suitable objects):
function assignType(object){
if(object && typeof(object) === 'object' && window[object.__type]) {
object = assignTypeRecursion(object.__type, object);
}
return object;
}
function assignTypeRecursion(type, object){
for (var key in object) {
if (object.hasOwnProperty(key)) {
var obj = object[key];
if(Array.isArray(obj)){
for(var i = 0; i < obj.length; ++i){
var arrItem = obj[i];
if(arrItem && typeof(arrItem) === 'object' && window[arrItem.__type]) {
obj[i] = assignTypeRecursion(arrItem.__type, arrItem);
}
}
} else if(obj && typeof(obj) === 'object' && window[obj.__type]) {
object[key] = assignTypeRecursion(obj.__type, obj);
}
}
}
return Object.assign(new window[type](), object);
}
A very simple way to get the desired effect is to add an type attribute while generating the json string, and use this string while parsing the string to generate the object:
serialize = function(pObject) {
return JSON.stringify(pObject, (key, value) => {
if (typeof(value) == "object") {
value._type = value.constructor.name;
}
return value;
});
}
deSerialize = function(pJsonString) {
return JSON.parse(pJsonString, (key, value) => {
if (typeof(value) == "object" && value._type) {
value = Object.assign(eval('new ' + value._type + '()'), value);
delete value._type;
}
return value;
});
}
Here a little example of use:
class TextBuffer {
constructor() {
this.text = "";
}
getText = function() {
return this.text;
}
setText = function(pText) {
this.text = pText;
}
}
let textBuffer = new TextBuffer();
textBuffer.setText("Hallo");
console.log(textBuffer.getText()); // "Hallo"
let newTextBuffer = deSerialize(serialize(textBuffer));
console.log(newTextBuffer.getText()); // "Hallo"
Here is a solution using typescript and decorators.
Objects keep their methods after deserialization
Empty objects and their children are default-initialized
How to use it:
#SerializableClass
class SomeClass {
serializedPrimitive: string;
#SerializableProp(OtherSerializedClass)
complexSerialized = new OtherSerializedClass();
}
#SerializableClass
class OtherSerializedClass {
anotherPrimitive: number;
someFunction(): void {
}
}
const obj = new SomeClass();
const json = Serializable.serializeObject(obj);
let deserialized = new SomeClass();
Serializable.deserializeObject(deserialized, JSON.parse(json));
deserialized.complexSerialized.someFunction(); // this works!
How it works
Serialization:
Store the type name in the prototype (__typeName)
Use JSON.stringify with a replacer method that adds __typeName to the JSON.
Deserialization:
Store all serializable types in Serializable.__serializableObjects
Store a list of complex typed properties in every object (__serializedProps)
Initialize an object theObject via the type name and __serializableObjects.
Go through theObject.__serializedProps and traverse over it recursively (start at last step with every serialized property). Assign the results to the according property.
Use Object.assign to assign all remaining primitive properties.
The code:
// #Class decorator for serializable objects
export function SerializableClass(targetClass): void {
targetClass.prototype.__typeName = targetClass.name;
Serializable.__serializableObjects[targetClass.name] = targetClass;
}
// #Property decorator for serializable properties
export function SerializableProp(objectType: any) {
return (target: {} | any, name?: PropertyKey): any => {
if (!target.constructor.prototype?.__serializedProps)
target.constructor.prototype.__serializedProps = {};
target.constructor.prototype.__serializedProps[name] = objectType.name;
};
}
export default class Serializable {
public static __serializableObjects: any = {};
private constructor() {
// don't inherit from me!
}
static serializeObject(typedObject: object) {
return JSON.stringify(typedObject, (key, value) => {
if (value) {
const proto = Object.getPrototypeOf(value);
if (proto?.__typeName)
value.__typeName = proto.__typeName;
}
return value;
}
);
}
static deserializeObject(typedObject: object, jsonObject: object): object {
const typeName = typedObject.__typeName;
return Object.assign(typedObject, this.assignTypeRecursion(typeName, jsonObject));
}
private static assignTypeRecursion(typeName, object): object {
const theObject = new Serializable.__serializableObjects[typeName]();
Object.assign(theObject, object);
const props = Object.getPrototypeOf(theObject).__serializedProps;
for (const property in props) {
const type = props[property];
try {
if (type == Array.name) {
const obj = object[property];
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; ++i) {
const arrItem = obj[i];
obj[i] = Serializable.assignTypeRecursion(arrItem.__typeName, arrItem);
}
} else
object[property] = [];
} else
object[property] = Serializable.assignTypeRecursion(type, object[property]);
} catch (e) {
console.error(`${e.message}: ${type}`);
}
}
return theObject;
}
}
Comments
Since I am a total js/ts newby (< 10 days), I am more than happy to receive any input/comments/suggestions. Here are some of my thoughts so far:
It could be cleaner: Unfortunately I did not find a way to get rid of the redundant parameter of #SerializableProp.
It could be more memory friendly: After you call serializeObject() every object stores __typeName which could massively blow up memory footprint. Fortunately __serializedProps is only stored once per class.
It could be more CPU friendly: It's the most inefficient code I've ever written. But well, it's just for web apps, so who cares ;-) Maybe one should at least get rid of the recursion.
Almost no error handling: well that's a task for another day
class A {
constructor (a) {
this.a = a
}
method1 () {
console.log('hi')
}
}
var b = new A(1)
b.method1() // hi
var c = JSON.stringify(b)
var d = JSON.parse(c)
console.log(d.a) // 1
try {
d.method1() // not a function
} catch {
console.log('not a function')
}
var e = Object.setPrototypeOf(d, A.prototype)
e.method1() // hi
Olivers answers is very clear, but if you are looking for a solution in angular js, I have written a nice module called Angular-jsClass which does this ease, having objects defined in litaral notation is always bad when you are aiming to a big project but saying that developers face problem which exactly BMiner said, how to serialize a json to prototype or constructor notation objects
var jone = new Student();
jone.populate(jsonString); // populate Student class with Json string
console.log(jone.getName()); // Student Object is ready to use
https://github.com/imalhasaranga/Angular-JSClass
if (typeof Object.create4 !== 'function') {
Object.create4 = function (t) {
var F, f, i, ins = {}, sta = {};
for(i in t){
// method: static, means will only exists 1, so is less memory intensive
if(typeof t[i] === 'function'){
sta[i] = t[i];
}
// vars: instance, means 1 for each object, so is more memory intensive
else{
ins[i] = t[i];
}
}
// make a copy of the instances
ins = jQuery.extend(true, {}, ins);
F = function() {}
F.prototype = sta;
f = new F();
// assign instances to the instance
for(i in ins){
f[i] = ins[i];
}
return f;
};
}
var Vehicle4 = (function(){
var that = {}
that.instanceVar = {hey: 1}
that.staticMethod = function(){
console.log(this.instanceVar);
}
return that;
}())
var v31 = Object.create4(Vehicle4);
var v32 = Object.create4(Vehicle4);
v31.instanceVar.hey = 2;
v31.staticMethod();
v32.staticMethod();
is this ok in terms of memory?
I mean:
in 1000 objects instanced there will be:
1*staticMethod
1000*instanceVar
is this efficient? I want to note that the instanceVar will be modified in each object so a signle object is not enought.
and might have any memory leaks?
var inherit = function(P, C) {
return jQuery.extend(true, {}, P, C);
}
var Vehicle = function() {}
Vehicle.prototype = {
init: function(){
this.instanceVar = {hey: 1}
},
staticMethod: function() {
console.log(this.instanceMember);
},
staticMethod3: function() {
console.log(this.instanceMember);
}
}
var SuperVehicle = function() {}
SuperVehicle.prototype = inherit(Vehicle.prototype, {
init: function(){
this.super.init.call(this);
this.instanceVar2 = {hey: 1}
},
staticMethod: function() {
console.log(this.instanceVar.hey);
console.log(this.instanceVar2.hey);
},
staticMethod2: function() {
console.log(this.instanceVar.hey);
console.log(this.instanceVar2.hey);
}
});
SuperVehicle.prototype.super = Vehicle.prototype;
var s = new SuperVehicle();
s.init();
s.staticMethod();
s.staticMethod2();
s.staticMethod3();
I can tell you for sure that it is correct and you won't have memory leaks, but concerning the efficiency, i have some doubts.
Firstly, your static members aren't quite static... they are just added to the prototypic chain of the object. The whole prototype inheritance system relies on the fact that every object inherits it's fathers prototype recursively.
So, if you add a property to the primitive Object , such as:
Object.prototype.toString = function(){console.log("I am a primitive object");}
all of the object in your window would inherit this function and they would be "primitive" :)).
You could call this a "static" method only if you consider the fact that it is loaded in the memory only once and not for every instance, but you cannot consider it static, because it interacts with the current instance of the object (in other object oriented languages, if you put the "this" keyword inside a static method, it throws an exception)
But I don't see the point of all that in your example.
In your example, you pin the "static" methods into the prototype of the object you want to create, but you recreate the prototype for each object instance. If you create 2 or more instances of the same "class", they won't share the prototype but they will each have identical ones.
It's right there:
F = function() {};
F.prototype = sta;
f = new F();
Every time you create vehicles with this method:
var myVehicle = Object.create4(Vehicle4);
var anotherVehicle = Object.create4(Vehicle4);
You create prototypes for each instance, witch kind of defeats the purpose of the prototypic inheritance.
I would definitely go for the classic method of creating objects (with the "new" operator) :
var Vehicle = function(val){this.instanceMember = val;}
Vehicle.prototype = {
"staticMethod": function(){console.log(this.instanceMember);}
}
var v1 = new Vehicle("foo");
var v2 = new Vehicle("bar");
This way you can easily change the staticMethod and affect all Vehicle instances:
Vehicle.prototype.staticMethod = function(){
console.log(arguments[0] || this.instanceMember);
};
while in your example if you change the staticMethod, the changes will be applied only to the instances constructed after the change occurred.
This being said, in this case, the old classic way of creating objects with "static" members is by far more efficient.
P.S. : Sorry if i got carried away :)
I've been learning more about javascript's prototypal inheritance. I know there is a somewhat fierce debate on whether to extend native objects and I'd like to side step that whole debate entirely in this question.
Is it possible to extend only descendent object in javascript?
To extend all objects I can do this:
Object.prototype.size = function(){
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
But the problem is that It extends all objects. What I'd like to do is have this:
var MyNameSpace = function(){
};
MyNameSpace.Object.prototype.size = function(){
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
That way I would only be extending the native objects in the scope of my global object.
any suggestions would be great thanks
Update:
In response to a few comments I'm adding more code to clarify what I'm trying to do.
I think i may have not phrased my question correctly, or maybe my thinking is incorrect, but what i'd like to be able to do is this:
var my = new MyNameSpace();
var my.name = {firstName : 'Hello', lastName : 'World'};
var nameCount = my.name.size(); // 2
the code you provided will allow me to get the size of each MyNameSpace object I create, but not the object literals that are properties of the MyNameSpace object
You could use "pseudo-classical" inheritance style to achieve it:
var MyNameSpace = function() {
this.v1 = null;
this.v2 = null;
}
MyNameSpace.prototype.size = function() {
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
var my = new MyNameSpace(); // create new object based on MyNameSpace
my.size(); // length would be 2
What you define on a function object's prototype would be inherited by all the function objects created via new operator.
Updated code, according to your added requirements,
var obj = {};
var MyNameSpace = function(props) {
for(var name in props) {
this[name] = props[name];
}
}
MyNameSpace.prototype.size = function() {
var length = 0;
for(var i in this){
if(this.hasOwnProperty(i)){
length++;
}
}
return this;
}
obj.name = new MyNameSpace({firstName : 'Hello', lastName : 'World'});
obj.name.size(); // length would be 2
In your code
var my = new MyNameSpace();
var my.name = {firstName : 'Hello', lastName : 'World'};
var nameCount = my.name.size(); // 2
my.name is obviously traversable from my, but the opposite is not true. That means that properties of my cannot be accessed from my.name, and my is nowhere to be found in the prototype chain of my.name. If you don't want to inherit directly from MyNameSpace you have to explicitly "hang on" whatever functions you would like to inherit.
You could do
my.name.size = my.size;
alternatively (without having to instantiate MyNameSpace):
my.name.size = MyNameSpace.prototype.size;
if you have only few functions to "inherit". Or you could define an inherit function in MyNameSpace as follows:
MyNameSpace.prototype.addToNameSpace = function(obj) {
obj.size = this.size;
// obj.propertyI = this.propertyI, etc.
}
Note that I don't use for..in here as that would add the addToNameSpace function as well.
Hope this helps