Using ModelMapper in Grails for mapping domain object to DTO/POGO/POJO object. ModelMapper does good job mapping faster and effectively. However I come to know via author of library, ModelMapper faces issue with Groovy/Android byte code handling while PropertyMap creation. Default configuration works very well, however real-world usage never satisfied with default configuration in software industry, it’s a well known factor.
PropertyMap is used to configure ModelMapper library for your application object mapping such as deep mapping, handling mismatch types, skipping properties, conditional mapping, string mapping, etc.
Article will guide you to configure and create handy Groovy/Grails Meta methods for application – Using ModelMapper in Grails.
Steps to Achieve it
Version Info
1 2 |
ModelMapper: 0.7.3 Grails: 2.4.4 |
Typically it should work in upcoming versions too, if you face any issues and share its resolution you have taken via comments.
Configuring ModelMapper
BuildConfig.groovy or if your Gradle build tool then build.gradle
1 |
'org.modelmapper:modelmapper:0.7.3' |
Define bean in resources.groovy
1 |
modelMapper(org.modelmapper.ModelMapper) { } |
Autowired/Inject a ModelMapper bean wherever need
1 |
ModelMapper modelMapper |
At this point your application is ready to use ModelMapper in your Grails application like below, also you can leverage other methods appropriately for your need.
1 |
modelMapper.map(<source>, <destination>) |
How to achieve property Skip while object mapping, i.e. this article goal
We will be achieving properties skip using PropertyCondition and TypeMap.
Defining ModelMapper PropertyCondition
Better place to put this Util class or Helper Class in your application
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * Creating a Mapper field skip condition * * @param skipFields list of string. For eg. ['courses', 'students'] * @return {@link org.modelmapper.Condition} */ static Condition createSkipCondition(skipFields) { new Condition() { boolean applies(MappingContext context) { !(context.mapping.destinationProperties[0].name in skipFields) } } } |
Defining helper method of createTypeMap, place method where you’re globally configuring ModelMapper. Typical spots are Bootstrap.groovy or dedicated ApplicationInitializeService.groovy
1 2 3 4 5 6 7 8 9 10 11 |
private createTypeMap(Class source, Class target, String typeMapName = null, def skipFields = null) { if (skipFields && typeMapName) { modelMapper.createTypeMap(source, target, typeMapName).propertyCondition = Helper.createSkipCondition(skipFields) } else if (typeMapName) { modelMapper.createTypeMap(source, target, typeMapName) } else if (skipFields) { modelMapper.createTypeMap(source, target).propertyCondition = Helper.createSkipCondition(skipFields) } else { modelMapper.createTypeMap(source, target) } } |
Define Object Mapping
Utilizing above definition for ModelMapper configuration like Domain to DTO/POGO/POJO. Let’s say have method called initializeModelMapper to abstract this configuration into a method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Global Settings modelMapper.configuration.setPropertyCondition(Conditions.isNotNull()) /** * Domain To DTO/POGO/POJO Mapping * * Note: Mapping is required only if you want multiple combination of fields while mapping. * For Example- * ------------ * Scenario:: Student ==> StudentDto * Combinations: * 1. Default is skip reports and syllabus always * 2. Skip only reports * 3. Skip only syllabus * 4. All data - Don't skip at all */ createTypeMap(Student, StudentDto, null, ['reports', 'syllabus']) createTypeMap(Student, StudentDto, STUDENT_TO_DTO_REPORTS, ['syllabus']) createTypeMap(Student, StudentDto, STUDENT_TO_DTO_SYLLABUS, ['reports']) createTypeMap(Student, StudentDto, STUDENT_TO_DTO_REPORTS_SYLLABUS) |
Calling Map method
1 2 3 |
modelMapper.map(student, StudentDto) // Note: .class in not need in Groovy/Grails, just mention class name. |
That’s it, we have achieved Using ModelMapper in Grails with Skipping properties!! Wow 🙂
How about Groovyness with Collection and Solo Mapping
Groovy provides meta programming to extends Class capabilities. Let’s utilize and do some Groovyness.
- Add support of as keyword for Grails Domain to DTO/POGO/POJO
- Add following methods to Grails Domain class, Collection, ArrayList, Hashset and TreeSet
Defining Collection to DTO/POGO/POJO method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private mapDomainToDto(obj, clazz, typeMapName = null) { if (typeMapName) { modelMapper.map(obj, clazz, typeMapName) } else { modelMapper.map(obj, clazz) } } private collectionToDto(def delegate, Class clazz, String name = null) { def result = [] if (name) { delegate.each { result << mapDomainToDto(it, clazz, name) } } else { delegate.each { result << mapDomainToDto(it, clazz) } } result } |
Defining Closures
1 2 3 4 5 6 7 |
Closure toDtoClass = { Class clazz -> collectionToDto(delegate, clazz) } Closure toDtoClassWithCondition = { Class clazz, String name -> collectionToDto(delegate, clazz, name) } |
Initializing Meta methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// Adding the toDTO(Class) & toDTO(Class, name) methods to collection, list, set, etc [Collection, ArrayList, HashSet, TreeSet].each { it.metaClass.toDto = toDtoClass it.metaClass.toDto = toDtoClassWithCondition } grailsApplication.domainClasses.each { domainArtefact -> // Adding 'as DTO' final MetaClass mc = domainArtefact.metaClass final asTypeRef = mc.getMetaMethod('asType', [Class ] as Object[]) mc.asType = { Class clazz -> def result if (delegate) { // I know its a hacky way if (clazz.name.endsWith('Dto')) { result = mapDomainToDto(delegate, clazz) } else { result = asTypeRef.invoke(delegate, [clazz ] as Object[]) } } result } // Adding toDto(Class) mc.toDto = { Class clazz -> mapDomainToDto(delegate, clazz) } // Adding toDto(Class, name) mc.toDto = { Class clazz, String name -> mapDomainToDto(delegate, clazz, name) } } |
That’s it we did it. We can comfortably invoke methods as described below-
- Native as keyword support. For e.g.: student domain object then use ‘student as StudentDto’ will return StudentDto object
- toDto(<DTO class>)
- toDto(<DTO class>, typeMapName)
- toDto(<DTO class>)
- toDto(<DTO class>, typeMapName)
Also I have created Gist of this article code here or Clone URI
1 |
git clone https://gist.github.com/7b8010cea0f248960f1b.git |
Have fun and share your thoughts via comments – Using ModelMapper in Grails!!