Skip to content

Instantly share code, notes, and snippets.

@NotNite
Last active June 20, 2024 01:20
Show Gist options
  • Save NotNite/2a0b9d36aaf35deaf207159caa29f1e6 to your computer and use it in GitHub Desktop.
Save NotNite/2a0b9d36aaf35deaf207159caa29f1e6 to your computer and use it in GitHub Desktop.
JSON literal verification in TypeScript type system
/*
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