Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
384 views
in Technique[技术] by (71.8m points)

typescript - Creating a camelize function for nested objects with typing

Based on the answer in this post Typescript how to map type keys to camelCase I've added types for a camelize function to transform snake case objects to camel case with typing.

It works for the most part, but I'm struggling with the arrays. I'd like the type of the array properties to stay arrays, and the items of the array to keep their types, but I'm having a hard time figuring out how to declare the types properly.

This is the code for the types and the camelize function themselves:

import { types } from 'util'
import { camelCase } from 'lodash'

type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
  ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
  : Lowercase<S>

type KeysToCamelCase<T> = {
  [K in keyof T as CamelCase<string &K>]: T[K] extends {} ? KeysToCamelCase<T[K]> : T[K]
}

function walk(obj): any {
  if (!obj || typeof obj !== 'object') return obj
  if (types.isDate(obj) || types.isRegExp(obj)) return obj
  if (Array.isArray(obj)) return obj

  return Object.keys(obj).reduce((res, key) => {
    const camel = camelCase(key)
    res[camel] = walk(obj[key])
    return res
  }, {})
}

export default function camelize<T>(obj: T): T extends String ? string : KeysToCamelCase<T> {
  return (typeof obj === 'string' ? camelCase(obj) : walk(obj))
}

The conversion itself works well and for simple types the keys are transformed correctly and the type of the value stays in place, but the array properties are not like I want them. The example below won't work, because the roles and nestedArray properties are no longer have the string[] type.

function wantCamel(obj: { 
  id: number, 
  firstName: string; 
  roles: string[], 
  nested: { 
    nestedArray: string[]
  }
}) {
  return obj
}

wantCamel(camelize<{ 
  id: number; 
  first_name: string; 
  roles: string[],
  nested: {
    nested_array: string[],
  }
}>({ 
  id: 123, 
  first_name: 'John Doe', 
  roles: ['user', 'admin'],
  nested: { 
    nested_array: ['one', 'two']
  }
}))

...i get the following error, which i interpret as the value no longer being an array and therefore conflicting with the expected string[] type:

Argument of type 'KeysToCamelCase<{ id: number; first_name: string; roles: string[]; nested: { nested_array: string[]; }; }>' is not assignable to parameter of type '{ id: number; firstName: string; roles: string[]; nested: { nestedArray: string[]; }; }'.
  Types of property 'roles' are incompatible.
    Type 'KeysToCamelCase<string[]>' is missing the following properties from type 'string[]': indexOf, lastIndexOf, forEach, reduceRight, and 5 more.ts(2345)

I've tried a few different things (overloading declarations, expanding KeysToCamelCase type etc., but to no avail.

question from:https://stackoverflow.com/questions/65865302/creating-a-camelize-function-for-nested-objects-with-typing

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

After hours of trying different approaches I found one that worked: I had to check for whether or not the type extended the Array generic and infer the item type from that.

Like this:

type KeysToCamelCase<T> = {
  [K in keyof T as CamelCase<string &K>]: (
    T[K] extends Array<infer U>
      ? (U extends {} ? Array<KeysToCamelCase<U>> : T[K])
      : (T[K] extends {} ? KeysToCamelCase<T[K]> : T[K])
  )
}

Full source code with tests are available on https://github.com/kbrabrand/better-camelize


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...