Skip to content

Commit

Permalink
v3.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Ugo evola authored and ugoevola committed Sep 26, 2022
1 parent 6fec140 commit d724557
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 42 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# [3.1.0](https://github.com/ugoevola/ts-mapstruct/releases/tag/v3.1.0)

### Added
- You have now 2 solutions to expose properies :
> - with @Expose decorator of class-transformer
> - Come back of getters / setters (from v1.0.1). If you define a getter or a setter for a property, it will be exposed.
# [3.0.3](https://github.com/ugoevola/ts-mapstruct/releases/tag/v3.0.3)

### Added
Expand Down Expand Up @@ -29,6 +35,8 @@ Now, all the selection calculations are done when the mapper is instantiated, an
- parameters with underscors are now recognized
# [2.0.0](https://github.com/ugoevola/ts-mapstruct/releases/tag/v2.0.0)
> **NOTE**: This version is deprecated.
### Breaking Changed
- The getters / setters have no more influence to map an object. It is now necessary to expose each attribute.
### Added
- **@BeforeMapping**: perform some actions before the mapping process
- **@AfterMapping**: perform some actions after the mapping process
Expand Down
68 changes: 42 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ But it can be used in any typescript project.
## Usage
For the exemple, I will take a **UserMapper** that maps a **UserDto** into **UserEntity**.
### Classes
You must expose the properties of the target class by using an apropriate decorator.<br>
Otherwise, you will retrieve an empty Object, and probably have MapperExceptions. <br>
In this example, I'm using @Expose() decorator of the [class-tranformer library](https://www.npmjs.com/package/class-transformer#enforcing-type-safe-instance)
```ts
export class UserDto {
@Expose() private fname: string;
Expand Down Expand Up @@ -79,6 +76,10 @@ export class FriendEntity {
}
}
```
> **Note**: You must expose the properties of the target class by using an apropriate decorator.<br>
> Otherwise, you will retrieve an empty Object, and probably have MapperExceptions. <br>
> - In this example, I'm using @Expose() decorator of the [class-tranformer library](https://www.npmjs.com/package/class-transformer#enforcing-type-safe-instance)
> - You can also define well named getters/setters for properties.
### Mapper

```ts
Expand Down Expand Up @@ -120,7 +121,8 @@ export class UserMapper {
```
### Usage
```ts
@Mapper()
// NestJs (decorate your mapper with @Injectable)
@Injectable()
export class UserService {
constructor(private userMapper: UserMapper) {}

Expand All @@ -133,6 +135,13 @@ export class UserService {
}
}
```
```ts
// TypeScript
const userDto = new UserDto()
//...
const userMapper = new UserMapper()
const userEntity = userMapper.entityFromDto(userDto)
```
### Type conversion
The TS code is trans-compiled in JS before being executed, so the types of the source objects are kept on the end object.

Expand All @@ -156,18 +165,18 @@ The library allows you to define the targeted type for each property:
If you have multiple depths in your object, you can target the right property with the right type like this:
```ts
@Mappings(
{
target: 'bestFriend',
expression: 'getBestFriend(userDto.friends)',
type: FriendEntity
},
{ target: 'friends', type: FriendEntity },
{ target: 'friends.bdate', type: Date },
{ target: 'bestFriend.bdate', type: Date }
)
entityFromDto(_userDto: UserDto): UserEntity {
return new UserEntity;
}
{
target: 'bestFriend',
expression: 'getBestFriend(userDto.friends)',
type: FriendEntity
},
{ target: 'friends', type: FriendEntity },
{ target: 'friends.bdate', type: Date },
{ target: 'bestFriend.bdate', type: Date }
)
entityFromDto(_userDto: UserDto): UserEntity {
return new UserEntity;
}
```
Below are examples of options that may exist:

Expand Down Expand Up @@ -270,8 +279,7 @@ export class UserMapper {

}
```

Note: if you return object from your @AfterMapping or @BeforeMapping function, it will not be considered.
> **Note**: if you return object from your @AfterMapping or @BeforeMapping function, it will not be considered.
### @MappingTarget
The MappingTarget allows you to pass the resulting object throw the methods to perform some actions on it.
Expand Down Expand Up @@ -328,7 +336,7 @@ export class UserMapper {

}
```
> **Notes**: @MappingTarget is not used in the same way depending on the type of method in which it is used:
> **Note**: @MappingTarget is not used in the same way depending on the type of method in which it is used:
> - In an @BeforeMapping method, the argument bound to the @MappingTarget decorator must also be found in the mapping method. Otherwise @BeforeMapping will not be invoked.
> - In an @AfterMapping method, the argument bound to the @MappingTarget does not have to be in the mapping method. However, you must provide the return type of the mapping method for the @AfterMapping method to be invoked.
Expand Down Expand Up @@ -376,7 +384,9 @@ The thrown exceptions are extends of the HttpException of nestjs/common.
Injectable()
export class UserMapper {

// this will throw a BadExpressionExceptionMapper because the expression for fullName can't be evaluated (unknownMethod does not exist)
// this will throw a BadExpressionExceptionMapper
// because the expression for fullName
// can't be evaluated (unknownMethod does not exist)
@Mappings(
{ target: 'fullName', expression: 'unknownMethod()' }
)
Expand All @@ -392,7 +402,8 @@ export class UserMapper {
Injectable()
export class UserMapper {

// This will throw an IllegalArgumentNameExceptionMapper because getConcatProperties is a reserved name used for supplied mapping funcions
// This will throw an IllegalArgumentNameExceptionMapper
// because getConcatProperties is a reserved name used for supplied mapping funcions
// All supplied mapping function name are forbidden for naming the arguments.
// cf. Supplied Mapping Functions
// this exception is thrown as soon as there is an expression in one provided MappingOptions
Expand All @@ -411,7 +422,9 @@ export class UserMapper {
Injectable()
export class UserMapper {

// this will throw an InvalidMappingOptionsExceptionMapper because you provide multiple sources (value and source) for cn in one MappingOption
// this will throw an InvalidMappingOptionsExceptionMapper
// because you provide multiple sources (value and source)
// for cn in one MappingOption
@Mappings(
{ target: 'cn', value: 'Ugo', source: 'userDto.fname' }
)
Expand All @@ -427,8 +440,9 @@ export class UserMapper {
Injectable()
export class UserMapper {

// this will throw an InvalidMappingTargetExceptionMapper because
// the provided @MappingTarget object does not have the type of the returned mapping function
// this will throw an InvalidMappingTargetExceptionMapper
// because the provided @MappingTarget object
// does not have the type of the returned mapping function
@Mappings()
invalidMappingTargetExceptionMapper (@MappingTarget() _userDto: UserDto): UserEntity {
return new UserEntity()
Expand All @@ -442,7 +456,8 @@ export class UserMapper {
Injectable()
export class UserMapper {

// this will throw an InvalidSourceExceptionMapper because userDto.unknownProperty does not exist
// this will throw an InvalidSourceExceptionMapper
// because userDto.unknownProperty does not exist
@Mappings(
{ target: 'cn', source: 'userDto.unknownProperty' }
)
Expand All @@ -458,7 +473,8 @@ export class UserMapper {
Injectable()
export class UserMapper {

// this will throw an InvalidTargetExceptionMapper because unknown does not exist on UserEntity
// this will throw an InvalidTargetExceptionMapper
// because unknown does not exist on UserEntity
@Mappings(
{ target: 'unknown', source: 'userDto.fname' }
)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-mapstruct",
"version": "3.0.3",
"version": "3.1.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
Expand Down
File renamed without changes.
9 changes: 6 additions & 3 deletions src/core/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import {
getSourceArguments,
control,
retrieveMappingTarget,
clean
clean,
exposePropertiesFromGettersOrSetters
} from '../utils/utils'
import { SupplierDescriptor } from '../models/supplier-descriptor'
import { getOptionsMapping } from './get-options-mappings'
import { getOptionsMapping } from './get-options-functions'
import { ArgumentDescriptor } from '../models/argument-descriptor'
import { mapImplicitProperties } from './mapping-for-implicit'
import { MappingOptions } from '../models/mapping-options'
Expand All @@ -21,7 +22,9 @@ export const mapping = <T>(
mappingOptions: MappingOptions[]
): void => {
control(mapperClass, mappingMethodName, ...mappingOptions)
const targetedType: T = descriptor.value.call()
const targetedType: T = exposePropertiesFromGettersOrSetters(
descriptor.value.call()
)
const sourceNames: string[] = getArgumentNames(descriptor.value.toString())
const sourceArgs: ArgumentDescriptor[] = getSourceArguments(
mapperClass,
Expand Down
14 changes: 13 additions & 1 deletion src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
ClassConstructor,
ClassTransformOptions,
Expose,
plainToInstance
} from 'class-transformer'
import { validateSync } from 'class-validator'
import { isNil } from 'lodash'
import { isNil, lowerFirst } from 'lodash'
import { IllegalArgumentNameExceptionMapper } from '../exceptions/illegal-argument-name.exception'
import { InvalidMappingOptionsExceptionMapper } from '../exceptions/invalid-mapping-options.exception'
import { InvalidMappingTargetExceptionMapper } from '../exceptions/invalid-mapping-target.exception'
Expand Down Expand Up @@ -115,3 +116,14 @@ export const clean = <T>(object: T): T => {
})
return object
}

export const exposePropertiesFromGettersOrSetters = <T>(targetedType: T): T => {
Object.getOwnPropertyNames(targetedType.constructor.prototype)
.filter(key => key.startsWith('get') || key.startsWith('set'))
.map(key => lowerFirst(key.substring(3)))
.filter((v, i, a) => a.indexOf(v) === i)
.forEach(propertyName => {
Expose()(targetedType.constructor, propertyName)
})
return targetedType
}
6 changes: 3 additions & 3 deletions test/simple/simple.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ export class SimpleDto {
private property2: any
private property3: any

getPropety1(): void {
getProperty1(): void {
return this.property1
}

getPropety2(): void {
getProperty2(): void {
return this.property2
}

getPropety3(): void {
getProperty3(): void {
return this.property3
}

Expand Down
14 changes: 6 additions & 8 deletions test/simple/simple.entity.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { Expose } from 'class-transformer'

export class Simple {
@Expose() private property1: any
@Expose() private property2: any
@Expose() private property3: any
private property1: any
private property2: any
private property3: any

getPropety1(): void {
getProperty1(): void {
return this.property1
}

getPropety2(): void {
getProperty2(): void {
return this.property2
}

getPropety3(): void {
getProperty3(): void {
return this.property3
}

Expand Down

0 comments on commit d724557

Please sign in to comment.