Search code examples
typescriptdecoratorclass-transformer

How to transform a flat object to a nested object?


I have 3 classes as follows:

class Server {
    @Expose({ name: 'APP_PORT' })
    @Type(() => Number)
    port: number

    @Expose({ name: 'APP_TAG' })
    @Type(() => String)
    tag: string
}

class Database {
    @Expose({ name: 'DATABASE_PORT' })
    @Type(() => Number)
    port: number

    @Expose({ name: 'DATABASE_NAME' })
    @Type(() => String)
    name: string
}

class Global {
    @Type(() => Server)
    server: Server

    @Type(() => Database)
    database: Database
}

I will convert the plainObject below into instance with the following code:


import { plainToInstance } from 'class-transformer'

const plainObject = {
    APP_PORT: 5000,
    APP_TAG: '1.0.1',
    DATABASE_PORT: 8000,
    DATABASE_NAME: 'test',
}

const instance = plainToInstance(Global, plainObject)
console.log(instance)

The returned result is as follows:

Global {
  server: undefined,   
  database: undefined, 
  APP_PORT: 5000,      
  APP_TAG: '1.0.1',    
  DATABASE_PORT: 8000, 
  DATABASE_NAME: 'test',
}

It is not what I expected. I want it to be like this:

Global {
  server: {
    APP_PORT: 5000,
    APP_TAG: '1.0.1',
  },
  database: {
    DATABASE_PORT: 8000,
    DATABASE_NAME: 'test',
  },
}

What adjustments do I need to make to the 3 classes above to achieve what I want ?

Additional information is that I temporarily solved it by changing the plainObject to the following:

const plainObject = {
    server: {
        APP_PORT: 5000,
        APP_TAG: '1.0.1',
    },
    database: {
        DATABASE_PORT: 8000,
        DATABASE_NAME: 'test',
    },
}

But it is not a "pure" decorator approach.


Solution

  • Finally, I found a solution that seems to work. Just need to add these few lines to the Global class:

    class Global {
        // --------------Add this lines-------------- //
        @Expose({ name: 'APP_TAG' })
        @Transform(({ obj }) =>
            plainToInstance(Server, obj, { excludeExtraneousValues: true }),
        )
        // ------------------------------------------ //
        @Type(() => Server)
        server?: Server
    
        // --------------Add this lines-------------- //
        @Expose({ name: 'DATABASE_NAME' })
        @Transform(({ obj }) =>
            plainToInstance(Database, obj, { excludeExtraneousValues: true }),
        )
        // ------------------------------------------ //
        @Type(() => Database)
        database?: Database
    }