TypeScript konverter generisk objekt fra snake case til camel case


Jeg vil gjerne skrive en funksjon som tar imot et objekt med snake case-nøkler og konverterer det til et objekt med camel case-nøkler. Hva er den beste måten en slik funksjon kan typiseres på i TypeScript, forutsatt at vi kjenner typen til inngangsobjektet, men ønsker at løsningen skal være generisk.

type InputType = {
  snake_case_key_1: number,
  snake_case_key_2: string,
  ...
}

function snakeToCamelCase(object: T): U {
  ...
}

Hva er den beste jobben som kan gjøres for å skrive T og U.

Jeg ønsker at U skal være så snevert typet som mulig, og at typen til U helst skal være basert på T.

Ideelt sett, hvis T er min eksempel InputType, vil jeg at U skal være typet som

{
  snakeCaseKey1: number,
  snakeCaseKey2: string,
  ...
}

Svar #1

Løsning

Lekeplass

Dette er mulig med malbokstype-litteraler i TypeScript 4.1 (se også snake_case):

type SnakeToCamelCase<S extends string> =
  S extends `${infer T}_${infer U}` ?
  `${T}${Capitalize<SnakeToCamelCase<U>>}` :
  S
type T11 = SnakeToCamelCase<"hello"> // "hello"
type T12 = SnakeToCamelCase<"hello_world"> // "helloWorld"
type T13 = SnakeToCamelCase<"hello_ts_world"> // "helloTsWorld"
type T14 = SnakeToCamelCase<"hello_world" | "foo_bar">// "helloWorld" | "fooBar"
type T15 = SnakeToCamelCase<string> // string
type T16 = SnakeToCamelCase<`the_answer_is_${N}`>//"theAnswerIs42" (type N = 42)

Du vil da kunne bruke tastemapping i kartlagte typer for å konstruere en ny posttype:

type OutputType = {[K in keyof InputType as SnakeToCamelCase<K>]: InputType[K]}
/* 
  type OutputType = {
      snakeCaseKey1: number;
      snakeCaseKey2: string;
  }
*/

Utvidelser

Inversjonstype

type CamelToSnakeCase<S extends string> =
  S extends `${infer T}${infer U}` ?
  `${T extends Capitalize<T> ? "_" : ""}${Lowercase<T>}${CamelToSnakeCase<U>}` :
  S

type T21 = CamelToSnakeCase<"hello"> // "hello"
type T22 = CamelToSnakeCase<"helloWorld"> // "hello_world"
type T23 = CamelToSnakeCase<"helloTsWorld"> // "hello_ts_world"

Pascal-case, kebab-case og inversjoner

Når du har fått de ovennevnte typene, er det ganske enkelt å konvertere mellom dem og andre kasus ved å bruke de innebygde strengtypene Capitalize og Uncapitalize:

type CamelToPascalCase<S extends string> = Capitalize<S>
type PascalToCamelCase<S extends string> = Uncapitalize<S>
type PascalToSnakeCase<S extends string> = CamelToSnakeCase<Uncapitalize<S>>
type SnakeToPascalCase<S extends string> = Capitalize<SnakeToCamelCase<S>>

For kebab case, erstatt _ fra snake case-typen med -.

Konverter innlagte egenskaper

type SnakeToCamelCaseNested<T> = T extends object ? {
  [K in keyof T as SnakeToCamelCase<K & string>]: SnakeToCamelCaseNested<T[K]>
} : T

"Type-instansiering er ekstremt dyp og muligens uendelig."

Denne feilen kan oppstå med ganske lange strenger. Du kan behandle flere del-termer på en gang for å begrense typprosesseringens rekursjon til et akseptabelt nivå for kompilatoren. F.eks. SnakeToCamelCaseXXL:

Lekeplass

type SnakeToCamelCaseXXL<S extends string> =
  S extends `${infer T}_${infer U}_${infer V}` ?
  `${T}${Capitalize<U>}${Capitalize<SnakeToCamelCaseXXL<V>>}` :
  S extends `${infer T}_${infer U}` ?
  `${T}${Capitalize<SnakeToCamelCaseXXL<U>>}` :
  S

Merk: I den første tilstanden utleder T og U hver sin delterm, mens V utleder resten av strengen.

Oppdatering: TS 4.5 vil øke typen-instanseringsdybdebegrensningen fra 50 til 100, så dette kompilatortrikset er ikke nødvendig med nyere versjoner. For mer komplekse tilfeller kan du nå også bruke hale-rekursiv evaluering.

Svar #2

For det det er verdt, støtte jeg på noen problemer med typene fra @ford04s svar. Jeg fant CamelCasedProperties og SnakeCasedProperties fra https://github.com/sindresorhus/type-fest til å fungere bra så langt.

Relaterte artikler