Challenge your TypeScript skills: convert snake_case to camelCase and back again

I am looking to develop a Typescript function that can deeply traverse a plain object and convert all snake_case keys to camelCase. Additionally, I need a function that can convert camelCase keys to snake_case throughout the object.

While implementing this in JavaScript is straightforward, I find it challenging in Typescript due to the need to consider types.

Can anyone advise me on how to approach this task in Typescript?

This is my JavaScript version for reference:

const keyToSnakeCase = obj => {
    if (Array.isArray(obj)) {
        return obj.map(el => keyToSnakeCase(el));
    }

    if (!isPlainObject(obj)) {
        return obj;
    }

    const newObj = {};

    Object.entries(obj).forEach(([key, value]) => {
        newObj[camelCase(key)] = keyToSnakeCase(value);
    });

    return newObj;
};

const keyToCamelCase = obj => {
    if (Array.isArray(obj)) {
        return obj.map(el => keyToCamelCase(el));
    }

    if (!isPlainObject(obj)) {
        return obj;
    }

    const newObj = {};

    Object.entries(obj).forEach(([key, value]) => {
        newObj[snakeCase(key)] = keyToCamelCase(value);
    });

    return newObj;
};

Answer №1

Big shoutout to @jcalz for helping me crack the code!

import isPlainObject from "lodash-es/isPlainObject";
import camelCase from "lodash-es/camelCase";
import snakeCase from "lodash-es/snakeCase";

type SnakeToCamel<T extends string> = T extends `${infer F}_${infer R}`
  ? `${Lowercase<F>}${Capitalize<SnakeToCamel<R>>}`
  : T;

type CamelToSnake<
  T extends string,
  A extends string = ""
> = T extends `${infer F}${infer R}`
  ? CamelToSnake<R, `${A}${F extends Lowercase<F> ? F : `_${Lowercase<F>}`}`>
  : A;

type DeepCamelKeys<T> = T extends readonly any[]
  ? { [I in keyof T]: DeepCamelKeys<T[I]> }
  : T extends object
  ? {
      [K in keyof T as K extends string ? SnakeToCamel<K> : K]: DeepCamelKeys<
        T[K]
      >;
    }
  : T;

type DeepSnakeKeys<T> = T extends readonly any[]
  ? { [I in keyof T]: DeepSnakeKeys<T[I]> }
  : T extends object
  ? {
      [K in keyof T as K extends string ? CamelToSnake<K> : K]: DeepSnakeKeys<
        T[K]
      >;
    }
  : T;

function keyToSnakeCase<T>(obj: T): DeepSnakeKeys<T>;
function keyToSnakeCase(obj: any) {
  if (Array.isArray(obj)) {
    return obj.map((el) => keyToSnakeCase(el));
  }

  if (!isPlainObject(obj)) {
    return obj;
  }

  const newObj: any = {};

  Object.entries(obj).forEach(([key, value]) => {
    newObj[camelCase(key)] = keyToSnakeCase(value);
  });

  return newObj;
}

function keyToCamelCase<T>(obj: T): DeepCamelKeys<T>;
function keyToCamelCase(obj: any) {
  if (Array.isArray(obj)) {
    return obj.map((el) => keyToCamelCase(el));
  }

  if (!isPlainObject(obj)) {
    return obj;
  }

  const newObj: any = {};

  Object.entries(obj).forEach(([key, value]) => {
    newObj[snakeCase(key)] = keyToCamelCase(value);
  });

  return newObj;
}

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

Display real-time information in angular material table segment by segment

I need to incorporate service data into an Angular mat table with specific conditions as outlined below: If the difference between the start date and end date is less than 21 days, display 'dd/mm' between the 'start_date' and 'end ...

Steps for creating a copy of an Angular component

https://i.stack.imgur.com/4RMsR.png Whenever the user clicks on the Create Copy button, I aim to replicate the content of the DashboardComponent and position the duplicated version below the original one (the DashboardComponent featuring four dark blue sq ...

Solving Issues with Names, Modules, and Other Strange Elements in Angular Universal

While the main app runs smoothly, attempting to serve the bundled SSR results in perplexing errors that I'm struggling to comprehend. All setup details are provided below for reference. The process of creating a server-side app seems riddled with sma ...

Combine a constant interface with a generic function to create a unique generic interface

When dealing with legacy code that utilizes a const in the following pattern: const fnUsedInSetPrototypeOf = { equalityComparer<T>(a: T, b: T) { return a === b }, otherFn<T> (this: T) { /*...*/ }, // ... other things, all along the ...

"Facing an issue where ts-node is not recognizing d.ts files, despite tsc being able to compile them

I am currently using typescript along with express and attempting to enhance the request object in express. Below is my server.ts file: import express, { Request, Response } from "express"; const app = express(); app.use(function(req: Request, res: Respo ...

Tips for updating Ref objects in React

In the process of fixing a section of my project, I'm encountering an issue where I have no control over how refs are being utilized. The Editable text elements are currently handled through refs and a state variable within the component that holds al ...

To set up the store in configureStore, you must provide one type argument for the 'MakeStore' generic type

Encountering an issue with MakeStore showing a Generic type error 'MakeStore' requires 1 type argument(s) .ts(2314) Here is the code from configureStore.ts: import { configureStore, EnhancedStore, getDefaultMiddleware, } from '@reduxj ...

"Implementing type definitions for a function that updates records with nested properties and callback support

I am currently working on a function that updates nested values within a record. I have different versions of this function for paths of varying depths. My main struggle is figuring out how to properly type the callback function used when updating the val ...

What is the best way to provide JSON data in Angular?

I am working on an Angular 4 application that is using Webpack, and I am currently facing a challenge with serving a JSON file. I have two main questions regarding this: When the JSON file is static, I am struggling to configure Webpack to handle it the ...

Unlock the full potential of ts-transformer-keys in your Vue application

note: After spending countless hours on this, I finally had a breakthrough today. It turns out that changing transpileOnly to false made all the difference: chainWebpack: config => { const getCustomTransformers = program => ({ before: [ ...

Utilizing string to access property

Is there a better way to access interface/class properties using strings? Consider the following interface: interface Type { nestedProperty: { a: number b: number } } I want to set nested properties using array iteration: let myType: Type = ...

Tips for creating an interface in TypeScript that prevents access to uninitialized properties of a class

Interfaces still baffle me a bit. I understand that interfaces are typically used for public properties, but I want to create an interface that will prevent access to uninitialized properties. Currently, I am able to access this.material without any errors ...

What is the best way to set up environments for Google App Engine (GAE

After developing a web app with server and client components, I decided to deploy it to Google Cloud using App Engine. Although the deployment process was successful, the main issue lies in the non-functioning env_variables that are crucial for the applic ...

Can you demonstrate how to showcase images stored in an object?

Is there a way to properly display an image from an object in React? I attempted to use the relative path, but it doesn't seem to be working as expected. Here is the output shown on the browser: ./images/avatars/image-maxblagun.png data.json " ...

How to easily scroll to the top of the previous page in Angular 8

In my Angular 8 application, there are buttons that are meant to take the user back to the previous page when clicked. While the functionality works as expected, I am facing an issue where the page does not automatically scroll to the top upon navigating ...

Having trouble launching the node pm2 process manager on AWS Elastic Beanstalk, npm update verification did not pass

Having some issues with utilizing pm2 for managing processes in my typescript node application, deployed on elasticbeanstalk. Whenever a new instance is launched by pm2, the following shows up in the logs ---------------------node.js logs---------------- ...

What is the process for setting up a subrouter using React Router v6?

This is the current React Router setup I am using: const router = createBrowserRouter([ { path: "/", element: ( <Page activeNav="home" > <Home /> </Page> ) }, { ...

Bring in a library with Angular2 using webpack

I am currently utilizing the angular2-webpack starter from GitHub, and I am looking to incorporate an npm library, such as Babylon JS. My approach so far has been as follows: import * as BABYLON from 'babylonjs/babylon'; The Babylon library inc ...

Angular - The argument provided is not compatible with the parameter

I encountered the following TypeScript errors in app.component.ts: Issue: Argument of type '(events: Event[]) => void' is not assignable to parameter of type '(value: Event[]) => void'. Description: Types of parameters 'e ...

A guide on incorporating JavaScript variables within a GraphQL-tag mutation

I'm having trouble consistently using javascript variables inside graphql-tag queries and mutations when setting up an apollo server. Here's a specific issue I've encountered: gql` mutation SetDeviceFirebaseToken { SetDeviceFirebaseTok ...