Discussion: Compile-Time TypeScript type validation
See original GitHub issueThe current type definitions located at @types/superstruct
are good. However, they do not check the type of your struct at compile time, leading to poorer autocomplete as the shape of your struct is not known to TypeScript. A major reason for this is TypeScript’s widening of string literals in non-constant contexts. For example, the type of the following struct definition:
const def = {
someProp: 'number'
}
is known as:
{someProp: string}
rather than
{someProp: 'number'}
This makes it near impossible to write any kind of mapped type that would be valid to your struct. In my research, I have discovered two possible solutions rather than just defaulting to any
: a shape-only type and more specific struct definitions.
With a shape only type, upon constructing a struct with this definition:
const definition = {
someProp: 'number',
someObject: {
anotherProp: 'boolean?'
}
}
you would receive a validation function that has the following type:
(objectToTest: any) => {someProp: any, someObject: {anotherProp: any}}
This would at least enable autocomplete with your struct’s properties, preventing typos at compile time. This does not protect against a property possibly being null (for example, struct.someObject.anotherProp
can be null in the above example) nor does it actually enforce the types (i.e. number) you’ve already defined in your struct.
The other option is to use string literal narrowing in your struct definitions, or const enums. For example, if we define a const enum like this:
const enum Types {
String = 'string',
OptionalString = 'string?'
Number = 'number',
OptionalNumber = 'number?'
}
it becomes possible to write a mapped type such that a struct definition such as this:
const definition = {
someProp: Types.Number
}
returns a struct function with the following type:
(objectToTest: any) => {someProp: number}
This also works if you force the compiler to not widen the type. The following definition will produce the same type:
const definition = {
someProp: 'number' as 'number'
}
The nice thing about using a const enum is that is does not force this library to switch to TypeScript, rather, the enum constants themselves are inlined upon compilation rather than using a static class.
I’m looking here to start a discussion about how to implement such a feature in the typings, and whether any of these solutions I’ve thought of so far are any good.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:7
- Comments:16 (2 by maintainers)
Top GitHub Comments
I’ve taken a different route to this and written a TypeScript transform plugin that exports a
superstruct<T>
method. The plugin itself will transform that method and interface type and automatically generate a superstruct schema from the interface. So it pretty much takes all of the worry away from type checking the schemas.Will be released in the coming weeks.
I was looking for / building the same thing and since I like @ianstormtaylor work, decided to take a crack at adding types to superstruct. This won’t handle the custom types as of yet (though I think with a bit of work it’s possible), but for all the basic types, I think this works. Good for typing API calls with incoming unknown types for example:
And here’s a simple jest unit test that passes:
And to show that it works, here’s the typing information from VSCode:
Maybe I’ll create an NPM package for this…