Last active
June 20, 2024 01:20
-
-
Save NotNite/2a0b9d36aaf35deaf207159caa29f1e6 to your computer and use it in GitHub Desktop.
JSON literal verification in TypeScript type system
This file contains 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
/* | |
JSON literal verification in TypeScript type system (version 3.1 for workgroups) | |
now powerd by tail recursion (you can blame @ackwell for telling me to and | |
@s5bug for doing it) | |
*/ | |
type HEX_CHARS = | |
| "0" | |
| "1" | |
| "2" | |
| "3" | |
| "4" | |
| "5" | |
| "6" | |
| "7" | |
| "8" | |
| "9" | |
| "a" | |
| "b" | |
| "c" | |
| "d" | |
| "e" | |
| "f" | |
| "A" | |
| "B" | |
| "C" | |
| "D" | |
| "E" | |
| "F"; | |
type EXCLUDE_CHARS = | |
| '"' | |
| "\\" | |
| "\u0000" | |
| "\u0001" | |
| "\u0002" | |
| "\u0003" | |
| "\u0004" | |
| "\u0005" | |
| "\u0006" | |
| "\u0007" | |
| "\u0008" | |
| "\u0009" | |
| "\u000A" | |
| "\u000B" | |
| "\u000C" | |
| "\u000D" | |
| "\u000E" | |
| "\u000F" | |
| "\u0010" | |
| "\u0011" | |
| "\u0012" | |
| "\u0013" | |
| "\u0014" | |
| "\u0015" | |
| "\u0016" | |
| "\u0017" | |
| "\u0018" | |
| "\u0019" | |
| "\u001A" | |
| "\u001B" | |
| "\u001C" | |
| "\u001D" | |
| "\u001E" | |
| "\u001F"; | |
type ZeroOrMore<M extends string, T extends string> = M extends `${T}${infer R}` | |
? R extends ZeroOrMore<R, T> | |
? M | |
: never | |
: M extends `` | |
? M | |
: never; | |
type SUBTRACT_TABLE = { | |
readonly "1": 0; | |
readonly "2": 1; | |
readonly "3": 2; | |
readonly "4": 3; | |
}; | |
type Subtract<T extends number> = `${T}` extends keyof SUBTRACT_TABLE | |
? SUBTRACT_TABLE[`${T}`] | |
: never; | |
type CODEPOINT<S extends string, C extends number = 4> = C extends 0 | |
? S extends `` | |
? S | |
: never | |
: S extends `${infer H}${infer R}` | |
? H extends HEX_CHARS | |
? R extends CODEPOINT<R, Subtract<C>> | |
? S | |
: never | |
: never | |
: never; | |
type WHITESPACE = "\u0020" | "\u000A" | "\u000D" | "\u0009"; | |
type BACKSLASH = "\\"; | |
type ESCAPE = "\\" | '"' | "/" | "b" | "f" | "n" | "r" | "t" | "u"; | |
type CHAR = string; | |
type JsonBoolean = "true" | "false"; | |
type JsonNull = "null"; | |
type JsonLiteral = JsonBoolean | JsonNull; | |
type JsonStringContents<T extends string> = T extends `${BACKSLASH}u${infer U}` | |
? U extends `${CODEPOINT<U>}${infer R}` | |
? R extends JsonStringContents<R> | |
? T | |
: never | |
: never | |
: T extends `${BACKSLASH}${ESCAPE}${infer U}` | |
? U extends JsonStringContents<U> | |
? T | |
: never | |
: T extends `${CHAR}${infer U}` | |
? T extends `${EXCLUDE_CHARS}${infer U}` | |
? never | |
: U extends JsonStringContents<U> | |
? T | |
: never | |
: T extends `` | |
? T | |
: never; | |
type JsonString<T extends string> = T extends `"${infer U}"` | |
? U extends JsonStringContents<U> | |
? T | |
: never | |
: never; | |
type JsonNumber<T extends string> = T extends `${number}` | |
? T | |
: T extends `${number}.${number}` | |
? T | |
: T extends `-${number}` | |
? T | |
: T extends `-${number}.${number}` | |
? T | |
: never; | |
type JsonElement<T extends string> = T extends JsonValue<T> | |
? T | |
: T extends `${JsonValue<infer H>},${infer R}` | |
? R extends JsonElement<R> | |
? T | |
: never | |
: never; | |
type JsonElements<T extends string> = T extends ZeroOrMore<T, WHITESPACE> | |
? T | |
: JsonElement<T>; | |
type JsonArray<T extends string> = T extends `[${infer U}]` | |
? U extends JsonElements<U> | |
? T | |
: never | |
: never; | |
type JsonMember<T extends string> = T extends `${JsonString< | |
infer H | |
>}:${infer R}` | |
? R extends JsonValue<R> | |
? T | |
: never | |
: never; | |
type JsonMembers<T extends string> = T extends `${WHITESPACE}${infer U}` | |
? U extends JsonMember<U> | |
? T | |
: never | |
: T extends `${infer U}${WHITESPACE}` | |
? U extends JsonMember<U> | |
? T | |
: never | |
: T extends `${JsonMember<infer H>},${infer R}` | |
? R extends JsonMembers<R> | |
? T | |
: never | |
: T extends JsonMember<T> | |
? T | |
: never; | |
type JsonObject<T extends string> = T extends `{${infer U}}` | |
? U extends JsonMembers<U> | |
? T | |
: never | |
: never; | |
type JsonValue<T extends string> = T extends `${WHITESPACE}${infer U}` | |
? U extends JsonValue<U> | |
? T | |
: never | |
: T extends `${infer U}${WHITESPACE}` | |
? U extends JsonValue<U> | |
? T | |
: never | |
: T extends JsonString<T> | |
? T | |
: T extends JsonArray<T> | |
? T | |
: T extends JsonObject<T> | |
? T | |
: T extends JsonNumber<T> | |
? T | |
: T extends JsonLiteral | |
? T | |
: never; | |
function asValue<T extends string>(j: JsonValue<T>) {} | |
asValue(`"Hello, world!"`); | |
asValue(`"\\u0000"`); | |
asValue(`{"hello": "world"}`); | |
asValue(`["hello", "world"]`); | |
asValue(`{"hello": {"world": "!"}}`); | |
asValue(`[{"hello": "world"}]`); | |
asValue(`{"hello": ["world"]}`); | |
asValue(`true`); | |
asValue(`false`); | |
asValue(`null`); | |
asValue(`3.141592653589793`); | |
asValue(`-3.141592653589793`); | |
// @ts-expect-error | |
asValue(`"Hello,\nworld!"`); | |
// @ts-expect-error | |
asValue(`"\\u000g"`); | |
export {}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment