Using ModelMapper in Grails

using modelmapper in grailsUsing 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

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

'org.modelmapper:modelmapper:0.7.3'

Define bean in resources.groovy

modelMapper(org.modelmapper.ModelMapper) { }

Autowired/Inject a ModelMapper bean wherever need

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.

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

/**
 * 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

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.

// 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

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

Note: Like I said in the article before. All these methods should goto Bootstrap.groovy or your custom initialize service class.

Defining Collection to DTO/POGO/POJO method

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

Closure toDtoClass = { Class clazz ->
	collectionToDto(delegate, clazz)
}

Closure toDtoClassWithCondition = { Class clazz, String name ->
	collectionToDto(delegate, clazz, name)
}

Initializing Meta methods

// 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-

Grails Domain Class has –
  • 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)
Collection, ArrayList, HashSet, TreeSet has –
  • toDto(<DTO class>)
  • toDto(<DTO class>, typeMapName)

Also I have created Gist of this article code here or Clone URI

git clone https://gist.github.com/7b8010cea0f248960f1b.git

Have fun and share your thoughts via comments – Using ModelMapper in Grails!!