Compare commits
No commits in common. "13fc5afc7976d5b66d1b96947034320ea65d8797" and "95ddd7d2dfdebebc3bd27b7a6cd50adbcb6620fa" have entirely different histories.
13fc5afc79
...
95ddd7d2df
|
@ -63,7 +63,7 @@ images = [""]
|
||||||
#
|
#
|
||||||
homeSubtitle = [
|
homeSubtitle = [
|
||||||
"Hi! I'm a Software Engineer, an avid Linux user, an enthusiast of many programming languages, a <a href='/recipes'>home cook</a>, and an amateur gardener.",
|
"Hi! I'm a Software Engineer, an avid Linux user, an enthusiast of many programming languages, a <a href='/recipes'>home cook</a>, and an amateur gardener.",
|
||||||
"My interests include building web and mobile applications, both front and back end. Over the years I learned and used many programming languages and technologies, including JavaScript, TypeScript, React, React Native, Python, Java, C, C++, Clojure, Rust, and Haskell. Pretty much everything I've worked on is open source and available on <a href='https://github.com/SeriousBug/'>my Github page</a>.",
|
"My interests include building web and mobile applications, both front and back end. Over the years I learned and used many programming languages and technologies, including JavaScript, TypeScript, React, React Native, Python, Java, C, C++, Clojure, Rust, and Haskell. Pretty much everthing I've worked on is open source and available on <a href='https://github.com/SeriousBug/'>my Github page</a>.",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Set a background for the homepage
|
# Set a background for the homepage
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
---
|
|
||||||
title: "Get inferred type for a generic parameter in TypeScript"
|
|
||||||
date: 2023-01-28T14:50:54-05:00
|
|
||||||
toc: false
|
|
||||||
images:
|
|
||||||
tags:
|
|
||||||
- dev
|
|
||||||
- typescript
|
|
||||||
---
|
|
||||||
|
|
||||||
Have you used [Zod](https://zod.dev/)? It's a very cool TypeScript library for
|
|
||||||
schema validation. Compared to alternatives like Joi, one of the biggest
|
|
||||||
strenghts of Zod is that it can do type inference. For example,
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const PersonSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
age: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
type Person = z.infer<typeof PersonSchema>;
|
|
||||||
// This is equivalent to { name: string; age: number; }
|
|
||||||
```
|
|
||||||
|
|
||||||
Now I was recently working on a database client, where I'm using a validator
|
|
||||||
function to ensure the data on the database matches what the client expects. I
|
|
||||||
then take advantage of TypeScript's type inference so the type of everything
|
|
||||||
matches. It looks like this:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
class Database<T> {
|
|
||||||
constructor(validator: (input: unknown) => T);
|
|
||||||
|
|
||||||
function get(key: string): T | undefined { /* ... */ }
|
|
||||||
function put(key: string, data: T) { /* ... */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note I didn't have to specify the type parameter,
|
|
||||||
// TypeScript infers it from the validator argument
|
|
||||||
const PersonDB = new Database(PersonSchema.parse);
|
|
||||||
```
|
|
||||||
|
|
||||||
At this point I started to wonder, could I do something similar to what Zod does
|
|
||||||
and get the inferred type for the objects that are stored in the database? While
|
|
||||||
this is not required in this example above since I could get the type from Zod,
|
|
||||||
the validator function doesn't necessarily have to be implemented with Zod.
|
|
||||||
|
|
||||||
After reading through Zod's codebase, I found the trick they use, and it's very
|
|
||||||
simple. Let's see it:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
class Database<T> {
|
|
||||||
readonly _output!: T;
|
|
||||||
|
|
||||||
constructor(validator: (input: unknown) => T);
|
|
||||||
|
|
||||||
function get(key: string): T | undefined { /* ... */ }
|
|
||||||
function put(key: string, data: T) { /* ... */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
type EntityOf<D extends Database<any>> = D["_output"];
|
|
||||||
|
|
||||||
const PersonDB = new Database(PersonSchema.parse);
|
|
||||||
|
|
||||||
type Person = EntityOf<typeof PersonDB>;
|
|
||||||
```
|
|
||||||
|
|
||||||
This is surprisingly simple. We add a property `_output` to the class, which has
|
|
||||||
the inferred type. We can then get the type through that property with
|
|
||||||
`D["_output"]`. The `!` in the definition of the property is there because we
|
|
||||||
never actually set any value for `_output`. TypeScript normally will detect and
|
|
||||||
warn us that we did not set `_output`, but the exclamation point suppresses
|
|
||||||
that.
|
|
||||||
|
|
||||||
This is not without drawbacks of course, because the `_output` property will be
|
|
||||||
visible in the instances of the class. We can't hide the property with `private`
|
|
||||||
because TypeScript won't let us look it up in `EntityOf` if we do so. So the
|
|
||||||
best we can do is document the fact that this should not be used, and throw in
|
|
||||||
the prefix so it stands out from regular properties.
|
|
Loading…
Reference in a new issue