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.