5

I need to create a function to modify keys of object from PascalCase to camelCase format. Ie

const input = {
    FirstName: 'John',
    LastName: 'Smith'
};

const expectedOutput = {
    firstName: 'John',
    lastName: 'Smith'
};

// function looks as follows
function pascalToCamelCase<T>(input: T): CamelCaseKeys<T> {
    if (typeof input !== 'object' || input === null) {
        return input;
    }

    // do conversion....
    return output;
}

But I don't know how to modify keys accordingly via CamelCaseKeys

4 Answers 4

16

Well, I learned something today! We can use the new typescript 4.1 template string literal features and utility methods to achieve this in a typesafe way.

Capitalize<T> will capitalise a string.

Uncapitalize<T> will uncapitalise a string (what you're after).

More info here on these.

From these two we can build a helper type UncapitalizeObjectKeys<T>:

type UncapitalizeKeys<T extends object> = Uncapitalize<keyof T & string>;

type UncapitalizeObjectKeys<T extends object> = {
  [key in UncapitalizeKeys<T>]: Capitalize<key> extends keyof T ? T[Capitalize<key>] : never;
}

Notes:

  • Uncapitalize<keyof T & string> - we intersect with string to only get the keys of T which are strings, as we can't capitalise numbers or symbols
  • We have to uncapitalise the keys in [key in UncapitalizeKeys<T>] - and then re-capitalise them to actually pull the proper value out of T with T[Capitalize<key>]. The conditional part Capitalize<key> extends keyof T is just checking if the capitalised, uncapitalised key still is assignable to keyof T, as TS isn't able to maintain this relationship (...yet?).

We can then pull a couple of parts out of @spender's answer - replacing the runtime type checking as TS should be able to assert these (assuming these objects aren't coming from IO:

type UncapitalizeObjectKeys<T extends object> = {
  [key in UncapitalizeKeys<T>]: Capitalize<key> extends keyof T ? T[Capitalize<key>] : never;
}

type UncapitalizeKeys<T extends object> = Uncapitalize<keyof T & string>;


export const lowerCaseKeys = <T extends object>(obj: T): UncapitalizeObjectKeys<T> => {
    const entries = Object.entries(obj);
    const mappedEntries = entries.map(
        ([k, v]) => [
            `${k.substr(0, 1).toLowerCase()}${k.substr(1)}`,
            lowerCaseKeys(v)]
    );

    return Object.fromEntries(mappedEntries) as UncapitalizeObjectKeys<T>;
};

We now get the output we're after:

enter image description here

Playground link

Sign up to request clarification or add additional context in comments.

3 Comments

Unfortunately I have TS 3.2. So I need some 'older' approach
Not possible in that case sadly
methodologySaga.ts:59 RangeError: Maximum call stack size exceeded
4

I will put more modern answer here just for visibility. Though the implementation in the other answers is correct, I think this type is more elegant. playground

type UncapitalizeObjectKeys<T> = {
  [key in keyof T as Uncapitalize<key & string>]: T[key] extends Object ? UncapitalizeObjectKeys<T[key]> : T[key]
}

Works since 4.1

3 Comments

Thanks for your input. Can you please add playground link as well
@Timothy sure. I also updated it to be recursive over fields
Very neat solution. I love it mate
2

Here's a recursive solution that I've used to good effect:

export const lowerCaseKeys = (obj: any): any => {
    if (typeof obj !== 'object') {
        return obj;
    }
    if (Array.isArray(obj)) {
        return obj.map(lowerCaseKeys);
    }
    if (obj === null) {
        return null;
    }
    const entries = Object.entries(obj);
    const mappedEntries = entries.map(
        ([k, v]) => [
            `${k.substr(0, 1).toLowerCase()}${k.substr(1)}`,
            lowerCaseKeys(v)] as const
    );
    return Object.fromEntries(mappedEntries);
};

Playground link

3 Comments

turns out you can actually type this now with TS 4.1 typescriptlang.org/docs/handbook/release-notes/…
thank for that... this part I have, I don't have TypeScript part... The most important part I need implement is: (obj: any): any
check my answer for the TS part @Timothy
-1

This particular form works better with intellisense, and handles arrays

export type UncapitalizeObjectKeys<T> = T extends Array<infer U>
  ? Array<UncapitalizeObjectKeys<U>>
  : T extends object
  ? {
      [K in keyof T as Uncapitalize<string & K>]: UncapitalizeObjectKeys<T[K]>
    }
  : T

type Movie = {
  Title: string
  Year: number
}

type BibliographyPascalCase = {
  DirectorName: string
  Movies: Movie[]
}

type BibliographyCamelCase = UncapitalizeObjectKeys<BibliographyPascalCase>

/*
type BibliographyCamelCase = {
  directoName: string
  movies: {
    title: string
    year: number 
  }[]
*/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.