On the client, you might use the
fetch API to hit this endpoint and deserialize (parse) the data:
What's the relationship between the
user object in the server and the corresponding
user object in the client? And how would you model this in TypeScript?
JSON.parse functions, we can alternatively ask: what's the return type of this function?
If you mouse over
jsonRoundTrip on the TypeScript playground, you'll see that its inferred return type is
any. That's not very satisfying!
It's tempting to make the return type
T, so that this is like an identity function:
But this isn't quite right. First of all, there are many objects which can't be directly represented in JSON. A regular expression, for instance:
Second, there are some values that get transformed in the conversion process. For example,
undefined in an array becomes
strictNullChecks in TypeScript,
undefined have distinct types.
If an object has a
toJSON method, it will get called by
Dates get converted to
strings. Who knew? You can read the full details of how this works on MDN.
How to model this in TypeScript? Let's just focus on the behavior around Dates. For a complex mapping like this, we're going to want a conditional type:
This is already doing something sensible:
We even get support for union types because conditional types distribute over unions:
But what about object types? Usually the
Dates are buried somehwere in a larger type. So we'll need to make
Jsonify recursive. This is possible as of TypeScript 3.7:
In the case that we have an object type, we use a mapped type to recursively apply the
Jsonify transformation. This is already starting to make some interesting new types!
What if there's an array involved? Does that work?
It does! How was TypeScript able to figure that out?
First of all, Arrays are objects, so
T extends object is true for any array type. And
keyof T includes
number, since you can index into an array with a
number. But it also includes methods like
So it's a bit of a surprise
Jsonify produces such a clean type for the array. Perhaps mapped types over arrays are special cased.
But regardless, this is great! We can even loosen the definition slightly to handle any object with a
toJSON() method (including Dates):
As TypeScript Development lead Ryan Cavanaugh once said, it's remarkable how many problems are solved by conditional types. The types involved in JSON serialization are one of them! If you produce and consume JSON in a TypeScript project, consider using something like
Jsonify to safely handle Dates in your objects.