But there’s a catch to the `.toJSON` method – in spite of it’s name, you should not return a JSON string from it.
Defining The .toJSON Method
The definition of toJSON resides inside of the specification for JSON.stringify in ECMAScript 5.1 and ECMAScript 6. But, looking at the technical definitions does not make it easy to understand the intent or purpose.
When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value.
This description is easier to understand than a pure technical specification, providing a very important point about the toJSON method: “A toJSON method does not serialize: it returns the value […] that should be serialized.”
Returning An Object To Serialize
The JSON.stringify method will work on nearly any object, as-is – no special methods or features needed for that object. If you want to customize the output of the stringify method, though, you can provide a toJSON method on the target object.
The return value for the toJSON method – as mentioned above – should be the value to serialize into the final JSON document.
This means you don’t need to manually convert an object into a proper JSON document string. Instead, you can return an object or any other valid JSON data type, and the stringify process will serialize the result for you.
For example, say you have a “user” object with three attributes:
Maybe you’re using a getter for the fullName attribute, to concatenate firstName and lastName at runtime:
With this getter, calling JSON.stringify on the user object will return all three attributes as a JSON string:
If you need to exclude the fullName, you can do this in a number of different ways, including a custom toJSON method:
(Note: There are many ways to modify the output of JSON.stringify with an object, including a “replacer” method / parameter of JSON.stringify, that I’ll cover in another post.)
In this toJSON method implementation, the return value is not a string as a JSON document. Instead, it is an object literal – a set of key/values that will be serialized into the JSON document for you.
In the same way that you can add fields to be serialized, you can also exclude fields from serialization. Don’t want that firstName attribute in the JSON document? Remove it from the object that toJSON returns. The toJSON method isn’t limited to returning object literals, either. Any valid JSON data type / value can be returned, meaning the possibilities for customizing the JSON document for an object are nearly endless.
But what would happen if you returned a JSON document from the toJSON method?
toJSON Returning JSON?
It’s a common mistake and one that has caused countless hours of headaches and heartache for developers around the world. If you return a valid JSON string from the toJSON method, JSON.stringify will attempt to serialize it again, leaving you with a document like this:
instead of a proper JSON document that looks like this:
Do you see the difference, here?
The addition of \ near all of the quotes is an indication of JSON.stringify serializing a string, instead of an object. This is done to escape the quotes that were present in the string already.
Deserializing Dual-Serialized JSON
If you return a JSON string from toJSON, however, you will end up with a second layer of serialization – the \ escape characters on the quotes, inside of the string value, as shown before:
When you attempt to deserialize this document, JSON.parse will turn the dual-stringified and escaped JSON into a string literal – a proper JSON document that is not escaped, as shown previously:
All of this headache and dual-parsing was caused by returning JSON from the toJSON method, instead of returning an object or value to be serialized.
Don’t Return JSON From toJSON
Special thanks to Kyle Simpson for suggesting this blog post and for reviewing it!