TypeScriptを使っていて、コピー先のオブジェクトに存在するプロパティのみを、コピー元からコピーしたいということがありました。
「Object.assignじゃダメなの?」と思われるかもしれないですが、Object.assignだとコピー先にないプロパティまでコピーされてしまいます。
const target = { a: 1, b: 2 } const source = { b: 4, c: 5 } Object.assign(target, source) console.log(target) // { a: 1, b: 4, c: 5 }となるが、{ a: 1, b: 4 }となってほしい
これをTypeScriptで実現する方法について調べたのでメモとして残しておきます。
ちなみに、JavaScriptならすぐに書けます。
// 存在するプロパティの値のみコピーする function copySameProperties(target, source){ for(const key in source){ if(key in target){ target[key] = source[key] } } return target } const target = { a: 1, b: 2 } const source = { b: 4, c: 5 } copySameProperties(target, source) console.log(target) // {a: 1, b: 4}
ここまで書けるなら後は、TypeScriptで型を指定すればいいだけかと思ったのですが、これがなかなか難しい…。最終的に、下記のように書くことでうまくいきました。
function copySameProperties<T extends object, U extends object>( target: T, source: U ): T { for (const key in source) { if (key in target) { target[key as unknown as keyof T] = source[key] as unknown as T[keyof T]; } } return target; } const target = { a: 1, b: 2 } const source = { b: 4, c: 5 } copySameProperties(target, source) console.log(target) // {a: 1, b: 4}
ポイントとして下記の3つです。
1.引数のtargetとsourceがオブジェクトということを明示するため、ジェネリクスに「extends object」という制約をいれる
参考:型引数の制約 | TypeScript入門『サバイバルTypeScript』
2.keyの型が「Extract
参考:型アサーション「as」(type assertion) | TypeScript入門『サバイバルTypeScript』
3.2と同様に、source[key]の型がtarget[key]か判別できないため、「T[keyof T]」と型指定をいれている。
「extends object」とか、「as unknown as」とか、TypeScriptに慣れていないとなかなかでてこない書き方で、迷いました…。これぐらい、すんなり書けるようになりたいです。
ちなみに、ChatGPTでJavaScriptのコードを渡して、TypeScriptに変換してと伝えると、第2引数のsourceは「Partial
function copySameProperties<T extends object>( target: T, source: Partial<T> ): T { for (const key in source) { if (key in target) { target[key as keyof T] = source[key]!; } } return target; } const target = { a: 1, b: 2 } const source = { b: 4, c: 5 } copySameProperties(target, source) console.log(target) // {a: 1, b: 4}
※ChatGPTの提案では「extends object」が無かったり、「key as any」とされてしまったので少し書き換えてます。
コメント