生のtypescriptジェネリックのキャメルケースへの方法


TLDR


今日の挑戦は、AをタイプすることですUPPER_CASE 静的文字列camelCase そして、この変換をオブジェクトキーに再帰的に適用します.
プレビュー


ご覧のように、静的なタイプのテキストをUPPER_CASE フォーマットcamelCase . 次に,変換を全オブジェクトキーに再帰的に適用した.
You can play with full source code here
TypeScript 4.2は既にベータ版であるので、我々はそれを提供する力を完全に使うために、新しい着信機能の準備をしなければなりません.そこにすべての新しいtypemcripthttps://devblogs.microsoft.com/typescript/announcing-typescript-4-2-beta/

コードに深く飛び込みましょう


事件を変えるUPPER_CASE キャメルケースには、パーサを使用して大文字を小文字に変換し、不要な文字を取り除く必要があります_ .

文字マッパー


まず最初に、小文字と大文字の間の依存関係を記述する下側/上マッパー型を作成します.
type LowerToUpperToLowerCaseMapper = {
  a: 'A'
  b: 'B'
  c: 'C'
  d: 'D'
  e: 'E'
  f: 'F'
  g: 'G'
  h: 'H'
  i: 'I'
  j: 'J'
  k: 'K'
  l: 'L'
  m: 'M'
  // ... and so on 
}

type UpperToLowerCaseMapper = {
  A: 'a'
  B: 'b'
  C: 'c'
  // ... and so on 
}

文字列を解析する


読むべき小さなパーサを書かなければなりませんUPPER_CASE フォーマットし、変換される新しい構造に解析しますcamelCase . では、テキストパーサーUtil関数から始めましょう.

ヘッドレター


このジェネリックは最初の文字を入力し、それを返します.
type HeadLetter<T> = T extends `${infer FirstLetter}${infer _Rest}` ? FirstLetter : never


尾文字


このジェネリックは、最初の1つ以外のすべての文字を入力し、それらを返します.
type TailLetters<T> = T extends `${infer _FirstLetter}${infer Rest}` ? Rest : never

lettertoupper


このジェネリックは、適切な小文字のマッパー構造体を呼び出して1つのcharを変換します.
type LetterToUpper<T> = T extends `${infer FirstLetter}${infer _Rest}`
  ? FirstLetter extends keyof LowerToUpperToLowerCaseMapper
    ? LowerToUpperToLowerCaseMapper[FirstLetter]
    : FirstLetter
  : T

レトルト


type LetterToLower<T> = T extends `${infer FirstLetter}${infer _Rest}`
  ? FirstLetter extends keyof UpperToLowerCaseMapper
    ? UpperToLowerCaseMapper[FirstLetter]
    : FirstLetter
  : T

トウローケース


現在、我々は再帰的に呼び出しているALBEですHeadLetter , Tail and LetterToLower 全体を反復するstring そして、それらにlowecaseを適用します.

type ToLowerCase<T> = T extends ''
  ? T
  : `${LetterToLower<HeadLetter<T>>}${ToLowerCase<TailLetters<T>>}`


トーセンシクラーゼ


このジェネリックは、最初の文字を大文字と小文字に変換します.
type ToSentenceCase<T> = `${LetterToUpper<HeadLetter<T>>}${ToLowerCase<TailLetters<T>>}`


私たちはすべてのutilsジェネリックで行われているので、最終型の実装に飛び込むことができます.

Uppercasetopascalcase


我々はほとんどそこにいる.今、我々は変換されるジェネリックを書くことができますCAMEL_CASE into PascalCase .
type ToPascalCase<T> = T extends ``
  ? T
  : T extends `${infer FirstWord}_${infer RestLetters}`
  ? `${ToSentenceCase<FirstWord>}${ToPascalCase<RestLetters>}`
  : ToSentenceCase<T>
ご存知のように、我々は再帰的に言葉を分割_ 区切り文字.各単語に変換Sentencecase そして、一緒に結合します.

Uppercasetocamelcase


最後のステップは使用することですPascalCase しかし、最初の単語の小文字を小文字にします.
我々は以前に作成されたジェネリックを使用して、ちょうど一緒にそれらを組み合わせる.
export type UpperCaseToCamelCase<T> = `${ToLowerCase<HeadLetter<T>>}${TailLetters<ToPascalCase<T>>}`

かなり素晴らしいとちょっと単純なコード、右?

ケースキーへのケース変換の適用


ここで再帰的に適用される静的な型を作りたいUpperCaseToCamelCase オブジェクトネストされたキーへのジェネリック.
始める前に、3つのヘルパージェネリックを定義しましょう.

getObjValue


type GetObjValues<T> = T extends Record<any, infer V> ? V : never
この単純なジェネリックは、私たちがRecord<any, T> ラッパー.

キャスト


このジェネリックは、無効な型を渡すためにtypescriptコンパイラをバイパスするのに役立ちます.私たちは、第2のパラメータとして定義される他のタイプにユニオンタイプを「縮小する」ためにキャストを使用します.
type Cast<T, U> = T extends U ? T : any
type T4 = string | number
type T5 = Cast<T4, string>

SwitchKeyValue


私たちは以前定義のジェネリックを使用しますGetObjValues<T> を値に切り替えます.
このジェネリックの目的は、文字列の値をキーに変換することです.
type Foo = SwitchKeyValue<{ a: 'key-a', b: 'key-b' }>

type GetObjValues<T> = T extends Record<any, infer V> ? V : never

export type SwitchKeyValue<
  T,
  // step 1
  T1 extends Record<string, any> = {
    [K in keyof T]: { key: K; value: T[K] }
  },
  // step 2
  T2 = {
    [K in GetObjValues<T1>['value']]: Extract<GetObjValues<T1>, { value: K }>['key']
  }
> = T2
手順全体を2段階にするので、コードを入れ子にしないで、部分的な値を変数に保存することにしました.サブ結果変数は一般的なパラメータのために保存されます.そのtypescript機能のおかげで私は“変数”に変換の結果を“保存”することができますT1 and T2 . これは静的な型をよりネストすることで書く有用なパターンです.
すべてがうまくいくので、再帰的な入れ子になったキー変換に飛び込みましょう.

TransformKeyStamelcase


今、我々は全体の記事から芸術の1つの作品にジェネリックを結合します.

type TransformKeysToCamelCase<
  T extends Record<string, any>,
  T0 = { [K in keyof T]: UpperCaseToCamelCase<K> },
  T1 = SwitchKeyValue<T0>,
  T2 = {
    [K in keyof T1]:T[Cast<T1[K], string>]
  }
> = T2
type NestedKeyRevert = TransformKeysToCamelCase<{
  FOO_BAR: string
  ANOTHER_FOO_BAR: true | number,
}>

ご覧のように、ジェネリックには3つの手順がありますT0 , T1 and T2 変数.

第一歩


最初のステップは、キーがUperperCaseケースであり、値がCamelCaseに変換されたキーであるオブジェクト型を作成します
T0 = { [K in keyof T]: UpperCaseToCamelCase<K> },

第二段階


番目のステップは、以前に作成されたジェネリックスイッチとスイッチキーを
T1 = SwitchKeyValue<T0>,

第三段階


第3ステップ接続T1 からのデータ型T .
T2 = { [K in keyof T1]: T[Cast<T1[K], string>] }

入れ子深い再帰を加える


これを提供するために、値がObjectの型であるかどうかをチェックし、再帰を呼び出すジェネリックを作成します.
type CallRecursiveTransformIfObj<T> = T extends Record<any, any> ? TransformKeysToCamelCase<T> : T

を返します.

type TransformKeysToCamelCase<
  T extends Record<string, any>,
  T0 = { [K in keyof T]: UpperCaseToCamelCase<K> },
  T1 = SwitchKeyValue<T0>,
  T2 = { [K in keyof T1]: CallRecursiveTransformIfObj<T[Cast<T1[K], string>]> }
> = T2
そして、Vilは引きます!🎉🎉🎉
ネストしたデータ構造をジェネリックパラメータとしてテストする場合
type NestedKeyRevert = TransformKeysToCamelCase<{
  FOO_BAR: string
  ANOTHER_FOO_BAR: true | number,
  NESTED_KEY: {
    NEST_FOO: string
    NEST_BAR: boolean
  },
}>

万事うまくいく.
あなたが最後までこの記事を読んだことを祝ってください.我々は、生のtypescriptでかなり先進的な仕事である入れ子状のキーケース変換をうまく付け加えました.
You can play with the full source code here
忘れないで🫀 この記事が好きなら.