Type Dungeon
TypeScript code exercise.
Exercise
array-to-union
Difficulty:
Play this with TypeScript playground !
// Complete the following `toUnion` type to match the following excepcted condition.
type toUnion<T extends ArrayLike<any>> = unknown;
// Expected
const keys = ["age", "firstName", "lastName"] as const;
type Keys = toUnion<typeof keys>; // should be "age" | "firstName" | "lastName"
json-type
Difficulty:
Play this with TypeScript playground !
// Complete the `JSONLike` type. It represents type of values `JSON.parse()` returns.
type JSONLike = unknown;
const v1: JSONLike = {};
const v2: JSONLike = [];
const v3: JSONLike = null;
const v4: JSONLike = {
flag: false,
name: "name",
num: 100,
arr: [{ hoge: "hoge" }],
optionParam: null,
};
// The following RHSs can not be serialized to JSON
const v5: JSONLike = () => 1; // should be error
const v6: JSONLike = { fn: () => 1 }; // should be error
to-strict
Difficulty:
Play this with TypeScript playground !
// Edit the right hand side to match the following expected type conditions.
type Strict<T> = unknown;
// Expected
type SomeObjectType = {
name?: string;
age?: number;
};
declare const strictObj: Strict<SomeObjectType>;
const userName: string = strictObj.name; // typeof name should not be undefined / null
const age: number = strictObj.age; // typeof age should not be undefined / null
combine-latest
Difficulty:
Play this with TypeScript playground !
// Complete the following `combineLatest` function declaration.
//
// This function is used with RxJS API. See https://rxjs.dev/api/index/function/combineLatest .
interface Observable<T> {
subscribe(cb: (v: T) => void): void;
}
declare function combineLatest(...args: Observable<any>[]): Observable<unknown[]>;
// Expected
declare const s1: Observable<string>;
declare const s2: Observable<number>;
declare const s3: Observable<boolean>;
combineLatest(s1, s2, s3).subscribe(([v1, v2, v3]) => {
// v1 should be string
const result1: string = v1;
// v2 should be number
const result2: number = v2;
// v3 should be boolean
const result3: boolean = v3;
});
convert-object-keys
Difficulty:
Play this with TypeScript playground !
// Complete the `CamelizedObject` type operator to match the following expected condition.
//
// Imagine object from response of Web API implemented by language which prefer "snake-cased" variable name (e.g. Ruby).
// But you want to convert the response objects to objects whose keys are "camel-cased" strings.
//
// So you need to this type operator to represents the type of the converted objects.
type CamelizedObject<T> = unknown;
// Expected
type GeneratedResponseType = {
id: string;
first_name: string;
last_name: string;
created_at: number;
};
const responseObj: GeneratedResponseType = {
id: "10000",
first_name: "Yosuke",
last_name: "Kurami",
created_at: 1606389092229,
};
const converted: {
id: string;
firstName: string;
lastName: string;
createdAt: number;
} = convertKeysFromSnake2Camel(responseObj);
// The following function converts keys in input object to camel-cased string.
function convertKeysFromSnake2Camel<T extends Record<string, any>>(obj: T): CamelizedObject<T> {
const keys = Object.keys(obj) as string[];
return keys.reduce((acc, k) => {
const camelizedKey = snake2Camel(k);
return {
...acc,
[camelizedKey]: obj[k],
};
}, {} as any);
}
// hoGe -> Hoge
function capitalize(input: string) {
return input.slice(0, 1).toUpperCase() + input.slice(1).toLowerCase();
}
// hoge_foo -> HogeFoo
function snake2Pascal(input: string) {
return input.split("_").map(capitalize).join("");
}
// hoge_foo -> hogeFoo
function snake2Camel(input: string) {
const p = snake2Pascal(input);
return p.slice(0, 1).toLowerCase() + p.slice(1);
}
curry
Difficulty:
Play this with TypeScript playground !
// Complete the function type to match the following expected type condition.
declare function curry(fn: Function): any;
// Expected
const add = (a: number, b: number) => a + b;
const bound = curry(add)(1);
// @ts-expect-error `bound` should accept 1 argument
bound();
// @ts-expect-error `bound` should accept 1 argument
bound(100, 100); // should throw error
const value: number = bound(100);
diff
Difficulty:
Play this with TypeScript playground !
// Complete `$Diff` type to match the following condition.
// FYI, Flow type has the same name utility type.
type $Diff<A, B> = any;
type Props = {
id: number;
title: string;
tags: string[];
};
const defaultProps = {
tags: [],
};
type RequiredProps = $Diff<Props, typeof defaultProps>;
const props1: RequiredProps = {
id: 100,
title: "my post",
};
const props2: RequiredProps = {
id: 100,
title: "my post",
tags: ["ts"],
};
// @ts-expect-error
const props3: RequiredProps = {
id: 100,
tags: [],
};
// @ts-expect-error
const props4: RequiredProps = {
title: "my post",
};
only-required-keys
Difficulty:
Play this with TypeScript playground !
// Replace the RHS of the following `RequiredKeys` type to match the expected section.
type RequiredKeys<T> = keyof T;
// Expected
type User = {
id: number;
lastName: string;
firstName: string;
middleName?: string;
};
const validKeys: RequiredKeys<User>[] = ["id", "lastName", "firstName"];
// @ts-expect-error 'middleName' is an optional key in User type
const invalidKeys: RequiredKeys<User>[] = ["middleName"];
union-to-intersection
Difficulty:
Play this with TypeScript playground !
// Complete the following `U2I` type to match the following excepcted condition.
// `U2I` converts from union type to intersection type.
type U2I<T> = unknown;
type User = {
id: string;
name: string;
age: number;
};
type Post = {
id: string;
name: string;
body: string;
};
type UserOrPost = User | Post;
// @ts-expect-error
const x1: U2I<UserOrPost> = {
id: "HOGE",
name: "hoge",
age: 20,
};
// @ts-expect-error
const x2: U2I<UserOrPost> = {
id: "FOO",
name: "foo",
body: "foo body",
};
const x3: U2I<UserOrPost> = {
id: "BAR",
name: "bar",
age: 20,
body: "bar body",
};
cellular-automaton
Difficulty:
Play this with TypeScript playground !
// Complete the following type `CellularAutomaton<N>` using this `Rule` type.
// The `Rule` type represents a rule of one-dimensional cellular automan, a.k.a. Wolfram code "110".
// Read https://en.wikipedia.org/wiki/Rule_110 if you want more detail.
//
// `CellularAutomaton<N>` should give the N-th generation state when starting `[1]`.
// For example, `CellularAutomaton<1>` provides `[1,1,0]` because `[0,0,1]` generates `1`, [0,1,0] does `1`, and `[1,0,0]` does `0`.
type B = 1 | 0;
// prettier-ignore
type Rule110<T extends [B, B, B]> =
T extends [1, 1, 1] ? 0 :
T extends [1, 1, 0] ? 1 :
T extends [1, 0, 1] ? 1 :
T extends [1, 0, 0] ? 0 :
T extends [0, 1, 1] ? 1 :
T extends [0, 1, 0] ? 1 :
T extends [0, 0, 1] ? 1 :
T extends [0, 0, 0] ? 0 :
never;
type Rule<T extends [B, B, B]> = Rule110<T>;
type CellularAutomaton<N extends number> = unknown;
// Expected
const s0: CellularAutomaton<0> = [1] // prettier-ignore
const s1: CellularAutomaton<1> = [1,1,0] // prettier-ignore
const s2: CellularAutomaton<2> = [1,1,1,0,0] // prettier-ignore
const s3: CellularAutomaton<3> = [1,1,0,1,0,0,0] // prettier-ignore
const s4: CellularAutomaton<4> = [1,1,1,1,1,0,0,0,0] // prettier-ignore
const s5: CellularAutomaton<5> = [1,1,0,0,0,1,0,0,0,0,0] // prettier-ignore
const s6: CellularAutomaton<6> = [1,1,1,0,0,1,1,0,0,0,0,0,0] // prettier-ignore
randomize
Difficulty:
Play this with TypeScript playground !
// Complete the `Random` type.
type Random = unknown;
declare const random: Random;
const a = { value: 1 } as const;
const b = { value: 2 } as const;
const c = { value: 3 } as const;
// Expected:
// The `random` return type should be assignable this type annotation.
const valueAB = random(a, b);
const valueABC = random(a, b, c);
const x: { readonly value: 1 } | { readonly value: 2 } = valueAB;
const y: { readonly value: 1 } | { readonly value: 2 } | { readonly value: 3 } = valueABC;
How to create new exercise
Read CONTRIBUTING.md.
LICENSE
MIT