Search code examples
typescriptobject-object-mapping

Map incoming JSON Object to typescript interface


Is there a way to map incoming JSON object to typescript interface?

So, let me explain. I have tried using class-transformer, but it does not solve my problem. In javascript, I would have used object-mapper to transform from source to destination. My incoming object's keys can be in upper case, lower case, with hypen or underscore, but then they map to same interface. Example

Incoming Object

{
  'FIRST NAME': 'John',
  'last_name' : 'Doe',
  'Email Address': '[email protected]' 
}

I want to map this to

{
  'first_name': 'John',
  'last_name': 'Doe',
  'email_address': '[email protected]'
}

The issue with class-transformer is I can only use one name in Expose that I can map to from the incoming object.

How I can solve this in typescript?


Solution

  • You can use the following TS features to accomplish this:

    We'll split it by using a generic Split type, and split by the (space) character, replacing it with _ (underscore). In this case we use a recursive strategy to accomplish this.

    I borrowed the Split type from type-fest, and inspired it my answer from their various change case types

    // Taken from type-fest https://github.com/sindresorhus/type-fest/blob/main/source/split.d.ts
    type Split<
        S extends string,
        Delimiter extends string,
    > = S extends `${infer Head}${Delimiter}${infer Tail}`
        ? [Head, ...Split<Tail, Delimiter>]
        : S extends Delimiter
        ? []
        : [S];
    
    type ConvertSpaceToUnderscore<Parts extends readonly any[], PreviousPart = never> = 
        Parts extends [`${infer FirstPart}`, ...infer RemainingParts]
        ? FirstPart extends undefined
            ? ''
                : FirstPart extends ''
                    ? ConvertSpaceToUnderscore<RemainingParts, PreviousPart>
                    : RemainingParts extends {length: 0} ? Lowercase<FirstPart> : `${Lowercase<FirstPart>}_${ConvertSpaceToUnderscore<RemainingParts, PreviousPart>}`
            : '';
    
    type Convert<T extends Record<string, string>> = {
        [K in keyof T as
            K extends string ? ConvertSpaceToUnderscore<Split<K, ' '>> : never
        ]: T[K]
    }
    

    Then use it on your type

    type Input = {
      'FIRST NAME': 'John',
      'last_name' : 'Doe',
      'Email Address': '[email protected]' 
    }
    
    type Converted = Convert<Input>
    // =>
    {
      first_name: "John";
      last_name: "Doe";
      email_address: "[email protected]"
    }
    

    See it in action on TS Playground

    You can add your own splitters as needed to convert any other characters.

    This type then can be trivially applied to any implementation to object-to-object mapper utility as the return value.