Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save steinerkelvin/67e05fe1a470420696a6e73e83801d8f to your computer and use it in GitHub Desktop.
Save steinerkelvin/67e05fe1a470420696a6e73e83801d8f to your computer and use it in GitHub Desktop.
Typescript typings and utility functions for Rust's serde / serde_json serialized enums (tagged unions)
// Some magic from
// https://stackoverflow.com/questions/65750673/collapsing-a-discriminated-union-derive-an-umbrella-type-with-all-possible-key
// I replaced `undefined` usages with the placeholder below so we can exclude
// it properly on the last definition.
declare const PLACEHOLDER: unique symbol
type Placeholder = typeof PLACEHOLDER
// Magic as far as I'm concerned.
// Taken from https://stackoverflow.com/a/50375286/3229534
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
// This utility lets T be indexed by any key
type Indexify<T> = T & { [str: string]: Placeholder; }
// To make a type where all values are undefined, so that in AllUnionKeys<T>
// TS doesn't remove the keys whose values are incompatible, e.g. string & number
type UndefinedVals<T> = { [K in keyof T]: undefined }
// This returns a union of all keys present across all members of the union T
type AllUnionKeys<T> = keyof UnionToIntersection<UndefinedVals<T>>
// Where the (rest of the) magic happens ✨
type AllFields<T> = { [K in AllUnionKeys<T> & string]: Exclude<Indexify<T>[K], Placeholder> }
// End of Magic
// Enum TS library
// ===============
type Key = string | number | symbol
type Variant<Tag extends Key, T> = { [x in Tag]: T }
type TagVariants<T> = { [K in keyof T]: Variant<K, T[K]> }
type Enum<T> = TagVariants<T>[keyof T]
type FlatVariant<Tag extends Key, T> = { $: Tag, val: T }
type FlatEnum<T> = { [K in keyof T]: FlatVariant<K, T[K]> }[keyof T]
type VariantsFrom<T> = AllFields<T>
type FlatEnumFrom<T> = FlatEnum<VariantsFrom<T>>
function flatten_enum<T>(value: T): FlatEnumFrom<T> {
for (let key in value) {
if (key !== '$') {
let _result = value[key]
let result = { $: key, val: _result }
return result as unknown as FlatEnumFrom<T>;
}
}
throw new Error(`Variant is empty: '${value}'.`)
}
const flatten_enum_f =
<V,>(value: V) =>
<R,>(f: ((v: FlatEnumFrom<V>) => R)) => f(flatten_enum(value))
type MatchDict<V, R = void> = { [tag in keyof V]: (v: V[tag]) => R }
const match =
<E,>(value: E) =>
<R,>(matcher: MatchDict<VariantsFrom<E>, R>): R =>
flatten_enum_f(value)(({ $, val }) => {
let arm = matcher[$]
return arm(val)
})
const map_enum = <E,>(value: E) => <R,>(matcher: MatchDict<VariantsFrom<E>, R>): R =>
flatten_enum_f(value)(({$, val}) => {
let arm = matcher[$]
return arm(val)
})
const if_let =
<E,>(value: E) =>
<K extends keyof VariantsFrom<E>>(tag: K) =>
<R,>(th: (v: VariantsFrom<E>[K]) => R) =>
(el: () => R): R =>
flatten_enum_f(value)(({ $, val }) => ($ === tag ? th(val) : el()))
// Example Enum
// ============
type Stuff = Enum<Stuff_Variants>
interface Stuff_Variants {
Color: Color
BW: BW
}
interface Color {
r: number
g: number
b: number
}
interface BW {
val: boolean
}
function test() {
let a: Stuff = { Color: { r: 10, g: 20, b: 35 } }
let b: Stuff = { BW: { val: true } }
let all: Stuff[] = [a, b]
for (let _elem of all) {
console.log(`Matching with flatten_enum...`)
let variant = flatten_enum(_elem)
switch (variant.$) {
case "Color":
let color = variant.val
console.log(color.r + color.g + color.b)
break;
case "BW":
let bw = variant.val
console.log(bw.val)
}
}
for (let elem of all) {
console.log(`Matching with flatten_enum_f...`)
flatten_enum_f(elem)(({$, val}) => {
switch ($) {
case "Color":
console.log(val.r + val.g + val.b)
break
case "BW":
console.log(val.val)
break
}
}
)
}
for (let elem of all) {
console.log(`Matching with map_enum...`)
match(elem)({
Color: (color) => console.log(color.r + color.g + color.b),
BW: (bw) => console.log(bw.val),
})
}
for (let elem of all) {
console.log(`Matching with if_let...`)
if_let(elem)("Color")
((color) => console.log(`This is color: R:${color.r} G:${color.g} B:${color.b}`))
(() => console.log(`This is not color: ${elem}`))
}
}
test()
@steinerkelvin
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment