ModelMapper, Jackson,MessageConverter
훌륭한 질문입니다!
ModelMapper
는 DTO ↔ Entity 간의 명시적 매핑을 위해 사용되는 라이브러리이고,
Jackson
은 HTTP 요청/응답 본문 ↔ Java 객체 간의 직렬화/역직렬화를 담당합니다.
🔍 질문 요지
ModelMapper.map(...)
처럼 Jackson도 내부에서 "기본 생성자 → setter로 세팅" 흐름을 쓰는데, 그 과정을 명시적으로 코드로 작성한다면 어떻게 될까?
✅ ModelMapper
스타일
ModelMapper
스타일public ProductDto add(ProductDto productDto) {
Product product = modelMapper.map(productDto, Product.class);
Product savedProduct = repository.save(product);
ProductDto result = modelMapper.map(savedProduct, ProductDto.class);
return result;
}
✅ Jackson
이 자동으로 하는 일을 수동으로 구현하면?
Jackson
이 자동으로 하는 일을 수동으로 구현하면?예를 들어 다음과 같은 JSON 요청을 받는다고 가정해봅시다:
{
"name": "치킨",
"price": 12000
}
이를 DTO로 바꾸는 것은 일반적으로 다음과 같이 Spring이 자동으로 해줍니다:
@PostMapping("/products")
public ResponseEntity<ProductDto> createProduct(@RequestBody ProductDto productDto) {
...
}
하지만 이 자동 역직렬화 과정을 수동으로 구현하면 다음과 같이 할 수 있어요.
✅ Jackson 수동 역직렬화 + 직렬화 예제
@PostMapping("/products")
public ResponseEntity<String> createProduct(HttpServletRequest request) throws IOException {
// 1. 요청 바디에서 JSON 직접 읽기
String json = request.getReader().lines().collect(Collectors.joining());
// 2. ObjectMapper로 JSON → ProductDto
ObjectMapper objectMapper = new ObjectMapper();
ProductDto productDto = objectMapper.readValue(json, ProductDto.class);
// 3. 로직 처리
Product product = new Product();
product.setName(productDto.getName());
product.setPrice(productDto.getPrice());
Product saved = repository.save(product);
// 4. 다시 DTO로 변환
ProductDto responseDto = new ProductDto();
responseDto.setId(saved.getId());
responseDto.setName(saved.getName());
responseDto.setPrice(saved.getPrice());
// 5. ObjectMapper로 DTO → JSON 직렬화
String responseJson = objectMapper.writeValueAsString(responseDto);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(responseJson);
}
🔍 이 흐름을 단계별로 보면:
1. JSON 읽기
HTTP 메시지 바디 추출
request.getReader()
2. 역직렬화
JSON → DTO (@RequestBody
)
objectMapper.readValue()
3. 비즈니스 로직
사용자가 직접 작성
repository.save(...)
4. DTO 작성
return ProductDto
자동 처리
직접 DTO 생성 및 세팅
5. 직렬화
DTO → JSON (@ResponseBody
)
objectMapper.writeValueAsString()
✅ 핵심 요약
목적
객체 ↔ 객체 (DTO ↔ Entity)
JSON ↔ 객체 (직렬화/역직렬화)
생성 흐름
기본 생성자 + setter
기본 생성자 + setter
자동 사용 위치
서비스/컨트롤러 내부
컨트롤러 @RequestBody
, @ResponseBody
수동 구현 방식
map(A, B)
readValue(json, Class)
, writeValueAsString(obj)
좋습니다! 이제 이어서 Jackson의 동작 원리, @JsonCreator
, @JsonProperty
, 커스텀 직렬화/역직렬화 등 실무에서 자주 쓰이는 확장 기법까지 단계별로 설명드릴게요.
✅ 1. Jackson의 기본 동작 방식
Jackson의 ObjectMapper
는 다음과 같은 순서로 객체를 생성하고 값을 주입합니다:
🔸 역직렬화 (JSON → Java 객체)
기본 생성자(no-arg constructor) 호출
JSON 키에 해당하는 setter 또는 public 필드에 값을 주입
public class ProductDto {
private String name;
private int price;
public ProductDto() {} // 기본 생성자 필수
public void setName(String name) { this.name = name; }
public void setPrice(int price) { this.price = price; }
}
✅ 2. @JsonCreator
+ 생성자 기반 매핑
@JsonCreator
+ 생성자 기반 매핑불변 객체(immutable object)나 final
필드만 있는 객체를 매핑하려면
setter 없이도 생성자 기반으로 매핑해야 합니다.
public class ProductDto {
private final String name;
private final int price;
@JsonCreator
public ProductDto(@JsonProperty("name") String name,
@JsonProperty("price") int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
💡 이 경우 Jackson은
ProductDto
를 생성할 때 이 생성자를 사용하며, setter가 없어도 역직렬화됩니다.
✅ 3. 커스텀 직렬화: JsonSerializer
JsonSerializer
🔸 특정 객체를 JSON으로 변환할 때 포맷을 바꾸고 싶다면?
public class PriceSerializer extends JsonSerializer<Integer> {
@Override
public void serialize(Integer price, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(price + "원");
}
}
public class ProductDto {
private String name;
@JsonSerialize(using = PriceSerializer.class)
private int price;
// ...
}
결과:
{
"name": "치킨",
"price": "12000원"
}
✅ 4. 커스텀 역직렬화: JsonDeserializer
JsonDeserializer
🔸 문자열 "12000원"
→ 숫자 12000
으로 역직렬화
"12000원"
→ 숫자 12000
으로 역직렬화public class PriceDeserializer extends JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getText().replace("원", "");
return Integer.parseInt(value);
}
}
public class ProductDto {
private String name;
@JsonDeserialize(using = PriceDeserializer.class)
private int price;
// ...
}
입력 JSON:
{
"name": "치킨",
"price": "12000원"
}
✅ 5. 기타 유용한 Jackson 애노테이션
@JsonIgnore
해당 필드는 JSON에서 무시됨 (직렬화/역직렬화 모두)
@JsonInclude
null/빈값 제외 조건 지정
@JsonProperty("jsonName")
필드명 매핑 커스터마이징
@JsonFormat
날짜 포맷 등 형식 지정
@JsonView
조건에 따라 보이는 필드 조절
✅ 6. ObjectMapper 커스터마이징
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
불필요한 null 필드 제거
모르는 필드 있어도 실패하지 않도록 설정
✅ 마무리 요약
기본 동작
기본 생성자 + setter/public 필드
불변 객체 대응
@JsonCreator
+ @JsonProperty
생성자 사용
포맷 커스터마이징
JsonSerializer
, JsonDeserializer
구현 후 애노테이션 등록
고급 설정
ObjectMapper
커스터마이징으로 유연한 처리 가능
ModelMapper와 차이
ModelMapper는 객체 ↔ 객체 변환용, Jackson은 JSON ↔ 객체 변환용
Last updated