Skip to content

Instantly share code, notes, and snippets.

@mekarthedev
Created April 18, 2023 12:26
Show Gist options
  • Save mekarthedev/8ca67e04dfcee2fc32a06293015bd005 to your computer and use it in GitHub Desktop.
Save mekarthedev/8ca67e04dfcee2fc32a06293015bd005 to your computer and use it in GitHub Desktop.
Hand made inference of a Typescript type from a custom json schema
type TypeFromString<T extends string> =
T extends "number" ? number
: T extends "string" ? string
: ["Invalid type string", T]
type InferPropRequired<PropSpec> = PropSpec extends { isRequired: false } ? false : true
type InferPropType<PropSpec> =
PropSpec extends { values: ReadonlyArray<infer Values> } ? Values
: PropSpec extends { type: infer T extends string } ? TypeFromString<T>
: ["Invalid prop spec", PropSpec]
type InferPayload<Payload> = {
[Prop in keyof Payload as (InferPropRequired<Payload[Prop]> extends true ? Prop : never)]: InferPropType<Payload[Prop]>
} & {
[Prop in keyof Payload as (InferPropRequired<Payload[Prop]> extends false ? Prop : never)]+?: InferPropType<Payload[Prop]>
}
type InferSchema<Schema> = {
[Event in keyof Schema]: Schema[Event] extends { payload: infer P } ? InferPayload<P> : ["Invalid payload for event", Event]
}
//
// Simulate `import json from "schema.json" as const`
// See https://github.com/microsoft/TypeScript/issues/32063
const json = {
"some_metric": {
"description": "Some event",
"payload": {
"index": {
"description": "Some index",
"type": "number"
},
"card_type": {
"description": "Card type",
"type": "number",
"values": [0, 1]
},
"phrase": {
"description": "Some phrase",
"type": "string",
"isRequired": false
},
}
},
"single_prop": {
"payload": {
"prop": {
"type": "number"
}
}
},
"optional": {
"payload": {
"prop": {
"type": "number",
"isRequired": false,
}
}
},
"union": {
"payload": {
"prop": {
"type": "number",
"values": [17, 23]
}
}
}
} as const
type Metrics = InferSchema<typeof json>
function sendMetric<Event extends keyof Metrics>(name: Event, payload: Metrics[Event]) {
// ???
}
sendMetric("some_metric", { index: 42, card_type: 0, phrase: "asdf" })
sendMetric("some_metric", { index: 42, card_type: 1 })
sendMetric(
//@ts-expect-error "unknown_name" is not assignable to [some of known names]
"unknown_name",
{}
)
sendMetric("single_prop",
{
//@ts-expect-error "string" is not assignable to "number"
prop: "not-a-number",
}
)
sendMetric("single_prop",
//@ts-expect-error "prop" is missing
{}
)
sendMetric("single_prop",
{
prop: 42,
//@ts-expect-error may only specify known props
unknown_prop: 42,
}
)
sendMetric("optional", {})
sendMetric("union", { prop: 17 })
sendMetric("union", {
//@ts-expect-error 42 is not assignable to [some of values]
prop: 42
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment