views:

624

answers:

4

Attempting to deserialize JSON data and update each object's prototype and inherit a common function.

However, the following script throws error "people[0].getFullName is not a function". The prototype for deserialized objects appears to be undefined after assignment.

<html>
<head>
<script>
var json = '[ {"firstName": "John", "lastName": "Smith"}, {"firstName": "Nancy", "lastName": "Jones"} ]';
var people;
eval('people = ' + json);

function Person() { }

Person.prototype.getFullName = function() {
    return this.firstName + ' ' + this.lastName;
}

//assign prototype
for(var i=0; i < people.length; i++){
    people[i].prototype = new Person();
}


if(people[0].getFullName() !== 'John Smith')
    alert('Expected fullname to be John Smith but was ' + people[0].getFullName());
</script>
</head>
</html>
+4  A: 

An object x that is created by x=new Person() is linked to/inherits from Person.prototype, but as far as the ecma standard is concerned you cannot change x.prototype in order to change that link/inheritance afterwards, that's the "magic power" only the new keyword possesses.
Mozilla seems to offer a way to change the object link after an object has been created through the non-standard property __proto__.

Mozilla-only:

//assign prototype
for(var i=0; i < people.length; i++){
    people[i].__proto__ = Person.prototype;
}

should work anywhere:

function Person(data) { this.data = data; }
Person.prototype.getFullName = function() {
    return this.data.firstName + ' ' + this.data.lastName;
}

eval('people = ' + json);
//assign prototype
for(var i=0; i < people.length; i++){
    people[i] = new Person(people[i]);
}
VolkerK
Ahhh... thanks for the enlightenment. The cross-browser example of constructing with the data arg works. I'll experiment with this some more. Thx!
Cubic Compass
+2  A: 

The prototype property is a property of constructors, not of instances. What you are looking for is the property __proto__:

people[i].__proto__ = new Person();

The bad news is that it does not work in all browsers. It does work in Firefox and Safari, it does not work in IE. An alternative is to use constructors to instantiate your array of people. Unfortunately you'll have to copy all properties:

function Person(obj) {
    for (var property in obj) {
        this[property] = obj[property];
    }
    return this;
}
Person.prototype.getFullName = function() {
    return this.firstName + ' ' + this.lastName;
}

var people;
eval('people = ' + json);
for(var i=0; i < people.length; i++) {
    people[i] = new Person(people[i]);
}
molf
The property copy loop on the CTOR provides the best late-bound solution for what I'm trying to do. Thx!
Cubic Compass
+2  A: 

Basically, you have to get the JSON object INTO a Person object, and then the getFullName just applies. I've rewritten what you had slightly to work. There's probably even better ways, but I think this is what you were intending to do...

<html>
<head>
<script>
//NOTE: Sending around JSON arrays leaves bad security holes for non-IE browsers (__defineSetter__)
var json = '[ {"firstName": "John", "lastName": "Smith"}, {"firstName": "Nancy", "lastName": "Jones"} ]';
//Persons is just a temporary JSON array
var persons = eval(json);

//constructor takes optional object instance and copies all properties if it gets one
function Person(person) { 
    if (person) {
     for(var prop in person)
     this[prop] = person[prop];
    }
}

//Prototype applies to all Person objects
Person.prototype.getFullName = function() {
    return this.firstName + ' ' + this.lastName;
}

//Create People array
var people = new Array();
for(var i=0; i < persons.length; i++){
    people[i] = new Person(persons[i]);
}

//Now do your check
if(people[0].getFullName() !== 'John Smith')
    alert('Expected fullname to be John Smith but was ' + people[0].getFullName());

</script>
</head>
</html>
Tony Heupel
Thanks Tony. I'm using ASP.NET Sys.Serialization namespace instead of eval in production. Would be curious to learn more about potential security risks involved sending JSON data.
Cubic Compass
Take a look at: http://haacked.com/archive/2009/06/25/json-hijacking.aspx
Tony Heupel
+2  A: 
for(var i=0; i < people.length; i++){
      people[i].getFullName = Person.prototype.getFullName; }
Mike Chaliy