Last active
June 9, 2021 19:03
-
-
Save safareli/1403eb8ff269d2bfd2a56cb45e835d7c to your computer and use it in GitHub Desktop.
Mini decoder library in 60 lines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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