헤르메스 LIFE

[ModelMapper] Object to Object 매핑 본문

Core Java

[ModelMapper] Object to Object 매핑

헤르메스의날개 2023. 4. 26. 16:23
728x90

Object 변환에는 여러 가지 툴이 있습니다.

ObjectMapper 나 BeanUtil 등입니다.

https://hermeslog.tistory.com/483

 

[Jackson] ObjectMapper, String to Map, to List, Object to JSON

Object를 Map으로 변경 Json 데이터를 Map으로 변경 Json 데이터를 List로 변경 Object를 Json으로 변경 package test.json; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import

hermeslog.tistory.com

 

ModelMapper 라는 Utility를 테스트 했습니다.

http://modelmapper.org/getting-started/

 

ModelMapper - Getting Started

Getting Started This section will guide you through the setup and basic usage of ModelMapper. Setting Up If you’re a Maven user just add the modelmapper library as a dependency: org.modelmapper modelmapper 3.0.0 Otherwise you can download the latest Mode

modelmapper.org


package test.mapper;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        
        Order order = new Order(
                new Customer(new Name("홍", "길동")),
                new Address("용산구", "서울"));
        
        OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);
        // [실행 결과]
        System.out.println("orderDTO :: " + orderDTO);
        
        // 정확히 일치하는 필드만 매핑
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        orderDTO = modelMapper.map(order, OrderDTO.class);
        
        // [실행 결과]
        System.out.println("STRICT orderDTO :: " + orderDTO);
        
        // 지능적인 매핑
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STANDARD);
        orderDTO = modelMapper.map(order, OrderDTO.class);
        
        // [실행 결과]
        System.out.println("STANDARD orderDTO :: " + orderDTO);
        
        // 느슨한 매핑
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
        orderDTO = modelMapper.map(order, OrderDTO.class);
        // [실행 결과]
        System.out.println("LOOSE orderDTO :: " + orderDTO);
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class OrderDTO {
    private String customerFirstName;
    private String customerLastName;
    private String billingStreet;
    private String billingCity;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Order {
    private Customer customer;
    private Address  billingAddress;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Customer {
    private Name name;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Name {
    private String firstName;
    private String lastName;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Address {
    private String street;
    private String city;
}

[실행결과]
orderDTO :: OrderDTO(customerFirstName=홍, customerLastName=길동, billingStreet=용산구, billingCity=서울)
STRICT orderDTO :: OrderDTO(customerFirstName=홍, customerLastName=길동, billingStreet=용산구, billingCity=서울)
STANDARD orderDTO :: OrderDTO(customerFirstName=홍, customerLastName=길동, billingStreet=용산구, billingCity=서울)
LOOSE orderDTO :: OrderDTO(customerFirstName=홍, customerLastName=길동, billingStreet=용산구, billingCity=서울)

https://modelmapper.org/user-manual/configuration/

 

ModelMapper - Configuration

Configuration ModelMapper uses a set of conventions and configuration to determine which source and destination properties match each other. Available configuration, along with default values, is described below: Setting Description Default Value Ambiguity

modelmapper.org

사용 가능한 규약

ModelMapper에는 다양한 속성 일치 요구사항을 처리하기 위해 사전 정의된 규약이 포함되어 있습니다.

규약 설명

규약 설명
NamingConventions.NONE 모든 속성 이름에 적용되는 명명 규칙 없음
NamingConventions.JAVABEANS_ACCESSOR JavaBeans 규칙에 따라 적합한 접근자를 찾습니다.
NamingConventions.JAVABEANS_MUTATOR JavaBeans 규칙에 따라 적합한 mutators(돌연변이) 를 찾습니다.
NameTransformers.JAVABEANS_ACCESSOR JavaBeans 규칙에 따라 접근자 이름 변환
NameTransformers.JAVABEANS_MUTATOR JavaBeans 규칙에 따라 변환자 이름 변환
NameTokenizers.CAMEL_CASE Camel Case 규칙에 따라 속성 토큰(Token) 및 클래스 이름
NameTokenizers.UNDERSCORE underscores에 의한 속성 토큰(Token)과 클래스 이름
MatchingStrategies.STANDARD 소스와 대상 속성을 지능적으로 일치 시킴
MatchingStrategies.LOOSE 소스와 대상 속성을 느슨하게 일치 시킴
MatchingStrategies.STRICT 소스와 대상 속성을 엄격하게 일치 시킴

매핑전략

  • MatchingStrategies.STRICT : 정확히 일치하는 필드만 매핑
  • MatchingStrategies.STANDARD : 지능적인 매핑
  • MatchingStrategies.LOOSE : 느슨한 매핑

설정 예제

  • Protected 필드 매핑
    modelMapper.getConfiguration().setMethodAccessLevel(AccessLevel.PROTECTED);
  • Private 필드 매핑
    modelMapper.getConfiguration().setFieldMatchingEnabled(true).setFieldAccessLevel(AccessLevel.PRIVATE);

 

Token (토큰) 이란?
Camel 표기법 : billingCity -> Billing 과 City 두개의 토큰
Underscore 표기법 : billing_city -> Billing 과 City 두개의 토큰

package test.mapper;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        Item        itemA       = new Item("상품", 10, 1500, true);
        Bill        bill        = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
        // 정확히 일치하는 필드만 매핑
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        bill = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("STRICT bill :: " + bill);
        
        Bill2 bill2 = modelMapper.map(itemA, Bill2.class);
        
        // [실행 결과]
        System.out.println("STRICT bill2 :: " + bill2);        
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  name;
    private Integer stock;
    private Integer price;
    private Boolean discountYn;
}

@Getter
@Setter
@ToString
class Bill {
    private Integer stock;
    private Integer price;
    private String  name;
    private Boolean discountYn;
}


@Getter
@Setter
@ToString
class Bill2 {
    private String  name1;
    private Integer stock1;
    private Integer price1;
    private Boolean discountYn1;
}

[실행결과]
bill :: Bill(stock=10, price=1500, name=상품, discountYn=true)
STRICT bill :: Bill(stock=10, price=1500, name=상품, discountYn=true)
STRICT bill2 :: Bill2(name1=null, stock1=null, price1=null, discountYn1=null)

MatchingStrategies.STRICT

  • 토큰들은 엄격한 순서로 일치해야 한다
  • 모든 destination 속성 이름 토큰이 반드시 일치해야 한다
  • 모든 source 속성 이름과 모든 토큰이 일치해야 한다
package test.mapper;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        Item        itemA       = new Item("상품", 10, 1500, true);
        Bill        bill        = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
        // 정확히 일치하는 필드만 매핑
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STANDARD);
        bill = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("STANDARD bill :: " + bill);
        
        Bill2 bill2 = modelMapper.map(itemA, Bill2.class);
        
        // [실행 결과]
        System.out.println("STANDARD bill2 :: " + bill2);        
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  name;
    private Integer stock;
    private Integer price;
    private Boolean discountYn;
}

@Getter
@Setter
@ToString
class Bill {
    private Integer stock;
    private Integer price;
    private String  name;
    private Boolean discountYn;
}


@Getter
@Setter
@ToString
class Bill2 {
    private Float stock;
    private Integer price2;
    private String  name;
    private Boolean discountYn;
}

[실행결과]
bill :: Bill(stock=10, price=1500, name=상품, discountYn=true)
STRICT bill :: Bill(stock=10, price=1500, name=상품, discountYn=true)
STRICT bill2 :: Bill2(stock=10.0, price2=null, name=상품, discountYn=true)

MatchingStrategies.STANDARD

  • 토큰은 어떤 순서로든 일치될 수 있다.
  • 모든 destination 속성 이름 토큰이 일치해야 한다.
  • 모든 source 속성 이름은 일치하는 토큰이 하나 이상 있어야 한다.
package test.mapper;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        Item        itemA       = new Item("상품", 10, 1500, true);
        Bill        bill        = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
        // 정확히 일치하는 필드만 매핑
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
        bill = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("LOOSE bill :: " + bill);
        
        Bill2 bill2 = modelMapper.map(itemA, Bill2.class);
        
        // [실행 결과]
        System.out.println("LOOSE bill2 :: " + bill2);        
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  name;
    private Integer stock;
    private Integer price;
    private Boolean discountYn;
}

@Getter
@Setter
@ToString
class Bill {
    private Integer stock;
    private Integer price;
    private String  name;
    private Boolean discountYn;
}


@Getter
@Setter
@ToString
class Bill2 {
    private String  name;
    private String  name1;
    private Integer stock1;
    private Integer price;
    private Integer price1;
    private Boolean adiscountYn;
    private Boolean discountYn;
}

[실행결과]
bill :: Bill(stock=10, price=1500, name=상품, discountYn=true)
STRICT bill :: Bill(stock=10, price=1500, name=상품, discountYn=true)
STRICT bill2 :: Bill2(name=상품, name1=상품, stock1=10, price=1500, price1=1500, adiscountYn=true, discountYn=true)

MatchingStrategies.LOOSE

  • 토큰은 어떤 순서로든 일치될 수 있다
  • 마지막 destination 속성 이름에는 모든 토큰이 일치해야 한다
  • 마지막 source 속성 이름은 일치하는 토큰이 하나 이상 있어야 한다

그러면, 이런 경우는 어떻게 될까요..?

package test.mapper;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        Item        itemA       = new Item("상품", 10, 1500, 3000, true, false);
        Bill        bill        = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
        // 정확히 일치하는 필드만 매핑
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
        bill = modelMapper.map(itemA, Bill.class);
        
        // [실행 결과]
        System.out.println("LOOSE bill :: " + bill);
        
        Bill2 bill2 = modelMapper.map(itemA, Bill2.class);
        
        // [실행 결과]
        System.out.println("LOOSE bill2 :: " + bill2);        
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  name;
    private Integer stock;
    private Integer priceA;
    private Integer priceB;
    private Boolean discountYnA;
    private Boolean discountYnB;
}

@Getter
@Setter
@ToString
class Bill {
    private Integer stock;
    private Integer priceB;
    private Integer priceA;
    private String  name;
    private Boolean discountYnB;
    private Boolean discountYnA;
}


@Getter
@Setter
@ToString
class Bill2 {
    private String  name;
    private String  name1;
    private Integer stock1;
    private Integer price;
    private Integer price1;
    private Boolean adiscountYn;
    private Boolean discountYn;
}

[실행결과]
bill :: Bill(stock=10, priceB=3000, priceA=1500, name=상품, discountYnB=false, discountYnA=true)
STRICT bill :: Bill(stock=10, priceB=3000, priceA=1500, name=상품, discountYnB=false, discountYnA=true)
Exception in thread "main" org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) The destination property test.mapper.Bill2.setPrice() matches multiple source property hierarchies:

	test.mapper.Item.getPriceA()
	test.mapper.Item.getPriceB()

2) The destination property test.mapper.Bill2.setPrice1() matches multiple source property hierarchies:

	test.mapper.Item.getPriceA()
	test.mapper.Item.getPriceB()

3) The destination property test.mapper.Bill2.setDiscountYn() matches multiple source property hierarchies:

	test.mapper.Item.getDiscountYnB()
	test.mapper.Item.getDiscountYnA()

4) The destination property test.mapper.Bill2.setAdiscountYn() matches multiple source property hierarchies:

	test.mapper.Item.getDiscountYnB()
	test.mapper.Item.getDiscountYnA()

4 errors
	at org.modelmapper.internal.Errors.throwConfigurationExceptionIfErrorsExist(Errors.java:241)
	at org.modelmapper.internal.ImplicitMappingBuilder.matchDestination(ImplicitMappingBuilder.java:159)
	at org.modelmapper.internal.ImplicitMappingBuilder.build(ImplicitMappingBuilder.java:90)
	at org.modelmapper.internal.ImplicitMappingBuilder.build(ImplicitMappingBuilder.java:75)
	at org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:131)
	at org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:106)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:112)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:71)
	at org.modelmapper.ModelMapper.mapInternal(ModelMapper.java:573)
	at org.modelmapper.ModelMapper.map(ModelMapper.java:406)
	at test.mapper.ModelMapperSample.main(ModelMapperSample.java:30)

오류 나네요. ㅎㅎ


Mismatching 조작하기

package test.mapper;

import org.modelmapper.ModelMapper;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        Item        item        = new Item("상품", 10, 1500, true);
        Bill        bill        = modelMapper.map(item, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
        modelMapper.typeMap(Item.class, Bill.class).addMappings(mapper -> {
            mapper.map(Item::getStock, Bill::setQty);
            mapper.map(Item::getPrice, Bill::setSinglePrice);
        });
        
        Bill bill2 = modelMapper.map(item, Bill.class);
        
        // [실행 결과]
        System.out.println("bill2 :: " + bill2);
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  name;
    private Integer stock;
    private Integer price;
    private Boolean sale;
}

@Getter
@Setter
@ToString
class Bill {
    private String  itemName;
    private Integer qty;
    private Integer singlePrice;
    private Double  discount;
}

[실행결과]
bill :: Bill(itemName=상품, qty=null, singlePrice=null, discount=null)
bill2 :: Bill(itemName=상품, qty=10, singlePrice=1500, discount=null)

Convert 타입 변환

package test.mapper;

import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        Item        item        = new Item("상품", 10, 1500, true);
        Bill        bill        = modelMapper.map(item, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
        modelMapper.typeMap(Item.class, Bill.class).addMappings(mapper -> {
            mapper.map(Item::getStock, Bill::setQty);
            mapper.map(Item::getPrice, Bill::setSinglePrice);
            mapper.using((Converter<Boolean, Double>) context -> context.getSource() ? 20.0 : 0.0)
                    .map(Item::getIsSale, Bill::setDiscount);
        });
        
        Bill bill2 = modelMapper.map(item, Bill.class);
        
        // [실행 결과]
        System.out.println("bill2 :: " + bill2);
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  name;
    private Integer stock;
    private Integer price;
    private Boolean isSale;
}

@Getter
@Setter
@ToString
class Bill {
    private String  itemName;
    private Integer qty;
    private Integer singlePrice;
    private Double  discount;
}

[실행결과]
bill :: Bill(itemName=상품, qty=null, singlePrice=null, discount=null)
bill2 :: Bill(itemName=상품, qty=10, singlePrice=1500, discount=20.0)

전달하고 싶지 않은 필드 :: Skip

package test.mapper;

import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        Item        item        = new Item("상품", 10, 1500, true);
        Bill        bill        = modelMapper.map(item, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
        modelMapper.typeMap(Item.class, Bill.class).addMappings(mapper -> {
            mapper.map(Item::getStock, Bill::setQty);
            mapper.map(Item::getPrice, Bill::setSinglePrice);
            mapper.using((Converter<Boolean, Double>) context -> context.getSource() ? 20.0 : 0.0)
                    .map(Item::getIsSale, Bill::setDiscount);
            mapper.skip(Bill::setItemName); // skip 추가
        });
        
        Bill bill2 = modelMapper.map(item, Bill.class);
        
        // [실행 결과]
        System.out.println("bill2 :: " + bill2);
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  name;
    private Integer stock;
    private Integer price;
    private Boolean isSale;
}

@Getter
@Setter
@ToString
class Bill {
    private String  itemName;
    private Integer qty;
    private Integer singlePrice;
    private Double  discount;
}

[실행결과]
bill :: Bill(itemName=상품, qty=null, singlePrice=null, discount=null)
bill2 :: Bill(itemName=null, qty=10, singlePrice=1500, discount=20.0)

Camel Case 에서 Underscore 로 변환

package test.mapper;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.NameTokenizers;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

public class ModelMapperSample {
    
    public static void main(String... args) {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration()
                .setSourceNameTokenizer(NameTokenizers.CAMEL_CASE)
                .setDestinationNameTokenizer(NameTokenizers.UNDERSCORE);
        
        Item item = new Item("상품", 10, 1500, 2.0);
        Bill bill = modelMapper.map(item, Bill.class);
        
        // [실행 결과]
        System.out.println("bill :: " + bill);
        
    }
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Item {
    private String  itemName;
    private Integer itemQty;
    private Integer singlePrice;
    private Double  itemDiscount;
}

@Getter
@Setter
@ToString
class Bill {
    private String  item_name;
    private Integer item_qty;
    private Integer single_price;
    private Double  item_discount;
}

https://hermeslog.tistory.com/483

 

[Jackson] ObjectMapper, String to Map, to List, Object to JSON

Object를 Map으로 변경 Json 데이터를 Map으로 변경 Json 데이터를 List로 변경 Object를 Json으로 변경 package test.json; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import

hermeslog.tistory.com

 

728x90