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!
Top comments (2)
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?
If there is no
_types
field, the original object is returned, so it will work same asJSON.parse
.