Closed8
ネストしたオブジェクトの値を取り出す型安全な関数
ピン留めされたアイテム
最終的な成果物
type KeyOf<T> = keyof T & string;
type ObjectPath<T, K extends KeyOf<T>> = K extends K // @ts-ignore
? `${K}.${Path<T[K]>}`
: never;
type IsFinite<T extends any[]> = number extends T["length"] ? false : true;
type IsTuple<T> = T extends any[] ? IsFinite<T> : false;
type IsArray<T> = T extends any[]
? IsFinite<T> extends true
? false
: true
: false;
type TupleKeys<T extends any[]> = Exclude<keyof T, keyof any[]> & string;
type ArrayKey = `${number}`;
type TuplePath<T extends any[], K extends TupleKeys<T>> = K extends K
? `[${K}].${Path<T[K]>}`
: never;
type ArrayPath<T extends any[]> = `[${ArrayKey}].${Path<T[number]>}`;
type Path<T> = T extends any[]
? IsTuple<T> extends true
? `[${TupleKeys<T>}]` | TuplePath<T, TupleKeys<T>>
: IsArray<T> extends true
? `[${ArrayKey}]` | ArrayPath<T>
: never
: T extends object
? KeyOf<T> | ObjectPath<T, KeyOf<T>>
: never;
type ObjectValue<T, K> = K extends keyof T ? T[K] : never;
type TupleValue<T, K> = K extends `[${infer L}]`
? L extends keyof T
? T[L]
: never
: never;
type ArrayValue<T, K> = K extends `[${number}]`
? T extends any[]
? T[number]
: never
: never;
type NestedValue<T, K> = IsTuple<T> extends true
? TupleValue<T, K>
: IsArray<T> extends true
? ArrayValue<T, K>
: T extends object
? ObjectValue<T, K>
: never;
type Value<T, P> = P extends `${infer K}.${infer Rest}`
? Value<NestedValue<T, K>, Rest>
: NestedValue<T, P>;
const getValue = <T, P extends Path<T>>(x: T, path: P): Value<T, P> => {
return path.split(".").reduce((value: any, key) => {
if (!value) {
return undefined;
}
const index = key.match(/^\[(\d+)\]$/);
if (index) {
return value[parseInt(index[1])];
}
return value[key];
}, x);
};
オブジェクトのパスを取得する型
配列、タプルは一旦考えない
type KeyOf<T> = keyof T & string;
type ObjectPath<T, K extends KeyOf<T>> = K extends K
? `${K}.${Path<T[K]>}`
: never;
type Path<T> = T extends object ? KeyOf<T> | ObjectPath<T, KeyOf<T>> : never;
オブジェクトとパスを指定して値を取得する型
type Value<T, P> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Value<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
実装
const getPathValue = <T, P extends Path<T>>(x: T, path: P): Value<T, P> => {
return path
.split(".")
.reduce((value: any, key) => (value ? value[key] : undefined), x);
};
配列とタプルにも対応しよう
まずはユーティリティ
type IsFinite<T extends any[]> = number extends T["length"] ? false : true;
type IsTuple<T> = T extends any[] ? IsFinite<T> : false;
type IsArray<T> = T extends any[]
? IsFinite<T> extends true
? false
: true
: false;
type TupleKeys<T extends any[]> = Exclude<keyof T, keyof any[]> & string;
type ArrayKey = `${number}`;
ObjectPath
に相当するTuplePath
, ArrayPath
を定義し、Path
を再定義する
type TuplePath<T extends any[], K extends TupleKeys<T>> = K extends K
? `[${K}].${Path<T[K]>}`
: never;
type ArrayPath<T extends any[]> = `[${ArrayKey}].${Path<T[number]>}`;
type Path<T> = T extends any[]
? IsTuple<T> extends true
? `[${TupleKeys<T>}]` | TuplePath<T, TupleKeys<T>>
: IsArray<T> extends true
? `[${ArrayKey}]` | ArrayPath<T>
: never
: T extends object
? KeyOf<T> | ObjectPath<T, KeyOf<T>>
: never;
Value
についてもタプルと配列を考慮する
type ObjectValue<T, K> = K extends keyof T ? T[K] : never;
type TupleValue<T, K> = K extends `[${infer L}]`
? L extends keyof T
? T[L]
: never
: never;
type ArrayValue<T, K> = K extends `[${number}]`
? T extends any[]
? T[number]
: never
: never;
type NestedValue<T, K> = IsTuple<T> extends true
? TupleValue<T, K>
: IsArray<T> extends true
? ArrayValue<T, K>
: T extends object
? ObjectValue<T, K>
: never;
type Value<T, P> = P extends `${infer K}.${infer Rest}`
? Value<NestedValue<T, K>, Rest>
: NestedValue<T, P>;
タプル、配列も考慮した実装
const getValue = <T, P extends Path<T>>(x: T, path: P): Value<T, P> => {
return path.split(".").reduce((value: any, key) => {
if (!value) {
return undefined;
}
const index = key.match(/^\[(\d+)\]$/);
if (index) {
return value[parseInt(index[1])];
}
return value[key];
}, x);
};
このスクラップは2024/01/14にクローズされました