Restoring JavaScript Objects

Shantanu Raj
Code & Development
Published in
5 min readMay 12, 2016

--

“There won’t be any winning tonight.” — JavaScript

Saving & restoring objects in JavaScript seems trivial right?

const save    = obj => JSON.stringify(obj)
const restore = str => JSON.parse(str)
Obama out

Well, not so fast; let’s try this in a real (as it gets) world example:

class TodoList {
constructor(tasks) {
this.tasks = tasks || []
}
addTask(task) {
this.tasks.push(task)
}
}
const todos = new TodoList([
'laundry',
'assignments',
'procrastination'
])
So much to do!

That’s nice we have an instance of TodoList, with our tasks, let’s us our save implementation to save it.

const savedTodos = save(todos)

This comes in handy, when we want to serialise our object for saving to something like localStorage or redis. So we are able to save our todos list to our favourite storage in string format. Let’s try to read it back.

const restoredTodos = restore(savedTodos)
That looks different

restoredTodos, unlike the original todos is an plain Object and not an instance of TodoList, only the data fields are restored and not the the member functions.

1. Restore function

Add a static function restore to TodoList that takes the plain JSON representation and returns a working instance with the methods added back.

class TodoList {
constructor(tasks) {
this.tasks = tasks || []
}
addTask(task) {
this.tasks.push(task)
}
static restore(todos) {
return new TodoList(todos.tasks)
}
}

This allows us to pass the savedTodos to the restore function and get back a functioning object.

const restoredTodos_ = TodoList.restore(restoredTodos)
Hurray!!!

This works if all the member fields are initialised in the constructor but what if we have a field that is not settable via the constructor. Consider

class TodoList {
constructor(tasks) {
this.tasks = tasks || []
}
addTask(task) {
this.tasks.push(task)
}
addName(name) {
this.name = name
}
static restore(todos) {
return new TodoList(todos.tasks)
}
}

Here the name field is dynamically assigned to the object by the addName method. What to do in this case, manually assign every such field? Well thank ES6, we can use Object.assign to simplify the job for us.

class TodoList {
constructor(tasks) {
this.tasks = tasks || []
}
addTask(task) {
this.tasks.push(task)
}
addName(name) {
this.name = name
}
static restore(todos) {
return Object.assign(new TodoList(), todos)
}
}

That was easy, Object.assign is similar to lodash’s extend/assign function, it can be used to merge two or more objects.

Object.assign(target, source...)

This way the all the fields of the object are restored with its member functions. That sounds good so are we done? Not quite.

What if tasks instead of being plain string type were custom objects too?

const Task {
constructor(description) {
this.description = description
}
update(description) {
this.description = description
}
}

We would need to add a similar restore function to the Task class.

const Task {
constructor(description) {
this.description = description
}
update(description) {
this.description = description
}
static restore(task) {
return Object.assign(new Task(), task)
}
}

Then modify the TodoList restore function to invoke Task’s restore function on each of the task it holds.

class TodoList {
constructor(tasks) {
this.tasks = tasks || []
}
addTask(task) {
this.tasks.push(task)
}
addName(name) {
this.name = name
}
static restore(todos) {
return Object.assign(new TodoList(), todos, {
tasks: todos.map(Task.restore)
})
}
}

Does the job but doesn’t look quite pretty. This work-around establishes a hard coupling between the parent and the child object, each parent needs to manually restores its child object before restoring itself.

To solve this look no further than lodash or any FP language like Haskell.

2. Functional Approach

The functional approach to this problem proclaims

Data-structures hold data, not functions — Someone (I hope)

If we step out of the comforts of Object-oriented programming, and step into the quirky world of Functional programming the problem solves it self.

(todos: TaskList).addTask(task: Task) //OOP approachaddTask(todos: TaskList, task: Task)  //FP  approach

Instead of defining the methods on instances, define them to take the instance as a parameter itself.

This does not make much sense in OOP land as addTask means nothing outside the context of a TodoList object which is its internal state or this”. In OOP only a TodoList can have the addTask method which modifies the internal state of the object.

In FP however, addTask is a function (similar to math functions) it takes some parameters and does some work (this one being a side-effect), but that’s a story for another time. In functional programming the parameters are the context, and not the internal state of the object.

const addTask = (todos, task) => todos.tasks.push(task)const addName = (todos, name) => todos.name = nameclass TodoList {
constructor(tasks) {
this.tasks = tasks || []
}
}
const todos = new TodoList([
'laundry',
'assignments',
'procrastination'
])
const savedTodos = save(todos)
const restoredTodos = restore(savedTodos)
addTask(restoredTodos, 'win')
WIN!

--

--

Shantanu Raj
Code & Development

Functional Programming fanatic; software engineer at HousingAnywhere