interface ContactInfo {
address_1: string;
address_2?: string;
city: string;
state: string;
zip: string;
phone: number;
}
function isContactInfo(arg: any): arg is ContactInfo {
let valid = false;
const contactInfoFields: { readonly [key: string]: string[] } = {
address_1: [ 'string', ],
address_2: [ 'string', 'undefined' ],
city: [ 'string', ],
state: [ 'string', ],
zip: [ 'string', ],
phone: [ 'number' ],
};
if (typeof arg !== 'object') {
return valid;
}
for (const key in contactInfoFields) {
if(!contactInfoFields[key].includes(typeof arg[key]) || arg[key] === '' || Number.isNaN(arg[key])) {
return valid;
}
}
valid = true;
return valid;
}- io-ts[1]
This requires you to write your types as a runtime value, and allows you to extract static types from those, e.g.:
const ContactInfo = t.type({
address_1: t.string,
...
})
type ContactInfo = t.TypeOf<typeof ContactInfo>
You can validate objects with `ContactInfo.decode(someObject)`Note that we can have the same name for the type and value because they live in different namespaces. When you do ContactInfo.decode, you're calling the decode property of `const ContactInfo`. When you use `ContactInfo` in a type position (e.g. `function x(arg: ContactInfo)`), you're using the `type ContactInfo` declaration
- typescript-is[2]
This uses TypeScript's transformer API. You can use it with https://github.com/cevek/ttypescript. It generates validators for your interfaces/types/etc at compile-time.
type TypeOf<T> = (x: any) => x is (infer U) ? U : never;
I wrote a really small and simple validator library called narrows that works this way [1]. You could simplify your code like so: import { number, optional, record, string, TypeOf } from 'narrows';
const isContactInfo = record({
address_1: string,
address_2: optional(string),
city: string,
state: string,
zip: string,
phone: number
})
type ContactInfo = TypeOf<typeof isContactInfo>;
The source is less than 50 lines and super readable if you want to dig in. The libraries in the sibling comments are great too!Typescript-is looks like it might have an overall edge, though, because we can use it on the autogenerated types as well with almost no hassle.
instanceof
typeof
Array.isArray()
== null
And if you want to refine object types it gets even worse; you have to just check for properties that may or may not be there, which means those properties have to be rigidly distinguishable between object types in a union, etc. etc. Being able to do custom, rich object validation and give it first-class hooks into the type system sounds incredible.