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.
As a quick review of toJSON
, open your favorite JavaScript console
and type (copy & paste) this:
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.
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.
// 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:
{ "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.
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.
// 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.
// 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:
{ "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.
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.
// 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
orbase
. 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 namedestroy
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.
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 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,