21

When using AJAX, I tend to pass objects from my server to Javascript in the form of JSON objects (aka Javascript). Certain functions within my Javascript rely on the specific type of object I am using. For instance, lets use a phone number for example. I have a constructor:

function PhoneNumber(number, type, isPrimary, contactId, id, className) {
    this.number = number;
    this.type = type;
    this.isPrimary = isPrimary;
    this.contactId = contactId;
    this.id = id;
    this.className = className;
}

Which I use when creating a phone number object in my Javascript. In some situations I don't create the object in JS, I get the object from the server so it comes in the form of a generic object with the exact same fields. So when my code relies on the specific type by using something such as this:

var objType = objArray[i].constructor.name;
var mappedObj;

switch(objType) {
    case 'PhoneNumber':
        currentArray = currentArray.phone;
        //Convert response to javascript object.
        mappedObj = mapPhone(jsonResponse[i]);
        break;
    case 'Email':
        currentArray = currentArray.email;
        mappedObj = mapEmail(jsonResponse[i]);
        break;
    case 'Address':
        currentArray = currentArray.address;
        mappedObj = mapAddress(jsonResponse[i]);
        break;
    case 'Website':
        currentArray = currentArray.website;
        mappedObj = mapWebsite(jsonResponse[i]);
}

In this situation, I check the name of the objects constructor and set certain variables based on that name. If the object I check the name on is a JSON from the server, it simply gives me a generic "Object" response and thus the code does not work. I get around this by using a mapping function for each object such as:

function mapPhone(phoneObj) {
    var id = phoneObj.id;
    var contactId = phoneObj.contactId;
    var number = phoneObj.number;
    var type = phoneObj.type;
    var primary = phoneObj.isPrimary;
    var className = phoneObj.className;
    var phoneNumber = new PhoneNumber(number, type, primary, contactId, id, className);
    return phoneNumber;
}

This works just fine, but to me seems a little redundant. Is this the best way to solve the JSON Object problem, or is there a better solution? I understand this is more of a "Am I doing this the best way possible" type of question, but I repeat this type of logic CONSTANTLY in my Javascript code and I figure I might as well get another opinion or two on whether or not its the proper way to do this before I have to spend hour upon hour fixing it in the future.

EDIT: I ended up accepting a jQuery solution because I happen to use jQuery in my project. There are however multiple solutions that also worked for me before I found the jQuery option. They just weren't quite as clean and efficient.

Craig Conover
  • 4,710
  • 34
  • 59
ryandlf
  • 27,155
  • 37
  • 106
  • 162
  • 7
    *"JSON objects (aka Javascript)"* JSON is not aka JavaScript. It has its own name because it has its own syntax and specification. –  Jan 09 '12 at 02:37

4 Answers4

30

The following requires you to have the same properties in your object and your JSON object.

var phoneNumber = $.extend(new PhoneNumber(), yourJSONObject);

This basically creates a new PhoneNumber object and then copies all properties from your JSON object onto it. The $.extend() method is from jQuery, but you could also use as similar method from e.g. Underscore.js or one of the other js libraries/frameworks.

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • I do use jQuery so this may be an even easier solution for me. – ryandlf Jan 09 '12 at 03:33
  • 3
    If you use angular, you can do `var phoneNumber = angular.copy(yourJSONObject, new PhoneNumber())`. I may not have that syntax right, but you can do it, and that is the function you want! – Christopher Nov 07 '15 at 17:05
  • 1
    For angular you can use either angular.copy(), angular.extend() or angular.merge(). Copy strips your new object's properties and then clones the source object's properties. Extend and merge brings the source object's properties into the new object (through shallow and deep copy respectively), while still keeping the new object's existing properties. http://davidcai.github.io/blog/posts/copy-vs-extend-vs-merge/ – Nhan Nov 18 '16 at 07:42
6

This similar question has a lot of interesting answers:

Parse JSON String into a Particular Object Prototype in JavaScript

Based off the poster's own answer, I think this would be an effective solution for you:

function recastJSON(jsonObject) {
    // return generic object if objectType is not specified
    if (!jsonObject.objectType)
        return jsonObject;

    // otherwise create a new object of type specified
    var obj = eval('new '+jsonObject.objectType+'()');
    for(var i in jsonObject)
        obj[i] = jsonObject[i];
    return obj;
}

You will need to add objectType to the JSON objects you are receiving to define the javascript class you want to instantiate. Then when you call this function, it will cast the object to that type and copy the data over (including the variable 'objectType').

Using your phone number example, your code would look like this:

// generic object from JSON
var genericObject = {objectType:'PhoneNumber', number:'555-555-5555', type:'home', primary:true, contactId:123, id:1234, className:'stdPhone'};
var phoneObject = recastJSON(genericObject);
Community
  • 1
  • 1
Levi
  • 2,103
  • 14
  • 9
2

AFAIK, in everything that is not IE, you can do this:

// define a class
var Foo = function(name) {
  this.name = name;
}
// make a method
Foo.prototype.shout = function() {
  return "I am " + this.name;
}
// make a simple object from JSON:
var x = JSON.parse('{ "name": "Jason" }');
// force its class to be Foo
x.__proto__ = Foo.prototype;
// the method works
x.shout();

Unfortunately, IE does not support the __proto__ accessor, so what you would need to do is first create an empty instance of your object, then just copy everything over:

// make a simple object from JSON:
var x = JSON.parse('{ "name": "Jason" }');
// make an empty Foo
var y = Object.create(Foo.prototype);
// copy stuff over
for (a in x) {
  y[a] = x[a];
}
y.shout();

Both of these approaches are quite a bit more generic than your mapWhatever functions, keeping it DRY.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • Looks like a cleaner solution. This is what I was looking for, thanks. Assuming my testing works, this looks like an accepted answer. – ryandlf Jan 09 '12 at 03:03
  • 1
    Downvoted two years after Amadan posting his answer. One should never access the prototype using __proto__ since this is deprecated: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto – Frederik Prijck Dec 01 '14 at 13:44
1

If not supporting older browsers is ok, You can use Object.create to do the mapping for you. (dropping the shim—at least the shim at MDN—in will not fix older browsers, since that shim does not accept the second parameter.)

DEMO

function makeThisExtend(obj, CtorFunc) {
    for (var k in obj)
        if ({}.hasOwnProperty.call(obj, k))
            obj[k] = { value: obj[k] };

    return Object.create(CtorFunc.prototype, obj);
}

var objFromServer = { Number: "123", id: 5 };
objFromServer = makeThisExtend(objFromServer, PhoneNumber);


alert(objFromServer.Number + " " + objFromServer.id); //123 5
alert(objFromServer.constructor); //function PhoneNumber ...
Adam Rackis
  • 82,527
  • 56
  • 270
  • 393