Serializing object methods using ES6 template strings and eval

Adrian Oprea
8 min readFeb 29, 2016

--

Sergio DelgadoThree-toed sloth in the Dallas World Aquarium

Understanding the way AJAX and JSON manipulation work in JavaScript could make the difference between writing an elegant, easy to reason about application, and a crude hack. As far as my experience goes, only a small number of developers I’ve talked to in the past year or so, know how to write a vanilla XMLHttpRequest, from scratch. I do agree that it is important to get the job done in a timely manner and not reinvent the wheel, but using something without understanding it’s substance is something that I cannot come to terms with, for someone other than a junior developer.

A bit of background

I’ve been working with JSON since 2009 and all I knew is that you are able to serialize JavaScript objects, by using JSON.stringify, but only the properties(values) get serialized, and the methods get left behind. I started looking into the literature around JSON serialization in JavaScript and the only information I found on JSON.org and the Mozilla Developer Network confirmed what I already said before, so then I started to think about the “forbidden” parts of JavaScript.

In my case, I started learning JavaScript reading JavaScript The Good Parts written by Douglas Crockford and if you’re like me, the rest of your career was highly influenced by that book and Crockford’s approach to JavaScript and software development. But everything comes at a cost, and the cost I had to pay was believing too much in the “Eval is evil” mantra.

What is JSON?

First, let’s start with some basics, namely, let’s see what JSON is.

According to JSON.org:

JSON (JavaScript Object Notation) is a lightweight data-interchange format. […] JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages […]

According to the Mozilla Developer Network

JSON is a syntax for serializing objects, arrays, numbers, strings, booleans, and null. It is based upon JavaScript syntax but is distinct from it: some JavaScript is not JSON, and some JSON is not JavaScript.

Both definitions tell us one thing: JSON is a format for distributing data accross “the wire” and can be easily understood and generated by both humans and computers.

What is serialization?

Again, in order to continue, we first need to establish what serialization is, and without having to resort to external resources for this, I will give you my own definition: “Serialization is a process through which one data format gets translated to an intermediary data format, for the purpose of distribution.”

The reverse of serialization is de-serialization, where you take a the intermediary data format and turn it back into its original form, so you can manipulate it and use it in your application.

Now, with theory out of the way, let’s get to the code.

Object serialization and the replacer function

Based on the knowledge we have at this point, we can now demonstrate how exactly serialization works, so let’s take an object, run it through JSON.stringify and see what we get.

'use strict'; 
let person = {
name: 'Susan',
age: 24,
sayHi: function() {
console.log('Susan says hi!');
}
};
const serialized = JSON.stringify(person);
console.log(serialized); // {"name":"Susan","age":24} typeof serialized === 'string' // true

As you can see, the sayHi method is nowhere to be seen. The reason is that JSON.stringify serializes only the properties of the object, leaving its methods behind.
Now, let’s say we want to also serialize the sayHi method. How would we go about that? First, let’s take a look at JSON.stringify’s signature, outlined below.

JSON.stringify(value[, replacer[, space]]) // => value(required) — the object we want to serialize 
// => replacer(optional) — a function that will be called for each // of the object’s properties, or an array of strings and numbers // that serve as a whitelist for the property selection process
// => space(optional) — the number of spaces each key will receive // as indent

Our main interest is the replacer argument, and we are going to use its function form. In order for this trick to work and for the sayHi method’s code to be properly converted to a string, we can call its toString() method.
Calling toString() on a function, returns a string representation of the function’s body. This way, if we callperson.sayHi.toString() we will get the following output:

"function () { console.log(‘Susan says hi!’); }"

So now that we know how to get our method’s body, let’s build the logic to serialize the whole person object, including its method.

'use strict'; 
let person = {
name: 'Susan',
age: 24,
sayHi: function() {
console.log('Susan says hi!');
}
};
let replacer = (key, value) => {
// if we get a function, give us the code for that function
if (typeof value === 'function') {
return value.toString();
}
return value;
}
// get a stringified version of our object
// and indent the keys at 2 spaces
const serialized = JSON.stringify(person, replacer, 2);
console.log(serialized);
// {"name":"Susan","age":24,"sayHi":"function () {\n\tconsole.log('Susan says hi!');\n }"}

De-serialization and reviving properties

As you can see, we now have our method, properly serialized and ready for transportation “across the wire”. What we need to do next is to de-serialize our object and make the sayHi method executable, again, and this can be accomplished using JSON.parse.

Similar to what we did with JSON.stringify, we will have to take a look at JSON.parse’s signature in order to get a clearer view of how we will perform the de-serialization process in such a way that we can convert our current sayHi string into a function, again.

JSON.parse has the following signature, according to the Mozilla Developer Network:

JSON.parse(text[, reviver]) // => text(required) - the string we wish to de-serialize 
// and convert back to a standard JavaScript object(JSON)
// => reviver(optional) - function used to pre-process keys and
// values in order to render a specific object structure

Before going any further with the solution, let’s first see what template strings are, at their most basic level. According to MDN, template literals(usually called template strings) are string literals that allow embedded expressions. To simplify this explanation, let’s take a look at a piece of code using ES6 template strings and compare it to the way we used to mimic this in ES5.

//ES6 template strings
let someComputedValue = 5 + 3;
let theTemplate = `This is the result of the computation:
${someComputedValue}`;
console.log(theTemplate);
// "This is the result of the computation: 8"
// ES5 version
var someComputedValue = 5 + 3;
var theTemplate = 'This is the result of the computation: ' +
someComputedValue;
console.log(theTemplate);
// "This is the result of the computation: 8"

You can probably see some good use-cases for template literals, one of them being DOM templates. If you’d like more info on the topic, this page on the Mozilla Developer Network sums it up pretty well.

Given that we already have our serialized version of the person object, along with its sayHi method, let’s now try to convert the body of the method back to executable code.
For this matter, we’re going to have to resort to the “evil” eval and before using something that so many people labelled as evil, let’s see what it does, so we are conscious about the risks we’re taking.

In a nutshell, eval takes strings and treats them as executable code. One of the biggest risks you’re exposing yourself and your application to would be code injection. If you end up serializing user-inserted data, be sure to thoroughly validate their input and as a rule of thumb, don’t run code inserted by your user, unless you’re building the next jsbin.com or the next codepen.io

With the knowledge we have about eval and template strings, we can now build our reviver function as follows:

let reviver = (key, value) => {  
if (typeof key === 'string' && key.indexOf('function ') === 0) {
let functionTemplate = `(${value})`;
return eval(functionTemplate);
}
return value;
};

The code is pretty self-explanatory, but for extra clarity, we’re building a new template for all the items in the string we’re parsing, that contain the ‘function’ keyword followed by a space, at the very beginning and then we’re evaluating the resulting expression and returning it to JSON.parse in order to be added to the final object.
The final version of the code would look like the snippet below:

'use strict'; 
// serialize.js
let person = {
name: 'Susan',
age: 24,
sayHi: function() {
console.log('Susan says hi!');
}
};
let replacer = (key, value) => {
// if we get a function give us the code for that function
if (typeof value === 'function') {
return value.toString();
}
return value;
}
// get a stringified version of our object
// and indent the keys at 2 spaces
const serialized = JSON.stringify(person, replacer, 2);

console.log(serialized);
// {"name":"Susan","age":24,"sayHi":"function () {\n\tconsole.log('Susan says hi!');\n }"}
// de_serialize.js
let reviver = (key, value) => {
if (typeof value === 'string'
&& value.indexOf('function ') === 0) {
let functionTemplate = `(${value})`;
return eval(functionTemplate);
}
return value;
}
const parsedObject = JSON.parse(serialized, reviver);
parsedObject.sayHi(); // Susan says hi!

The reason that we wrap the function in parentheses is that we need to force the vm to evaluate the function in an expression context, and our function needs to be a function expression as it is missing its name. We are only operating with eval on the value of the sayHi key, which is an unnamed function. If you’d like more info about this limitation of eval take a look at this answer on StackOverflow.
As you can see, the newly resulted object has the sayHi method available and ready for use and executing the method prints the same message as person.sayHi.

Another issue I ommitted to mention about eval is that it is pretty slow, because it has to invoke the VM in order to evaluate the code, unlike the built-in Function constructor, which is highly optimised in newer versions of JavaScript engines. If performance is something that is critical for you, there’s an option to use the Function constructor instead of eval in yourreviver function like in the snippet below:

let reviver = (key, value) => {  
if (typeof value === 'string'
&& value.indexOf('function ') === 0) {
let functionTemplate = `(${value}).call(this)`;
return new Function(functionTemplate);
}
return value;
};

Even though it is said that the Function constructor is faster than eval, be cautious that in this case, you have to create 2 functions in-memory — the outer IIFE that gets auto-executed and returns the method we want.
You also need to use Function.prototype.call in order to keep this relative to the current object, and not to the window/global object.

Ultimately, it all comes down to personal preference so whether you use eval or another method it is all up to you, the important thing is to use what makes the most sense for you and gets the job done in an elegant manner.

The article is also available on my personal blog.

Reference

Like the article? Found it useful? Follow me on Medium / Twitter.

--

--

Adrian Oprea
Adrian Oprea

Written by Adrian Oprea

Founder & CEO of WeRemote.EU. Remote work enthusiast. Bookworm.

Responses (5)