Telerik blogs

In my previous post on JSON, I covered the basics of what a JSON document is, and showed a few very simple things that can be done to produce them. In this post, I want to expand on one of those methods, toJSON, and show how to produce a custom JSON document for any JavaScript object, no matter what framework is serializing the object.

toJSON

As a quick review of toJSON, open your favorite JavaScript console and type (copy & paste) this:

toJSON Console Example

var obj = { foo: "bar", baz: "quux", toJSON: function(){ return {a: "b"}}};
JSON.stringify(obj);

The result is an object that has "foo" and "bar" attributes, but produces a JSON document of {"a": "b"}.

The toJSON method is part of the JSON specification. It provides opportunity to override the default serialization process, and return the JavaScript object that should be serialized instead of the original object.

This might not seem terribly useful off-hand, but it does have some real value in application development.

A Registration Form

For this example, I'm going to borrow code from the registration form example for the MVVM framework.

The example shown on that demo page doesn't submit anything to a server API. It is fairly simple to add this capability, though. I can set up a DataSource to create a record on the server and then use the "register" button click to save the record.

A Registration Form

// define a DataSource to handle creating a registration entry
var ds = new kendo.data.DataSource({
  transport: {
    create: {
      url: "/api/registration",
      dataType: "json",
      type: "POST"
    },
    
    // post the data as JSON instead of raw form post
    parameterMap: function(options, operation){
      return kendo.stringify(options)
    }
    
  },
  autoSync: false,
  schema: {
    model: {
      id: "RegistrationID"
    }
  }
});

// Set up the view model to run the form
var viewModel = kendo.observable({
  firstName: "John",
  lastName: "Doe",
  genders: ["Male", "Female"],
  gender: "Male",
  agreed: false,
  confirmed: false,

  register: function(e) {
    e.preventDefault();

    // when we click the "register" button, sync
    // the registration back to the server
    ds.sync();
    
    this.set("confirmed", true);
  },

  startOver: function() {
    this.set("confirmed", false);
    this.set("agreed", false);
    this.set("gender", "Male");
    this.set("firstName", "John");
    this.set("lastName", "Doe");
  }
});

ds.add(viewModel);

kendo.bind($("form"), viewModel);

When I run this and click the "Register" button, it will attempt to POST this JSON document to my server:

toJSON Output Of The Registration

{
  "firstName":"John",
  "lastName":"Doe",
  "genders":["Male","Female"],
  "gender":"Male",
  "agreed":true,
  "confirmed":false,
  "dirty":false,
  "id":""
}

This document contains all of the information that I need from the registration form, but it also contains information that my API does not need. For example, there is no need to send back a list of "genders", or the "dirty" flag. My API does not use these fields, and depending on the server I'm using, this can cause problems.

Hijacking .toJSON

To filter out the unwanted data and prevent the server from receiving more information than it needs, I can override the .toJSON method on my Observable object.

Overriding toJSON

// Set up the view model to run the form
var viewModel = kendo.observable({

  // ... existing code goes here

  // hijack the toJSON method and overwrite the
  // data that is sent back to the server
  toJSON: function(){
    return {
      a: "B",
      c: "d",
      foo: "BAR"
    }
  }
}

Now when I click the "Register" button, my server is sent the following JSON document:

{"a":"B","c":"d","foo":"BAR"}

Of course sending back junk data isn't exactly what I want. A better idea would be to have the toJSON method serialize all of the data that I need to send back, and ignore the extra information.

A Better toJSON Method

// Set up the view model to run the form
var viewModel = kendo.observable({

  // ... existing code goes here

  // hijack the toJSON method and overwrite the
  // data that is sent back to the server
  toJSON: function(){
    return {
      firstName: this.firstName,
      lastName: this.lastName,
      gender: this.gender,
      agreed: this.agreed
    };
  }

}

When I click the "Register" button, now, I see this sent to the server:

The Right JSON Document

{
  "firstName":"John",
  "lastName":"Doe",
  "gender":"Male",
  "agreed":true
}

This version of the JSON document only supplies the values that my API needs, and nothing else.

Additive vs subtractive

Supplying a custom .toJSON method is definitely useful, but it can also be rather tedious. If I have a very large form - 30 or 40 fields for example - then it would be very time consuming and require a lot of code and maintenance to write the custom method the way that I've shown above. There is an alternative, though, which will make my life much easier in some cases.

The above example is an additive version of a toJSON method. Every time I need a new field in the JSON document, I need to add it to the method manually. The alternative to this, is a subtractive toJSON method. In this version, I only need to remove the fields that are not needed and allow all other fields to pass through.

Removing Fields

// Set up the view model to run the form
var viewModel = kendo.observable({

  // ... existing code goes here

  // hijack the toJSON method and overwrite the
  // data that is sent back to the server
  toJSON: function(){

    // call the original toJSON method from the observable prototype
    var json = kendo.data.ObservableObject.prototype.toJSON.call(this);

    // remove the fields that do not need to be sent back
    delete json.genders;
    delete json.confirmed;
    delete json.dirty;
    delete json.id;

    return json;
  }
}

I'm doing 2 very different things in this case. First, I'm using JavaScript's prototypes to call the original version of the toJSON method.

If you're coming from another object-oriented language like C#, Java, or Ruby, you can think of this line:

var json = kendo.ObservableObject.prototype.toJSON.call(this);

as the equivalent of a call to super or base. It reaches back to the original method of the defining type, and calls it in the context of the current object instance.

If this were C#, it would look like this:

var json = base.toJSON();

Although some browsers provide a short syntax to reach an object's base methods, closer to what C# provides, the prototype code above is compatible across all browsers and is recommended at this point in time.

The second difference is that instead of adding fields to the object, I'm removing them. The JavaScript delete keyword will remove a specified attribute from a specified object. Note that this is not the same as deleting a record from a database. The delete keyword does not cause any network calls or other code to be executed. It only removes an attribute from an object.

Since delete is a keyword in JavaScript, most frameworks opt for the name destroy when creating a method that will remove an object from a data store.

By using a subtractive form of a toJSON method, I can potentially reduce the number of fields that need to be managed. I'm not limited to either additive or subtractive implementations, though. These can be combined in to a more complex and robust implementation that both adds fields and removes them as needed.

One For All, All For One

Overriding a .toJSON method is an easy way to ensure the server API is only getting the data is needs. It provides a simple entry point for any framework to serialize a JavaScript object in to a JSON document. And while the samples that I've shown in this blog post are centered around Kendo UI's MVVM framework, this is a standard that all modern browsers and frameworks implement and know how to work with. Having the .toJSON method in your tool-belt will allow you to customize nearly any object for nearly any modern JavaScript framework, including jQuery, Knockout, Backbone, Ember, Angular and more.


About the Author

Derick Bailey

About the Author
Derick Bailey is a Developer Advocate for Kendo UI, a developer, speaker, trainer, screen-caster and much more. He's been slinging code since the late 80’s and doing it professionally since the mid 90's. These days, Derick spends his time primarily writing javascript with back-end languages of all types, including Ruby, NodeJS, .NET and more. Derick blogs atDerickBailey.LosTechies.com, produces screencasts atWatchMeCode.net, tweets as @derickbailey and provides support and assistance for JavaScript, BackboneJS,MarionetteJS and much more around the web.

Comments

Comments are disabled in preview mode.