ค้นหาเว็บไซต์

TypeScript แปลงคีย์จาก camelCase เป็น snake_case


ลองจินตนาการว่าเรามีประเภทบางอย่างสำหรับวัตถุที่มีคุณสมบัติแบบ camelCase:

type Foo = {
  propertyBob: string;
  propertyJane?: number;
}

มีวิธีใดบ้างในการสร้าง generic ที่จะแปลง camelCase เป็น snake_case เช่น

type foo_snake = Snakeify<Foo>; 
/*
{
  property_bob: string;
  property_jane?: number;
}
*/

คำตอบ #1

ตอนนี้ TypeScript มีชนิดของ template literal ดังนั้นสิ่งนี้จึงเป็นไปได้จริง ๆ

แพ็กเกจ type-fest ใช้เพื่อทำตัวแปลงหลายตัวสำหรับ snake_case, kebab-case, camelCase เป็นต้น

ตัวแปลงเฉพาะนี้ตรงข้ามกับสิ่งที่คุณต้องการ แต่เป็นสิ่งที่ง่ายที่สุดในการใช้งาน:

type SnakeCaseToPascalCase<S extends string> =
    S extends `${infer A}_${infer B}`
    ? `${Capitalize<A>}${SnakeCaseToPascalCase<B>}`
    : Capitalize<S>

const snakeToPascal =
    <S extends string>(s: S): SnakeCaseToPascalCase<S> =>
        s.split('_')
         .map(word => word[0].toUpperCase() + word.slice(1))
         .join("") as any

const pascalCase = snakeToPascal("get_abstract_singleton_manager")
// pascalCase: "GetAbstractSingletonManager"

จากนั้นคุณสามารถใช้ตัวแปลงนี้ร่วมกับชนิดที่ทำการแมปได้:

type SnakeToPascal<M extends Record<string, any>> =
    {[K in keyof M as SnakeCaseToPascalCase<K & string>]: M[K]}

type User = SnakeToPascal<{
    first_name: string,
    last_name: string,
}>
// inferred as: {
//    FirstName: string;
//    LastName: string;
// }

คำตอบ #2

ถ้าคุณอยากเจาะลึกลงไป ผมขอแนะนำ: https://type-level-typescript.com/

แต่ที่นี่คือคำตอบสำหรับคำถามของคุณ:

type Foo = {
  propertyBob: string;
  propertyJane?: number;
};

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

type SnakeCaseFoo = {
  [K in keyof Foo as CamelToSnakeCase<K>]: Foo[K];
};

const snakeCaseFooExample: SnakeCaseFoo = {
  property_bob: "hello",
  property_jane: 123,
};

โอเค ดังนั้นนี่คือสิ่งที่กำลังเกิดขึ้น:

ประเภท SnakeCaseFoo เป็นวัตถุ คีย์ของวัตถุนี้ ซึ่งเป็นประเภททั่วไปที่เราเรียกตามอำเภอใจว่า K ต้องอยู่ในคีย์ของประเภท Foo นั่นคือเหตุผลที่เราทำ K in keyof Foo

ตอนนี้ กุญแจนี้ เราจะเปลี่ยนเป็นบางสิ่งอย่างอื่น เราจะเปลี่ยนกุญแจนั้นเป็น camel_case

ประเภทของคีย์นั้นคือประเภทใดก็ตามที่คีย์นั้นมีใน foo ดังนั้นจึงเป็น Foo[K] ดังนั้น ถ้าประเภทของคีย์นั้นใน Foo เป็นสตริง ประเภทใหม่ของคีย์นั้นใน SnakeCaseFoo ของเราก็จะเป็นสตริงด้วยเช่นกัน

ตกลง ตอนนี้เรามาเปลี่ยนไปที่ประเภท CamelToSnakeCase:

ประเภทนี้ได้รับเจนเนอริก ซึ่งเรากำหนดชื่อแบบสุ่มว่า S เจนเนอริกนี้มีข้อจำกัด มันต้องสืบทอดมาจาก string (สตริงใดก็ได้) หรือประเภทที่ถูกรวมอยู่ในประเภท string เช่น ตัวอักษรสตริง 'asdf'

จากนั้นเราจะทำ Conditional Type ซึ่งมีลักษณะดังนี้:

type TrueOrFalse = A extends B ? true : false;

นี่คล้ายกับตัวดำเนินการ ternary ใน JavaScript แต่ไม่เหมือนกัน มันเป็นคุณสมบัติของ TypeScript สำหรับชนิดข้อมูล อีกครั้ง แหล่งข้อมูลที่ยอดเยี่ยมในการเรียนรู้เพิ่มเติมเกี่ยวกับมันคือ https://type-level-typescript.com/

ตอนนี้ เรากำลังจะทำการจับคู่รูปแบบบนสตริงลิตเทอรัล การจับคู่รูปแบบของเราจะใช้ตัวอักษรหนึ่งตัว ซึ่งเราเรียกว่า T และจากนั้นก็ส่วนที่เหลือของสตริง ซึ่งเราเรียกว่า U

จากนั้นเราจะพูดว่า: ถ้า S ไม่ตรงตามรูปแบบของเรา ประเภทผลลัพธ์ของเราก็คือ S เราไม่ทำการเปลี่ยนแปลงมัน แต่ถ้า S ตรงตามรูปแบบของเรา เราจะคืนค่าประเภทใหม่ หนึ่งประเภทที่เป็นการแปลงของ S นั้น

ดังนั้นมาลองแปลง S ซึ่งประกอบด้วยตัวอักษร T และส่วนที่เหลือของสตริง U:

ถ้าตัวอักษร T เป็นตัวพิมพ์ใหญ่ (T extends Capitalize<T>) เราจะคืนค่า '_' มิฉะนั้นเราจะคืนค่า '' ตามด้วยตัวอักษร T นั้นในรูปตัวพิมพ์เล็ก ถ้ามันเป็นตัวพิมพ์เล็กอยู่แล้วก็จะไม่ทำอะไรกับ T ดังนั้นเราจึงมีตัวอย่างต่อไปนี้สำหรับส่วนนี้โดยเฉพาะ:

if T is 'a' then it is converted to ''+'a' which is the same as 'a'.
if T is 'A' then it is converted to '_'+'a' which is '_a'

จากนั้นมันจะเรียกตัวเองซ้ำด้วยตัวอักษรที่เหลือ (U)

ดังนั้น สำหรับสตริง ptBob การคำนวณจะทำดังนี้:

CamelToSnakeCase<'ptBob'>

  • T คือ 'p' และ U คือ 'tBob' => 'p' + CamelToSnakeCase<'tBob'>
  • T คือ 't' และ U คือ 'Bob' => 't' + CamelToSnakeCase<'Bob'>
  • T คือ 'B' และ U คือ 'ob' => '_b' + CamelToSnakeCase<'ob'>
  • T คือ 'o' และ U คือ 'b' => 'o' + CamelToSnakeCase<'b'>
  • T คือ 'b' และ U คือ '' => 'b' + CamelToSnakeCase<''>
  • S ไม่สอดคล้องกับ $ {infer T}$ {infer U} ดังนั้น => S ซึ่งในจุดนี้คือ ''

ขึ้นไปบนกอง เราจะได้ 'p' + 't' + '_b' + 'o' + 'b' ซึ่งคือ 'pt_bob'.

เราสามารถเปลี่ยนประเภทตัวอักษรแบบ string literal จาก 'ptBob' เป็นประเภทตัวอักษรแบบ string literal 'pt_bob' ได้สำเร็จ

บทความที่เกี่ยวข้อง