개념
Jackson을 공부하기 앞서 기본적인 용어를 알아보자.
- 직렬화
객체를 문자열로 변환하는 과정 - 역직렬화
반대로 문자열에서 객체를 변환하는 과정
Jackson은 위에서 언급한 직렬화, 역직렬화를 도와주는 자바 라이브러리다.
Jackson이 객체를 문자열로 변환해서 응답해주면 응답받은 곳은 문자열의 형태가 Json포맷을 하고 있어 해당 문자열을 받은 쪽은 Json으로 파싱이 가능한 것이다.
예제에 앞서 spring boot로 프로젝트를 생성하자.
다행스럽게 spring boot의 경우 Jackson라이브러리를 기본으로 제공한다.
즉 dependency를 따로 주입할 필요가 없다. (spring boot버전 따라 다를 수 it다!)
예제
Object to Json 기초 예제
자바 객체에서 Json포맷 형태의 문자열로 변환해보자.
import java.util.Map;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class PilDto {
private String value1 = "superpil";
private int value2 = 1;
private Map<String, String> value4;
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import lombok.extern.slf4j.Slf4j;
@SpringBootApplication
@Slf4j
public class DemoApplication {
public static void main(String[] args) throws InterruptedException, JsonProcessingException {
SpringApplication.run(DemoApplication.class, args);
ObjectMapper objectMapper = new ObjectMapper();
PilDto pilDto = new PilDto();
log.info("objectMapper : {}", objectMapper.writeValueAsString(pilDto));
}
}
👉 ObjectMapper objectMapper = new ObjectMapper()
- PilDto를 Json으로 변환하기 위해 jackson의 ObjectMapper를 생성한다.
👉 objectMapper.writeValueAsString(pilDto)
- {"value1":"superpil", "value2":1, "value4":null}로 변환되어 출력된다.
Json to Object 기초 예제
이번에는 반대로 Json에서 자바 객체로 변환 과정을 알아보자.
정확하게 말하면 Joson포맷으로 된 문자열을 자바 객체로 변환하는 과정이다.
만약, 문자열이 Json포맷이 아닌 경우 “JsonMappingException”을 맛보게 된다.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class PilDto {
private String value1;
private int value2;
private Map<String, String> value3 = new HashMap<>();
private List<String> value4 = new ArrayList<>();
}
👉 PilDto.class
- 문자열을 파싱한 값을 담기 위한 객체다.
- 기본 타입 String, int와 함께 Map, List까지 파싱 하여 객체에 담아보자.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class DataController {
@PostMapping("")
public void test(String jsonString) throws JsonMappingException, JsonProcessingException {
log.info("jsonString : {}", jsonString); // json포맷의 문자열
ObjectMapper mapper = new ObjectMapper();
PilDto pilDto = mapper.readValue(jsonString, PilDto.class);
log.info("pilDto : {}", pilDto);
}
}
👉 PilDto pilDto = mapper.readValue(jsonString, PilDto.class)
- readValue속성을 사용하여 문자열(jsonString)을 읽어 PilDto객체에 담는다.
- postman으로 요청 시 반드시 json포맷을 가진 문자열로 요청한다.
LocalDateTime 변환 이슈
import java.time.LocalDateTime;
import lombok.Getter;
@Getter
public class PilDto {
private LocalDateTime date = LocalDateTime.now();
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import lombok.extern.slf4j.Slf4j;
@SpringBootApplication
@Slf4j
public class DemoApplication {
public static void main(String[] args) throws InterruptedException, JsonProcessingException {
SpringApplication.run(DemoApplication.class, args);
// 예제
ObjectMapper mapper = new ObjectMapper();
PilDto pilDto = new PilDto();
mapper.writeValueAsString(pilDto);
}
}
📌 com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type java.time.LocalDateTime not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
Jackson으로 LocalDateTime을 직렬 화하게 되면 위 같은 예외가 발생한다.
해당 예외를 피하고 정상적으로 직렬화하기 위해서는 아래와 같이 Serializer과정을 거쳐야 한다.
CustomLocalDateTImeSerializer
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class CustomLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeString(formatter.format(value));
}
}
우선 objectMapper에 LocalDateTime 포맷을 등록하기 위해 JsonSerializer를 확장받아 원하는 포맷으로 설정이 필요하다.
👉 formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
- 원하는 포맷을 지정한다.
👉 gen.writeString(formatter.format(value))
- serialize메소드를 Override를 받아 포맷을 등록하면 기본 설정은 끝이다.
import java.time.LocalDateTime;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import lombok.extern.slf4j.Slf4j;
@SpringBootApplication
@Slf4j
public class DemoApplication {
public static void main(String[] args) throws InterruptedException, JsonProcessingException {
SpringApplication.run(DemoApplication.class, args);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(LocalDateTime.class, new CustomLocalDateTimeSerializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(simpleModule);
PilDto pilDto = new PilDto();
mapper.writeValueAsString(pilDto);
log.info("mapper.writeValueAsString(pilDto) : {}", mapper.writeValueAsString(pilDto));
}
}
👉 SimpleModule simpleModule = new SimpleModule()
- 방금 설정한 LocalDateTime포맷을 등록하기 위해 jackson의 simpleModule객체를 생성한다.
- simpleModule객체는 모듈 역할로써 objectMapper에 입맛에 맞게 모듈을 주입할 수 있다.
- 해당 예제는 LocalDateTime포맷만 모듈로 등록했지만 더욱 다양한 모듈을 objectMapper에 주입하여 사용할 수 있다.
👉 simpleModule.addSerializer(LocalDateTime.class, new CustomLocalDateTimeSerializer())
- simpleModule의 addSerializer메소드를 사용해서 포맷을 등록한다.
👉 mapper.registerModule(simpleModule)
- 이제 simpleModule을 objectMapper에 등록하면 jackson은 설정한 LocalDateTime포맷으로 파싱 하여 직렬화를 시켜준다.
실습 코드
jackson을 사용하게 된 이유인 AuthenticationEntryPoint를 custom 하기 위해서다.
commence메소드는 내가 원하는 객체로 return할 수 없고 HttpServletResponse response로만 응답 가능하다. 따라서 응답 값을 직렬화하여 json포맷으로 return 해주었다.
앞서 LocalDateTime에 대한 이슈를 핸들링하여 AuthenticationEntryPoint를 입맛에 맞게 수정한 코드다.
AuthenticationEntryPoint 커스텀한 내용은 주제에 벗어나고 LocalDateTime에 대한 이슈는 위에서 다뤘기 때문에 간략하게 작성된 코드만 남긴다.
import java.io.IOException;
import java.time.LocalDateTime;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.msgmart2.msg_mart2api.common.responseHandler.ResponseDto;
import com.msgmart2.msg_mart2api.config.jackson.CustomLocalDateTimeSerializer;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
/**
* 401(UnAuthorized) handler
* 인증과정에서 실패했거나 인증헤더(Authorization)를 요청하지 않은 경우
*/
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// response header 설정
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 응답객체 생성
ResponseDto resDto = ResponseDto
.builder()
.responseCode(ResponseCode.F_AUTH)
.build()
;
// 모듈 생성
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(LocalDateTime.class, new CustomLocalDateTimeSerializer());
// 모듈 등록
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(simpleModule);
// 응답
String resJson = objectMapper.writeValueAsString(resDto);
response.getWriter().println(resJson);
}
}
import java.time.LocalDateTime;
import lombok.Getter;
@Getter
public class ResponseDto {
private String responseCode;
private LocalDateTime responseDate = LocalDateTime.now();
}
마무리
기본적인 직렬화, 역직렬화 과정을 알아보고 직렬화 과정에서 발생하는 이슈(LocalDateTime직렬화)를 알아봤다.
jackson라이브러리는 이번 글에서 언급한 내용보다 훨씬 많은 기술을 제공한다.
그중에서 사용자들이 많이 필요로 하고 사용하는 속성에 대해서는 다음 글에서 다뤄보겠다.
잘못된 내용이나 보충이 필요한 내용이 있다면 댓글 남겨 주세요!
주니어 개발자에게 큰 도움이 됩니다! 감사합니다! 😃
Reference
- https://velog.io/@zooneon/Java-ObjectMapper%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-JSON-%ED%8C%8C%EC%8B%B1%ED%95%98%EA%B8%B0 - 복잡한 구조의 json to object
- https://recordsoflife.tistory.com/28 - jackson 속성
- https://d2.naver.com/helloworld/0473330 - jackson 구조
- https://www.baeldung.com/jackson-annotations - jackson 주요 속성
- https://github.com/FasterXML/jackson - jackson github
- https://homoefficio.github.io/2016/11/18/%EC%95%8C%EA%B3%A0%EB%B3%B4%EB%A9%B4-%EB%A7%8C%EB%A7%8C%ED%95%9C-Jackson-Custom-Serialization/ - objectMapper custom방법
'개발노트 > Spring' 카테고리의 다른 글
[Webflux] RSocket Basic (0) | 2022.04.03 |
---|---|
[디자인 패턴] Singleton Pattern (0) | 2022.03.27 |
[디자인 패턴] Observer Pattern (0) | 2022.03.13 |
[Spring Boot] Rest API 에 Spring Security Form 로그인 적용하기 (4) | 2021.12.27 |
[Spring Boot] 파일 다운로드 - 서버에서 다운 (1) | 2021.08.16 |
개발 기록
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!