Tips on streamlining two similar TypeScript interfaces with distinct key names

Presented here are two different formats for the same interface: a JSON format with keys separated by low dash, and a JavaScript camelCase format:

JSON format:

interface MyJsonInterface {
  key_one: string;
  key_two: number;
}

interface MyInterface {
  keyOne: string;
  keyTwo: number;
}

In order to avoid duplications, I am unsure of the correct approach. I have reviewed this question, but the provided answer did not meet my requirements as I do not want the same keys to be accessible in both interfaces.

Is there an alternative solution available?

Answer №1

Breaking down this task into smaller subtasks is essential. To begin with, you will need to create a utility type that can convert snake_case to camelCase. Let's concentrate on this aspect first.

Take a look at the following:

type Separator = '_'
type Convert<Str extends string, Acc extends string = ''> =
  // Verify if Str follows the pattern string_string
  (Str extends `${infer Head}${Separator}${infer Tail}`
    // If yes, determine whether it is the initial call or not, as we don't want to capitalize part of the string
    ? (Acc extends ''
      // As Acc is empty, this is the starting call and the first part should not be capitalized
      ? Convert<Tail, `${Acc}${Head}`>
      // This is not the starting call, so Head should be capitalized
      : Convert<Tail, `${Acc}${Capitalize<Head>}`>)
    // As Str does not match the pattern, this is the final call
    : `${Acc}${Capitalize<Str>}`)

Now, we can iterate through the interface and replace each key with its converted version:

type Builder<T> = {
  [Prop in keyof T as Convert<Prop & string>]: T[Prop]
}

// Transformed object keys:
// {
//     oneTwoThreeFourthFiveSixSevenEightNineTen: "hello";
// }
type Result = Builder<{
  one_two_three_fourth_five_six_seven_eight_nine_ten: 'hello'
}>

Here's a Playground link with the complete code

To reverse the conversion process:

type Separator = '_'

type IsChar<Char extends string> = Uppercase<Char> extends Lowercase<Char> ? false : true;

type IsCapitalized<Char extends string> =
  IsChar<Char> extends true
  ? Uppercase<Char> extends Char
  ? true
  : false
  : false

type Replace<Char extends string> =
  IsCapitalized<Char> extends true
  ? `${Separator}${Lowercase<Char>}`
  : Char

type Result2 = Replace<'A'>

type CamelToSnake<
  Str extends string,
  Acc extends string = ''
  > =
  Str extends `${infer Char}${infer Rest}` ? CamelToSnake<Rest, `${Acc}${Replace<Char>}`> : Acc

// Converted string: "foo_bar_baz"
type Result = CamelToSnake<'fooBarBaz'>

Access the Playground link here

Answer №2

Here is a solution that will work for you.

interface MyJsonInterface {
  key_one: string;
  key_two: number;
  key_three_other: number;
  key_four_with_another: number;
}

type PropMapping<T> =
  T extends `${infer ST}_${infer ND}_${infer RD}_${infer TH}`
  ? `${ST}${Capitalize<ND>}${Capitalize<RD>}${Capitalize<TH>}`
  : T extends `${infer ST}_${infer ND}_${infer RD}`
  ? `${ST}${Capitalize<ND>}${Capitalize<RD>}`
  : T extends `${infer ST}_${infer ND}`
  ? `${ST}${Capitalize<ND>}`
  : never

type MyInterface = {
  [K in keyof MyJsonInterface as PropMapping<K>]: MyJsonInterface[K]
}

Answer №3

Utilizing the concepts from the user-friendly and understandable response mentioned above by @lepsch, I have crafted a version capable of managing any number of lodashs or subscripts.

Note: It appears that there is a limit of up to 1000 lodashs due to TypeScript's built-in tail-recursion depth restriction since version 4.5. Testing with 1000 showed successful results. Strangely, using 1001 lodashs did not trigger an error indicating 'possibly infinite recursion depth,' but instead caused the key to disappear from MyInterface.

For easier reference, here is a link to the relevant Typescript Playground.

interface MyJsonInterface {
  key_one: string;
  key_two: number;
  key_three_other: number;
  key_four_with_another: number;
  key_with_many_more_lodashs_bla_bla_bla_bla_bla: boolean;
  // quirk: keys starting with _ will be turned into upper case (-> HelloStuff)
  __hello__stuff: number
}

type SubstringUntilLodash<T> = T extends `${infer U}_${infer P}` ? U : never; 
type SubstringAfterLodash<T> = T extends `${infer U}_${infer P}` ? P : never; 
type ContainsLodash<T> = SubstringAfterLodash<T> extends '' ? false : true;

type PropMappingRecursive<T extends string, Original extends string = T, Processed extends string = "", Result extends string = ""> =
  // recursion anchor 1: if no lodash contained in T
  ContainsLodash<T> extends false ? 
  // then return T if it was the original string (handles that the first latter is small), else the previous result plus capitalized T.
  T extends Original ? T : `${Result}${Capitalize<T>}` :
  // Making sure the first substring starts with a small letter, and dorecursive call
  Processed extends '' ? PropMappingRecursive<SubstringAfterLodash<T>, Original, `${Processed}_${SubstringUntilLodash<T>}`, `${SubstringUntilLodash<T>}`> :
  // else capitalize and do recursive call
  PropMappingRecursive<SubstringAfterLodash<T>, Original, `${Processed}_${SubstringUntilLodash<T>}`, `${Result}${Capitalize<SubstringUntilLodash<T>>}`>

type MyInterface = {
  [K in keyof MyJsonInterface as PropMappingRecursive<K>]: MyJsonInterface[K]
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Understanding the Typescript Type for a JSON Schema Object

When working with JSON-schema objects in typescript, is there a specific type that should be associated with them? I currently have a method within my class that validates whether its members adhere to the dynamic json schema schema. This is how I am doing ...

What is the reason for the manual update of a view when copying an object's attributes into another object, as opposed to using Object.assign()?

In my application, I have a parent component called 'EmployeeComponent' that is responsible for displaying a list of employees. Additionally, there is a child component named 'EmployeeDetailComponent' which displays the details of the s ...

Oops! An unhandled promise error occurred when trying to fetch a URL with a status of 0. The response received has a status code of

I keep encountering an error whenever I try to hit a post request URL: Error: Uncaught (in promise): Response with status: 0 for URL: null at c (http://localhost:8100/build/polyfills.js:3:19752) at c (http://localhost:8100/build/polyfills.js:3:1 ...

Is it possible to dynamically close the parent modal based on input from the child component?

As I follow a tutorial, I am working on importing the stripe function from two js files. The goal is to display my stripe payment in a modal. However, I am unsure how to close the modal once I receive a successful payment message in the child. Below are s ...

Navigating the terrain of multiple checkboxes in React and gathering all the checked boxes

I am currently developing a filter component that allows for the selection of multiple checkboxes. My goal is to toggle the state of the checkboxes, store the checked ones in an array, and remove any unchecked checkboxes from the array. Despite my attemp ...

Next.js routing can sometimes be unpredictable, especially when navigating from a nested route

I recently set up a directory named dashboard within the pages folder, and it contains the following files: index.tsx customers.tsx invoice.tsx items.tsx When a user navigates to http://localhost:3000/dashboard, the index.tsx page is displayed. However, ...

An endless cascade of dots appears as the list items are being rendered

Struggling to display intricately nested list elements, Take a look at the JSON configuration below: listItems = { "text": "root", "children": [{ "text": "Level 1", "children": [{ "text": "Level 2", "children": [{ "text": ...

Having trouble retrieving values from radio buttons in Angular 2 forms

Having trouble displaying the values of radio button inputs in Angular 2 forms. ...

Updating the navigation bar in Node/Angular 2 and displaying the sidebar once the user has logged in

I am facing a challenge with my first project/application built using Angular 2, particularly related to the login functionality. Here is what I expect from the application: Expectations: When I load the login component for the first time, the navbar ...

What is the proper way to reference the newly created type?

I came up with a solution to create a custom type that I can easily use without the need to constantly call useSession(), as it needs to be a client-side component. However, whenever I try to access this custom type, it always returns undefined (if I try t ...

Selecting a GoJS Node using keys

In Angular with TypeScript, what is the best way to select a node from a diagram based on its key? Currently, I am required to left-click a newly created node in order to select it, but I would like for it to be automatically selected upon creation. I ha ...

Applying the spread operator in the map function for copying objects

In my Angular application, I am attempting to copy an object and add a new property using the spread operator. To add the new property, I have created a method called 'addNewProperty(name)' which returns the property and its value. However, when ...

Is it possible to dynamically create an interface using an enum in TypeScript through programmatically means?

Recently, I defined an enum as shown below: enum EventType { JOB, JOB_EXECUTION, JOB_GROUP } Now, I am in need of creating an interface structure like this: interface EventConfigurations { JOB: { Enabled?: boolean; }; JOB_EXECUTION: { ...

Lack of Intellisense in Sveltekit for the generated $types module on WebStorm

Is there a setting in WebStorm to enable intellisense for automatically generated ./$types by SvelteKit? I'm writing without any minimal example, just curious. Everything is done according to SvelteKit's documentation. Using satisfies LayoutLoad ...

The compatibility between cross-fetch and React Native is currently not supported

I have developed an API wrapper that utilizes fetch to carry out the API requests. To ensure compatibility with Browsers, Node, and React Native, I incorporate cross-fetch. When testing the wrapper in Node, everything works fine. However, when using it in ...

Is there a way to resolve the issue of retrieving the processed value directly in NestJS's @OnEvent function?

Due to excessive logic in the API and its slow performance, I have resorted to handling some of the logic with @OnEvent. The problem arises when the frontend runs the @GET API immediately after this API, potentially without waiting for @OnEvent to update. ...

What could be causing the issue of CSS Styles not being applied to an Angular 2 component with Vaadin elements?

Currently, I am immersed in the tutorial on Vaadin elements using Angular 2 that I stumbled upon here In section 5.3, Styles are applied to the app.component.ts as shown below import { Component } from [email protected]/core'; @Component({ select ...

What could be causing Next.js to re-render the entire page unnecessarily?

As a newcomer to Next.js, I am trying to develop an app where the header/navbar remains fixed at all times. Essentially, when the user navigates to different pages, only the main content should update without refreshing the navbar. Below is the code I have ...

A guide on combining multiple arrays within the filter function of arrays in Typescript

Currently, I am incorporating Typescript into an Angular/Ionic project where I have a list of users with corresponding skill sets. My goal is to filter these users based on their online status and skill proficiency. [ { "id": 1, ...

The type 'MenuOptions[]' cannot be assigned to type 'empty[]'

Even after numerous attempts, I am still grappling with TypeScript problems. Currently, I am at a loss on how to resolve this particular issue, despite all the research I have conducted. The code snippet below is what I am working with, but I am struggling ...