[Java] JAVA object mapping library

5 minute read

Introduction

If the refilling work is performed manually for bean conversion, the amount of code will increase and maintainability is not good.
There are useful methods like BeanUtils.copyProperties, but let’s put together other mapping libraries.

FasterXML ObjectMapper
https://github.com/FasterXML/jackson-databind

This is useful for JSON-related processing.

How to use

Added to build.gradle

build.gradle


// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'

Bean class creation

package com.test.lombok;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class User {
	private String userId;
	
	private String userName;
	
	private String age;
	
	private String gender;
}

user.json sample creation

user.json


{
"userId": "user001",
"userName": "Test Taro",
"age": 20,
"gender": "famale"
}

Test code

package com.test.lombok;

import java.io.File;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

public class TestMain {

	public static void main(String[] args) throws Exception {
		ObjectMapper mapper = new ObjectMapper();

		//Specified by file path
		User user1 = mapper.readValue(
				new File("C:\\workspace_sts\\lombok\\src\\main\\java\\com\\test\\lombok\\user.json"), User.class);
		System.out.println(user1);

		//Specify in URL format
		User user2 = mapper.readValue(TestMain.class.getResource("user.json"), User.class);
		System.out.println(user2);

		//Convert from string
		User user3 = mapper.readValue(
				"{\"userId\":\"user001\", \"userName\": \"Test Taro 2\", \"age\": 22, \"gender\": \"male\"}", User.class);
		System.out.println(user3);

		//Convert from object to string
		String jsonString = mapper.writeValueAsString(user3);
		System.out.println(jsonString);

		//Convert from string to Map
		Map<String, String> userMap = mapper.readValue(jsonString, Map.class);
		System.out.println(userMap);

	}

}

result:

User (userId = user001, userName = test Taro, age = 20, gender = famale)
User (userId = user001, userName = test Taro, age = 20, gender = famale)
User (userId = user001, userName = test Taro 2, age = 22, gender = male)
{“userId”: “user001”, “userName”: “Test Taro 2”, “age”: “22”, “gender”: “male”}
{userId = user001, userName = test Taro 2, age = 22, gender = male}

ModelMapper
http://modelmapper.org/

How to use

Added to build.gradle

build.gradle


	// https://mvnrepository.com/artifact/org.modelmapper/modelmapper
	compile group: 'org.modelmapper', name: 'modelmapper', version: '2.3.5'

Former Bean class

package com.test.lombok;

import lombok.Data;

@Data
public class User {
	private String userId;
	
	private String userName;
	
	private String age;
	
	private String gender;
	
	private Address addr;
}

@Data
class Address {
	private String zipCode;
	private String address1;
	private String address2;
	private String address3;
}

DTO class

package com.test.lombok;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UserDTO {
	private String userId;

	private String userName;

	private String age;

	private String gender;
	
	private String addrZipCode;
	
	private String addrAddress1;
	
	private String addrAddress2;
	
	private String addrAddress3;
}

Test code

package com.test.lombok;

import org.modelmapper.ModelMapper;

public class TestMain {

	public static void main(String[] args) throws Exception {
		ModelMapper modelMapper = new ModelMapper();
		
		User user = new User();
		user.setUserId("user001");
		user.setUserName("test");
		user.setAge("18");
		user.setGender("male");
		Address addr = new Address();
		addr.setZipCode("984-0031");
		addr.setAddress1("○○ prefecture");
		user.setAddr(addr );
		
		UserDTO userDTO = modelMapper.map(user, UserDTO.class);
		System.out.println(userDTO);

	}

}

result:

UserDTO (userId = user001, userName = test, age = 18, gender = male, addrZipCode = 984-0031, addrAddress1 = ○○ prefecture, addrAddress2 = null, addrAddress3 = null)

Explicit Mapping

package com.test.lombok;

import org.modelmapper.ModelMapper;
import org.modelmapper.TypeMap;

public class TestMain {

	public static void main(String[] args) throws Exception {
		ModelMapper modelMapper = new ModelMapper();

		User user = new User();
		user.setUserId("user001");
		user.setUserName("test");
		user.setAge("18");
		user.setGender("male");
		Address addr = new Address();
		addr.setZipCode("984-0031");
		addr.setAddress1("○○ prefecture");
		user.setAddr(addr);

		TypeMap<User, UserDTO> typeMap = modelMapper.typeMap(User.class, UserDTO.class).addMappings(mapper -> {
			mapper.map(src -> src.getAddr().getZipCode(), UserDTO::setAddrZipCode);
			mapper.map(src -> src.getAddr().getAddress1(), UserDTO::setAddrAddress1);
		});

		System.out.println(typeMap.map(user));

	}

}

result:

UserDTO (userId = user001, userName = test, age = 18, gender = male, addrZipCode = 984-0031, addrAddress1 = ○○ prefecture, addrAddress2 = null, addrAddress3 = null)

It is convenient to use it to convert Form Bean to your own DTO with SpringBoot Controller.

MapStruct
https://mapstruct.org/

How to use

Added to build.gradle

build.gradle


plugins {
    id 'java'
    id 'net.ltgt.apt' version '0.20'
}

repositories {
    jcenter()
}

dependencies {
    // lombok
    compileOnly "org.projectlombok:lombok:1.18.12"
    annotationProcessor "org.projectlombok:lombok:1.18.12"
    
	// https://mvnrepository.com/artifact/org.mapstruct/mapstruct
	implementation  group: 'org.mapstruct', name: 'mapstruct', version: '1.3.1.Final'
	
	// https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor
	annotationProcessor group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.3.1.Final'
    
}

[compileJava, compileTestJava]*.options*.encoding= "UTF-8"

sourceSets {
    main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main"
}

Mapper interface

package com.test.lombok;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserMapper {
	UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

	@Mapping(source = "addr.zipCode", target = "addrZipCode")
	@Mapping(source = "addr.address1", target = "addrAddress1")
	@Mapping(source = "addr.address2", target = "addrAddress2")
	UserDTO userToUserDTO(User user);
}

After defining and building above, the UserMapperImpl.java class will be generated in build \ generated \ sources \ annotationProcessor \ java \ main \ com \ test \ lombok.

UserMapperImpl.java


package com.test.lombok;

import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-06-28T21:47:47+0900",
    comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDTO userToUserDTO(User user) {
        if ( user == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setAddrAddress2( userAddrAddress2( user ) );
        userDTO.setAddrAddress1( userAddrAddress1( user ) );
        userDTO.setAddrZipCode( userAddrZipCode( user ) );
        userDTO.setUserId( user.getUserId() );
        userDTO.setUserName( user.getUserName() );
        userDTO.setAge( user.getAge() );
        userDTO.setGender( user.getGender() );

        return userDTO;
    }

    private String userAddrAddress2(User user) {
        if ( user == null ) {
            return null;
        }
        Address addr = user.getAddr();
        if ( addr == null ) {
            return null;
        }
        String address2 = addr.getAddress2();
        if ( address2 == null ) {
            return null;
        }
        return address2;
    }

    private String userAddrAddress1(User user) {
        if ( user == null ) {
            return null;
        }
        Address addr = user.getAddr();
        if ( addr == null ) {
            return null;
        }
        String address1 = addr.getAddress1();
        if ( address1 == null ) {
            return null;
        }
        return address1;
    }

    private String userAddrZipCode(User user) {
        if ( user == null ) {
            return null;
        }
        Address addr = user.getAddr();
        if ( addr == null ) {
            return null;
        }
        String zipCode = addr.getZipCode();
        if ( zipCode == null ) {
            return null;
        }
        return zipCode;
    }
}

Test code

package com.test.lombok;

public class TestMain {

	public static void main(String[] args) throws Exception {

		User user = new User();
		user.setUserId("user001");
		user.setUserName("test");
		user.setAge("18");
		user.setGender("male");
		Address addr = new Address();
		addr.setZipCode("984-0031");
		addr.setAddress1("○○ prefecture");
		user.setAddr(addr);


		UserDTO userDTO = UserMapper.INSTANCE.userToUserDTO(user);

		System.out.println(userDTO);

	}
}

result:

UserDTO (userId = user001, userName = test, age = 18, gender = male, addrZipCode = 984-0031, addrAddress1 = ○○ prefecture, addrAddress2 = null, addrAddress3 = null)

ClassNotFound error

Exception in thread "main" java.lang.ExceptionInInitializerError
	at com.test.lombok.TestMain.main(TestMain.java:18)
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot find implementation for com.test.lombok.UserMapper
	at org.mapstruct.factory.Mappers.getMapper(Mappers.java:61)
	at com.test.lombok.UserMapper.<clinit>(UserMapper.java:9)
	... 1 more
Caused by: java.lang.ClassNotFoundException: Cannot find implementation for com.test.lombok.UserMapper
	at org.mapstruct.factory.Mappers.getMapper(Mappers.java:75)
	at org.mapstruct.factory.Mappers.getMapper(Mappers.java:58)

Add sourceSets to build.gradle

build.gradle


sourceSets {
    main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main"
}

Details Doc: https://mapstruct.org/documentation/stable/reference/html/

Dozer
https://github.com/DozerMapper/dozer

How to use

Added to build.gradle

build.gradle


// https://mvnrepository.com/artifact/com.github.dozermapper/dozer-core
compile group: 'com.github.dozermapper', name: 'dozer-core', version: '6.5.0'

Test code

package com.test.lombok;

import org.modelmapper.ModelMapper;
import org.modelmapper.TypeMap;

import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;

public class TestMain {

	public static void main(String[] args) throws Exception {

		User user = new User();
		user.setUserId("user001");
		user.setUserName("test");
		user.setAge("18");
		user.setGender("male");
		Address addr = new Address();
		addr.setZipCode("984-0031");
		addr.setAddress1("○○ prefecture");
		user.setAddr(addr);

		Mapper mapper = DozerBeanMapperBuilder.buildDefault();
		UserDTO userDTO = mapper.map(user, UserDTO.class);

		System.out.println(userDTO);

	}
}

result:

UserDTO (userId = user001, userName = test, age = 18, gender = male, addrZipCode = null, addrAddress1 = null, addrAddress2 = null, addrAddress3 = null)

The same item name was copied, but otherwise null.

In case of different items, it can be defined by XML, API and annotation.

If the item name is different, it can be handled by setting like @Mapping ("binaryData") , but if it is a complicated type, it must be set in XML.

I think you can choose your favorite library.

that’s all