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!
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).