配列を map で変換する際、オブジェクト全体ではなく特定プロパティだけを抽出したい場面は多い。インラインで書くと冗長になりがちだが、プロパティ抽出関数を事前定義しておけば、map(propsExtractor('id', 'name')) のような簡潔な表現が可能になる。
コード
type LiteralObject = Record<string, unknown>
/** * オブジェクトから指定プロパティだけを抽出する関数を生成する */export function propsExtractor< Props extends LiteralObject, PropKey extends keyof Props,>( ...props: PropKey[]): (obj: Props) => Pick<Props, PropKey> { return (obj: Props) => { const result: Partial<Props> = {} for (const prop of props) { if (Object.hasOwn(obj, prop)) { result[prop] = obj[prop] } } return result as Pick<Props, PropKey> }}使用例
type User = { id: string name: string email: string role: string createdAt: Date}
const users: User[] = [ { id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin', createdAt: new Date() }, { id: '2', name: 'Bob', email: 'bob@example.com', role: 'user', createdAt: new Date() },]
// IDと名前だけを抽出const userSummaries = users.map(propsExtractor('id', 'name'))// => [{ id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }]
// 入れ子のmapでも簡潔に書けるconst departments = [ { name: 'Engineering', members: users }, { name: 'Sales', members: users },]
const summaries = departments.map(dept => ({ department: dept.name, members: dept.members.map(propsExtractor('id', 'name'))}))仕組み
propsExtractorは可変長引数で抽出したいプロパティ名を受け取る- クロージャーとして
(obj: Props) => Pick<Props, PropKey>型の関数を返す - 返された関数は
Object.hasOwnでプロパティの存在を確認してから抽出する - TypeScript の
Pickユーティリティ型で、返り値の型を正確に推論する
Object.hasOwn を使うことで、プロトタイプチェーン上のプロパティを誤って抽出しない安全性を確保している。
メリット
- 可読性が高い:
map(propsExtractor('id', 'name'))のように意図が明確 - 型安全:TypeScript の型推論で抽出後の型が正確に決まる
- 再利用可能:一度定義すれば、複数箇所で使い回せる
- 入れ子に強い:
mapByの多重適用でも読みやすさを維持できる
注意点
プロパティが存在しない場合は結果に含まれない(undefined にもならない)。すべてのプロパティを含めたい場合は Object.hasOwn のチェックを外す必要がある。また、抽出対象のプロパティが多い場合は、明示的にインターフェースを定義した方が保守性が高いこともある。
応用
- API レスポンスから必要なフィールドだけを抽出してクライアントに返す
- ログ出力時に機密情報を含むプロパティを除外する(逆フィルタ版を作る)
- Redux や Zustand のセレクターで部分的な状態を取り出す
- GraphQL のフィールド選択に似た部分取得ロジックを実装する
実務メモ
このスニペットは、TypeScript、JavaScript、Functional Programming、Utility の周辺で同じ操作や判定を毎回書きたくない時に向く。小さな補助として切り出しておくと、呼び出し側では意図だけを追いやすい。
逆に、分岐や前提条件が増えて責務が膨らむなら、1本のスニペットに詰め込まない方がよい。手順と helper を分けるか、役割ごとに切り出す方が保守しやすい。
hsb.horse