/kamil

How to rehydrate typed data?

Originally published at dev.to

The problem

I have a simple blog with JSON file used as database. I would like to get all posts and display them in a list.

Below you can see my Post interface:

interface Post {
  authorId: number;
  title: string;
  tags: string[];
  content: string;
  state: 'draft' | 'published';
  createdAt: Date;
  modifiedAt: Date;
  publishedAt: Date | null;
}

And this is database representation in JSON:

{
  "posts": [{
    "authorId": 7,
    "title": "My first post",
    "tags": [],
    "content": "Hello world!",
    "state": "draft",
    "createdAt": "2022-05-20T17:21:34.000Z",
    "modifiedAt": "2022-05-23T18:45:17.000Z",
    "publishedAt": null
  }]
}

When I parse this with JSON.parse, I get type mismatch on createdAt, modifiedAt and publishedAt since they are all strings instead of Date objects.

The question is: how can I fix this mismatch?


The Solution

The first idea that comes to mind is to map over the list and manually convert to proper objects

const parsedData = JSON.parse(jsonData);

const data = {
  posts: parsedData.posts.map((post) => {
    return {
      ...post,
      createdAt: new Date(post.createdAt),
      modifiedAt: new Date(post.modifiedAt),
      publishedAt: post.publishedAt ? new Date(post.publishedAt) : null
    };
  })
};

This solution works, but has one issue: I need to repeat this conversion every time I want to get Post objects. Additionally, after making changes to Post interface, I need to reflect them in all places.

Let’s improve this a little.

Creating a function

I am going to extract conversion code to functions for reducing repetition. It will also make maintenance easier.

function parsePost(post) {
  return {
    ...post,
    createdAt: new Date(post.createdAt),
    modifiedAt: new Date(post.modifiedAt),
    publishedAt: post.publishedAt ? new Date(post.publishedAt) : null
  };
}

function parsePosts(list) {
  return list.map(parsePost);
}

I can freely use this function as needed:

const parsedData = JSON.parse(jsonData);
const data = {
  posts: parsePosts(parsedData.posts)
};

Using a library

Although the current solution works, is simple and reusable, I can go one step further.

Some time ago, I found an interesting library, that allows to serialize JS objects and save information about its type. It was a few years old, so I decided to create a modern version of it.

Let me present to you: hydration-next.


At the beginning, I need to add type info to my post. For this, I can use dehydrate function. It returns an object with additional _types field. There is also a stringify function, which is a shortcut for JSON.stringify(dehydrate(data)).

Here you can see how my JSON looks now:

{
  "authorId": "7",
  "title": "My first post",
  "tags": {},
  "content": "Hello world!",
  "state": "draft",
  "createdAt": 1653067294000,
  "modifiedAt": 1653331517000,
  "publishedAt": "",
  "_types": {
    "authorId": "number",
    "title": "string",
    "tags": "array",
    "content": "string",
    "state": "string",
    "createdAt": "Date",
    "modifiedAt": "Date",
    "publishedAt": "null"
  }
}

I can safely store it in file, local storage or elsewhere.

For the other way, I can use hydrate function. Similarly, there is a parse function, which is a shortcut for hydrate(JSON.parse(data)).

Primitive data types (string, boolean, number, null) are supported out-of-the-box, as well as arrays, objects, Date, RegExp and Buffer. You are also able to add custom types and define functions to hydrate and dehydrate them.

Here you can see a previous example, this time with hydration-next:

import { parse } from 'hydration-next';

// ...

const data = parse(jsonData);

The End

I hope you enjoyed this post. If you decide to use my package and find any issues with it, please let me know. I would like to make it as good as possible.

See you next time!

Created with Elmstatic