Search code examples
serializationnestjsclass-transformermikro-orm

NestJS serialization from snake_case to camelCase


I want to achieve automatic serialization/deserialization of JSON request/response body for NestJS controllers, to be precise, automatically convert snake_case request body JSON keys to camelCase received at my controller handler and vice versa.

What I found is to use class-transformer's @Expose({ name: 'selling_price' }), as on the example below (I'm using MikroORM):

// recipe.entity.ts
@Entity()
export class Recipe extends BaseEntity {
  @Property()
  name: string;
  
  @Expose({ name: 'selling_price' })
  @Property()
  sellingPrice: number;
}
// recipe.controller.ts
@Controller('recipes')
export class RecipeController {
  constructor(private readonly service: RecipeService) {}

  @Post()
  async createOne(@Body() data: Recipe): Promise<Recipe> {
    console.log(data);
    return this.service.createOne(data);
  }
}
// example request body
{
    "name": "Recipe 1",
    "selling_price": 50000
}
// log on the RecipeController.createOne handler method
{ name: 'Recipe 1',
  selling_price: 50000 }

// what I wanted on the log
{ name: 'Recipe 1',
  sellingPrice: 50000 }

There can be seen that the @Expose annotation works perfectly, but going further I want to be able to convert it as the attribute's name on the entity: sellingPrice, so I can directly pass the parsed request body to my service and to my repository method this.recipeRepository.create(data). Current condition is the sellingPrice field would be null because there exists the selling_price field instead. If I don't use @Expose, the request JSON would need to be written on camelCase and that's not what I prefer.

I can do DTOs and constructors and assigning fields, but I think that's rather repetitive and I'll have a lot of fields to convert due to my naming preference: snake_case on JSON and database columns and camelCase on all of the JS/TS parts.

Is there a way I can do the trick cleanly? Maybe there's a solution already. Perhaps a global interceptor to convert all snake_case to camel_case but I'm not really sure how to implement one either.

Thanks!


Solution

  • You could use mapResult() method from the ORM, that is responsible for mapping raw db results (so snake_case for you) to entity property names (so camelCase for you):

    const meta = em.getMetadata().get('Recipe');
    const data = {
      name: 'Recipe 1',
      selling_price: 50000,
    };
    const res = em.getDriver().mapResult(data, meta);
    console.log(res); // dumps `{ name: 'Recipe 1', sellingPrice: 50000 }`
    

    This method operates based on the entity metadata, changing keys from fieldName (which defaults to the value based on selected naming strategy).