loading...

How to stringify class instances in Javascript and Express.js

adamcoster profile image Adam Coster (he/him) ・3 min read

When trying to take an object-oriented approach in Javscript, you'll run into the following problem: The native JSON.stringify() method, which works wonders for plain objects, is basically useless (by default) for class instances.

For example:

const plainObject = {
  hello: 'world',
  something: 'else',
  nested: {item: {number:10}}
};
JSON.stringify(plainObject);
// Yields the perfect JSON string:
// '{"hello":"world","something":"else","nested":{"item":{"number":10}}}'

class MyFancyClass{
  #something;
  constructor(){
    this.hello = 'world';
    this.#something = 'else';
  }
  get item(){
    return {number:10};
  }
  get nested(){
    return {item: this.item}
  }
}
const instanceOfMyFancyClass = new MyFancyClass();
JSON.stringify(instanceOfMyFancyClass);
// Yields:
// '{"hello":"world"}'

(That #something syntax is modern JavaScript's way of identifying private properties, which is why it doesn't show up in the JSON string.)

Using .toObject() and a JSON.stringify replacer

An approach I've seen in a few JavaScript libraries is to include a method .toObject() that returns a plain object representation of a class instance. Presumably the method name is mirroring the built-in .toString() method that all JavaScript objects have. In any event, having a .toObject() method on your classes is great for a lot of reasons, but one thing it does not help you with is JSON stringification.

That is, unless you take advantage of the "replacer" argument in JavaScript's native JSON.stringify method. By updating the replacer to attempt to use .toObject(), and then using that method in all your custom classes (and extending built-in classes to add it), you can still use JSON.stringify in a meaningful way on class instances.

class MyStringifiableClass {
  #something;
  constructor(){
    this.hello = 'world';
    this.#something = 'else';
  }
  get item(){
    return {number:10};
  }
  get nested(){
    return {item: this.item}
  }
  toObject(){
    return {
      hello: this.hello,
      something: this.#something,
      nested: this.nested
    }
  }
}

/**
 * @param {string} key
 * @param {any} value
 */
function jsonStringifyToObjectReplacer(key,value){
  if(value && value.toObject){
    return value.toObject();
  }
  return value;
}

const stringifiableInstance = new MyStringifiableClass();
JSON.stringify(stringifiableInstance,jsonStringifyToObjectReplacer);
// And now we're back to where we started:
// '{"hello":"world","something":"else","nested":{"item":{"number":10}}}'

For convenience, wrap your own function around the native stringifier and use that throughout your application:

function stringify(something){
  return JSON.stringify(something,jsonStringifyToObjectReplacer);
}

Change the Express.js JSON.stringify replacer

The Express.js framework is stellar for rapidly setting up a RESTful server. One of its many great traits is how it handles sending data back to clients: if you attempt to send some sort of JavaScript object Express will automatically JSON stringify that object for you and set the Content-Type header to "application/json".

// Express server setup
// See {@link https://expressjs.com/en/starter/hello-world.html}
const express = require('express');
const app = express();
app.listen(8080);

app.get('/',(req,res)=>{
 res.send({hello:'world'});
 // Client recieves '{"hello":"world"}' body
 // with content-type header set to "application/json"
});

But what if you want to send a class instance to the client? Express uses the regular JSON.stringify method on the data you send, so we're right back to the original problem. Fortunately, Express lets you set any replacer you want:

app.set('json replacer',jsonStringifyToObjectReplacer)

And with that, you can simply res.send(myClassInstance) throughout your entire Express application, for any classes that include the .toObject() method.

Posted on by:

adamcoster profile

Adam Coster (he/him)

@adamcoster

Multidisciplinary hacker, doctor of biology, developer of full stack webs, maker of games.

Discussion

pic
Editor guide