🦄

Object.keys() が string[] を返してくる問題を解決する3つの方法

2022/07/02に公開

Object.keys() が string[] を返してくる問題

Object.keys() で取り出した配列の型は string[] なので
別の配列型に割り当てるとエラーになります

const FRUITS = {
  'PEACH': 'モモ',
  'BANANA': 'バナナ',
} as const

type Fruits = keyof typeof FRUITS // "PEACH" | "BANANA"

const fruits: Fruits[] = Object.keys(FRUITS) // エラー
// Type 'string[]' is not assignable to type '("PEACH" | "BANANA")[]'.
//   Type 'string' is not assignable to type '"PEACH" | "BANANA"'.

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAYgJQKoEkAqBlGBeGBvAKBhgHIAFAUQEEBhACRIC5TAjhhZIBojSAhKgOQFUmpQAsMgKYZxnAgF8YAQwgxQkKAQJQAngAcApvABOAVwCWUJbgDWuzSABmMLXvvxk6DOpXQYd42YjMcH7mANoAujgwAPIARgBWusBQAHTWmhAAFIiomACUMAD0BTCAFQyAlwyAPwwERTBoOvok0AYmYADm4SQwJkpgILCKECatYPIxADb6UCCO9aQZAESUtHTzMAA+MPN8goLzuR3J1cXEdXqkTS2tnd0wvf0Qg8OjE47TTg2L1PSrG1tCuyRDkA

解決策1: Type Assertion(型アサーション)を使う

一番手っ取り早い方法です
Type Assertion(型アサーション)を使うことに抵抗がなければ、これで良いと思います

const fruits: Fruits[] = Object.keys(FRUITS) as Fruits[]
console.log(fruits) // ['PEACH', 'BANANA']

Type Assertion(型アサーション)は強力すぎる

人は誰しも間違うものです
もしも、間違った型で上書きしていたら、どうなるでしょうか?

例えば……

以下のようなに Fruits に加えて Animal の型が増えたとします

const FRUITS = {
  'PEACH': 'モモ',
  'BANANA': 'バナナ',
} as const

const ANIMAL = {
  'DOG': 'いぬ',
  'CAT': 'ねこ',
} as const

type Fruits = keyof typeof FRUITS // "PEACH" | "BANANA"
type Animal = keyof typeof ANIMAL // "DOG" | "CAT"

与える引数を間違えて書いた場合

以下のように Object.keys() に与える引数を間違えて書いてしまうと
配列の値と型が異なり、危険な状態となります

const animals: Animal[] = Object.keys(FRUITS) as Animal[]
console.log(animals) // ['PEACH', 'BANANA'] 危険!

解決策2: ラッパー関数を作り型で縛る

const keys = <T extends {}, K extends keyof T>(o: T) => Object.keys(o) as K[]
const fruits: Fruits[] = keys(FRUITS)
console.log(fruits) // ['PEACH', 'BANANA']

解決策1よりもまだましな点として
K extends keyof T によって返り値の配列型 K[]
引数のオブジェクト型 T のプロパティである確認をしています

解決策1の問題は解決されたのか?

与える引数を間違えて書いた場合

解決策1と同じように、ラッパー関数に与える引数を間違えて書いてみます

const animals: Animal[] = keys(FRUITS) // エラー
// Type '("PEACH" | "BANANA")[]' is not assignable to type '("DOG" | "CAT")[]'.
//   Type '"PEACH" | "BANANA"' is not assignable to type '"DOG" | "CAT"'.
//     Type '"PEACH"' is not assignable to type '"DOG" | "CAT"'.

解決策2は、エラーになるので安全です

解決策3: ユーザー定義型ガードを filter 関数に与える

宗教的な理由により、どうしてもType Assertion(型アサーション)を使いたくない場合に使います

const fruits: Fruits[] = Object.keys(FRUITS)
  .filter((key): key is keyof typeof FRUITS => key in FRUITS)
console.log(fruits) // ['PEACH', 'BANANA']

filter 関数にユーザー定義型ガードを与え
key in FRUITS で、オブジェクトのプロパティである確認をして
型述語 key is keyof typeof FRUITS で、返り値の型を絞り込んでいます

解決策3は安全なのか?

与える引数を間違えて書いた場合

// Object.keys()に与える引数を間違えている
const animals1: Animal[] = Object.keys(FRUITS) // エラー
  .filter((key): key is keyof typeof FRUITS => key in FRUITS)
// Type '("PEACH" | "BANANA")[]' is not assignable to type '("DOG" | "CAT")[]'.

// Object.keys()に与える引数を間違えている、is keyof typeofで使う定数が間違っている
const animals2: Animal[] = Object.keys(FRUITS) // エラー
  .filter((key): key is keyof typeof FRUITS => key in ANIMAL)
// Type '("PEACH" | "BANANA")[]' is not assignable to type '("DOG" | "CAT")[]'.

// Object.keys()に与える引数を間違えている、key inで使う定数が間違っている
const animals3: Animal[] = Object.keys(FRUITS)
  .filter((key): key is keyof typeof ANIMAL => key in FRUITS)
console.log(animals3) // ['PEACH', 'BANANA'] 危険!

// Object.keys()に与える引数を間違えている、filter評価関数で使う定数が間違っている
const animals4: Animal[] = Object.keys(FRUITS)
  .filter((key): key is keyof typeof ANIMAL => key in ANIMAL)
console.log(animals4) // []

filter 関数に与えるユーザー定義型ガードの書き方次第で危険な場合もあります

間違えて書く可能性が増えているのでは?

はい

ラッパー関数化

すみません、上手く書けませんでした……

結論どれを使うと良いのか?

絶対に間違えない人は、解決策1で良いと思います

試してみる

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAYgJQKoEkAqBlGBeGBvAKBhgHIAFAUQEEBhACRIC5TAjhhZIBojSAhKgOQFUmpQAsMgKYZxnAgF8YAQwgxQkKAQIroMASgCyVADI583EgBEA8gHERJQCIMgGwZpxEjSppbgWwZAygzS5i5XBodSgATwAHAFN4ACcAVwBLKCVcAGtI0JAAMxgwqOz4ZHQsAHoSmAAiSlo6CpgAH0q+QUEKgjzoqjAEgFt5ABtjdMycjoKdfSMyyssrOsaKtzQ2gmmAYg21mEBjyMAvG0A1bwBGGE219U1YLPikiEPmOGvkgG0AXWMLACMAK0jgKAA6YYQAAUiFQmAAlAolA9Es8Xuppp8fn9ARkQRDANYMgDg5QASDIBohkAqPqAB1NAEkMgGTUwCqCbjAGYMdnxGiCsHk3T6-VuzC6vQGr3e31+AKBoKKkOh2lZPLe00A+dqAaUNAPYMT3I1HonF4QkEJBegCsGQB2DIBzBkAjsqALzTzkEQP1Iv9+iAAObAkh7I5qvAwK5w24cBQS9nHGQQxHlU47A4AJhOmzNqhgQOMAB40DBIgAPKCRMAAEyUeBkXoA0knU+mszGMgU0AA+YEgZhoKHYCswZECtGhEEgKEBPOvKNad03UP3R4QXlpdHC8EYAOrcpCrF4olkqm0+mM6Ms7nswfizej0ttifFKHTQAVDIBLhkAPwy9i1Wm32x1hl1u4ehr0btkQcP+wMRjYh-YAMy-mca59sOAFDh6e7NqiQpgke3D-FkCT9GmMTAsCwwQswwwwAkSjDAUYw5PBmA4I2uEJGAhSTtOSL8rB47zgSJIUtSdIMhc3qbhAAHyJyPrQQxgrjqRU4wKel6IchqGROhmEZNh+54QRZajBEkQFGJ5HKVRNEITOTbCa2GI4ixS7sfSgCADPh+5ERp2T6oA-vKAGIMgBZ2sSgAyDFSgDGDBxoHMj6vEfAJu5vLgMEiQeYnHuU55XsQSEoWhGFYThGQqXZ6n5CRIpYA2unURMhh0eUkUmcCzGLmxK74lZlFgM57leb5-lce+Ay8cAoVskJKJRSCMUSeUcqKsqNRqiQzRCFqepGqaiUySlCmhEplGqSMuQOTkxVGAVDX6ZCP7lXOZnVcuHFWUtcmAKpRgCF8oARanEk1HneZSfmru1QUARmPWSny-UVUN0zXTE91PfqgB6voAUQyAD3xgB+DIAMQyAPoMgAKvoAOeZWYAXl66oAsomAOhKnmACvxDKLclcmpYp6WhJlhHZZpO38Hohg6Qdu3TpoN7WnaDpOgBT79skAFvt98gizxAEfOLH4AcA0udT9MDfkAA

Discussion