DEV Community

Kamil Trusiak
Kamil Trusiak

Posted on

How to rehydrate typed data?

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;
}
Enter fullscreen mode Exit fullscreen mode

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
  }]
}
Enter fullscreen mode Exit fullscreen mode

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
    };
  })
};
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

I can freely use this function as needed:

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

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
nombrekeff profile image
Keff

Interesting solution!

The only thing that bothers me a bit is the fact that the dehydrated result contains a lot of extra data, and requires this extra data to hydrate it, forcing you to use hydration-next for both hydrating and dehydrating. I see an issue with this, what if the data comes from an api that does not return that extra data. How would it be handled in that case?

Collapse
 
kamil7x profile image
Kamil Trusiak

If there is no _types field, the original object is returned, so it will work same as JSON.parse.