Skip to content

Instantly share code, notes, and snippets.

@safareli
Last active June 9, 2021 19:03
Show Gist options
  • Save safareli/1403eb8ff269d2bfd2a56cb45e835d7c to your computer and use it in GitHub Desktop.
Save safareli/1403eb8ff269d2bfd2a56cb45e835d7c to your computer and use it in GitHub Desktop.
Mini decoder library in 60 lines.
type Decoder<I,O> = (_:I) => O
type TypeOf<D> = D extends Decoder<any, infer R> ? R: never
type InputOf<D> = D extends Decoder<infer R, any> ? R: never
const string: Decoder<unknown, string> = (x) => {
if (typeof x === "string") return x;
throw new Error("Expected string");
}
const number: Decoder<unknown, number> = (x) => {
if (typeof x === "number") return x;
throw new Error("Expected number");
}
const boolean: Decoder<unknown, boolean> = (x) => {
if (typeof x === "boolean") return x;
throw new Error("Expected boolean");
}
const _null: Decoder<unknown, null> = (x) => {
if (x === null) return x;
throw new Error("Expected null");
}
function object<T extends Record<string, Decoder<unknown, unknown>>>(
spec: T
): Decoder<unknown, { [P in keyof T]: TypeOf<T[P]> }> {
return (input) => {
const res = {} as any
for (const key in spec) {
if (key in (input as any)) {
res[key] = spec[key]((input as any)[key]) as any;
} else {
throw new Error(`Expected ${key} to be present`);
}
}
return res;
}
}
function array<T>(decoder: Decoder<unknown,T>): Decoder<unknown,T[]> {
return (input:unknown) => {
if (Array.isArray(input)) return input.map(x => decoder(x))
throw new Error("Expected array");
}
}
function union<MS extends [Decoder<any, any>, ...Array<Decoder<any, any>>]>(
...decoders: MS
): Decoder<InputOf<MS[number]>, TypeOf<MS[number]>> {
return (input) => {
for(const decoder of decoders) {
try{
return decoder(input)
} catch (_) {}
}
throw new Error(`Expected to decode using ${decoders.length} decoders but all failed`)
}
}
const userDecoder = object({
name: string,
pass: string,
age: number,
admin: boolean,
meta: union(string,number, boolean)
})
// : Decoder<unknown, {
// name: string;
// pass: string;
// age: number;
// admin: boolean;
// meta: string | number | boolean;
// }>
type User = TypeOf<typeof userDecoder>
const usersDecoder = array(userDecoder)
console.log(usersDecoder([{
name:"asd",
pass:"adads",
age: 1,
admin: false,
meta: false
},{
name: "bbbb",
pass:"cccc",
age: 1,
admin: true,
meta: "asd"
}]))
// try to decode something invalid
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment