Skip to content

Instantly share code, notes, and snippets.

@iczero
Created March 14, 2024 18:46
Show Gist options
  • Save iczero/d1bbf14395d82af9e6bdb13b1051ce50 to your computer and use it in GitHub Desktop.
Save iczero/d1bbf14395d82af9e6bdb13b1051ce50 to your computer and use it in GitHub Desktop.
Raising the Ceiling(TM)
type Value = ObjectValue | ArrayValue | StringValue | NumberValue | BooleanValue;
interface ObjectValue {
type: 'object';
values: Record<string, Value>;
optional?: boolean;
}
interface ArrayValue {
type: 'array',
element: Value;
optional?: boolean;
}
interface StringValue {
type: 'string';
optional?: boolean;
}
interface NumberValue {
type: 'number';
optional?: boolean;
}
interface BooleanValue {
type: 'boolean',
optional?: boolean;
}
type PrimitiveValues = StringValue | NumberValue | BooleanValue;
type MapOptional<V extends { optional?: boolean }, T> =
V extends { optional: true }
? T | undefined
: T;
type MapPrimitive<T extends PrimitiveValues> =
T extends StringValue
? MapOptional<T, string>
: T extends NumberValue
? MapOptional<T, number>
: T extends BooleanValue
? MapOptional<T, boolean>
: never; // should never hit this
type MapComplex<T extends Value> =
T extends PrimitiveValues
? MapPrimitive<T>
: T extends { type: 'array', element: infer E extends Value }
? MapOptional<T, MapComplex<E>[]>
: T extends { type: 'object', values: infer E extends Record<string, Value> }
? MapOptional<T, MergeOptional<{ [Key in keyof E]: MapComplex<E[Key]> }>>
: never;
type NotUndefined = string | number | boolean | symbol | object | null;
type IsOptional<T, V> = T extends NotUndefined ? never : V;
type OptionalKeys<T extends {}> =
keyof { [Key in keyof T as IsOptional<T[Key], Key>]: true };
type MergeOptional<T extends {}> =
Omit<T, OptionalKeys<T>> & Partial<T>;
type Derp = MapComplex<{
type: 'object',
values: {
test: {
type: 'object',
values: {
array: {
type: 'array', element: { type: 'string' },
}
memes: { type: 'number' },
},
},
optional: { type: 'boolean', optional: true }
},
}>;
let test: Derp = {
test: {
array: ['hello', 'world'],
memes: 4
},
}
// slight problem: MergeOptional<T> appears to be complex enough that TypeScript
// returns the type instead of the object in errors.
// it still works though!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment