[Java] 역직렬화(Deserialize)
어떤 요청에 대한 응답으로 JSON을 많이 이용하고 있다. 이때 필요한 Data만 전달받는 DTO를 만들고 적용해보자.
(카카오톡 챗봇 예제를 이용해 진행했습니다.)
1. 역직렬화(Deserialize)란?
- byte로 변환된 Data를 원래대로 변환하는 기술을 역직렬화(Deserialize)라고 부른다.
- Json → Java Object 변환
2. 예제
카카오 챗봇에서는 요청과 응답을 JSON Data로 주고 받는다. 전달된 JSON Data를 통해 사용자를 식별하거나, 사용자가 원하는 동작을 파악할 수 있고 데이터를 컨트롤할 수 있게 된다. 하지만 내가 사용하고자 하는 정보가 명확히 정해져 있고 한정적이라면 보내지는 JSON Data를 모두 받아 처리할 필요가 없다.
예를 들면,
{
"intent": {
"id": "5e4381918192ac000135e4fd",
"name": "start_chatbot",
"extra": {
"reason": {
"code": 101,
"message": "DIRECT_ID"
}
}
},
"userRequest": {
"timezone": "Asia/Seoul",
"params": {
"surface": "Kakaotalk.plusfriend"
},
"block": {
"id": "5e4381918192ac000135e4fd",
"name": "start_chatbot"
},
"utterance": "?? ????",
"lang": "ko",
"user": {
"id": "3800646c7318e785e6f9e60b94368e5d0029f4b38ab347bc44e08",
"type": "botUserKey",
"properties": {
"botUserKey": "3800646c7318e785e6f9e60b94368e5d0029f4b38ab347bc44e08",
"appUserStatus": "REGISTERED",
"app_user_status": "REGISTERED",
"app_user_id": "1290980424",
"plusfriendUserKey": "HfzparVPNY_s",
"appUserId": "1290980424",
"bot_user_key": "3800646c7318e785e6f9e60b94368e5d0029f4b38ab347bc44e08",
"plusfriend_user_key": "HfzparVPNY_s"
}
}
},
"contexts": [
{
"name": "Test_info",
"lifespan": 5,
"ttl": 600,
"params": {
"Test_Date": {
"value": "??? ??? ????",
"resolvedValue": "{\"from\": {\"date\": \"2020-03-01\"}, \"to\": {\"date\": \"2020-02-29\"}}"
}
}
}
],
"bot": {
"id": "5e40a08db617ea000",
"name": "??? RND?? ??"
},
"action" : {
"name" : "otp_url",
"clientExtra" : { },
"params" : {
"member_profile" : "{\"otp\":\"https://talk-plugin.kakao.com/otp/5e57269e4b1b3c/profile\",\"app_user_id\":129094}"
},
"id" : "5e54b8b38192ac0001377a46",
"detailParams" : {
"member_profile" : {
"origin" : "https://talk-plugin.kakao.com/otp/5e57269e4b1b3c/profile",
"value" : "{\"otp\":\"https://talk-plugin.kakao.com/otp/5e57269e4b1b3c/profile\",\"app_user_id\":129094}",
"groupName" : ""
}
}
}
}
위와 같은 요청에 대한 응답이 왔다고 가정해보자.
꽤나 복잡하고 어떤 정보를 가공해서 사용할지 한눈에 알아보기 힘들다. 많은 데이터들 중 내가 사용하고 싶은 데이터는 userRequest와 action, 사용자가 요청한 날짜 정보를 담고 있는 contexts라고 한다면 아래와 같이 데이터를 표현할 수 있다.
- userRequest - user - properties - appUserId
- contexts - params - Test_Date - resolvedValue
- action - detailParams - member_profile - origin
위 내용을 간략히 정리해보면 다음과 같은 Json 형태가 된다.
userRequest & contexts
{
"userRequest": {
"user": {
"properties": {
"appUserId": "1290980424",
}
}
},
"contexts": [
{
"params": {
"Test_Date": {
"resolvedValue": "{\"from\": {\"date\": \"2020-03-01\"}, \"to\": {\"date\": \"2020-02-29\"}}"
}
}
}
]
}
action
{
"action" : {
"detailParams" : {
"member_profile" : {
"origin" : "https://talk-plugin.kakao.com/otp/5e57b5e4f842692e7e4b1b3c/profile",
}
}
}
}
3. DTO 만들기
이제 내가 사용하고자 하는 Json 데이터의 형태가 정해졌고, 이를 DTO로 만들 수 있다.
DTO Generator를 이용하여 위에서 정리한 JSON 형태를 만족하는 DTO를 만든다. 이번에는 직렬화와 다르게 Inner Class로 구성하여 만들었다.
1. RequestDTO라는 Package를 만들고 아래에 Class파일을 구성
2. ActionDTO
action을 받는 DTO
- @JsonIgnoreProperties → 필드와 Mapping 되지 않는 값은 무시한다.
@JsonIgnoreProperties를 사용하지 않으면 Mapping 오류가 나서 Json DataType과 동일한 구조를 가지는 DTO를 만들어 줘야 한다.
@JsonIgnoreProperties(ignoreUnknown = true)
public class ActionDTO {
@JsonProperty("action")
private Action action;
public Action getAction() {
return action;
}
public void setAction(Action action) {
this.action = action;
}
public static class Action {
@JsonProperty("detailParams")
private DetailParams detailParams;
public DetailParams getDetailParams() {
return detailParams;
}
public void setDetailParams(DetailParams detailParams) {
this.detailParams = detailParams;
}
}
public static class DetailParams {
@JsonProperty("member_profile")
private MemberProfile memberProfile;
public MemberProfile getMemberProfile() {
return memberProfile;
}
public void setMemberProfile(MemberProfile memberProfile) {
this.memberProfile = memberProfile;
}
}
public static class MemberProfile {
@JsonProperty("origin")
private String origin;
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
3. ContextDTO
contexts와 userRequest를 받는 DTO
@JsonIgnoreProperties(ignoreUnknown = true)
public class ContextDTO {
@JsonProperty("contexts")
private List<Contexts> contexts;
@JsonProperty("userRequest")
private UserRequest userRequest;
public List<Contexts> getContexts() {
return contexts;
}
public void setContexts(List<Contexts> contexts) {
this.contexts = contexts;
}
public UserRequest getUserRequest() {
return userRequest;
}
public void setUserRequest(UserRequest userRequest) {
this.userRequest = userRequest;
}
public static class Contexts {
@JsonProperty("params")
private Params params;
public Params getParams() {
return params;
}
public void setParams(Params params) {
this.params = params;
}
}
public static class Params {
@JsonProperty("Test_Date")
private TestDate testDate;
public TestDate getTestDate() {
return testDate;
}
public void setTestDate(TestDate testDate) {
this.testDate = testDate;
}
}
public static class TestDate {
@JsonProperty("resolvedValue")
private String resolvedValue;
public String getResolvedValue() {
return resolvedValue;
}
public void setResolvedValue(String resolvedValue) {
this.resolvedValue = resolvedValue;
}
}
public static class UserRequest {
@JsonProperty("user")
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
public static class User {
@JsonProperty("properties")
@JsonAlias({ "property", "properties" })
private Properties properties;
public Properties getProperty() {
return properties;
}
public void setProperty(Properties properties) {
this.properties = properties;
}
}
public static class Properties {
@JsonProperty("appUserId")
private String appUserId;
public String getAppUserId() {
return appUserId;
}
public void setAppUserId(String appUserId) {
this.appUserId = appUserId;
}
}
}
4. Request Data 전달받기
Controller에서 @RequestBody 애노테이션과 함께 Mapping 시킬 DTO를 명시하면 요청의 body 내용(Json)을 자바 객체로 매핑해준다.
public MessageDTO createKey(@RequestBody ContextDTO contextDTO) throws Exception {
.........
.........
}
HTTP의 Header를 먼저 확인해서 DataType에 맞는 형태로 자동 매핑되는 것이다.
내가 사용하고 싶은 데이터 형태를 DTO로 만들고 이를 @RequestBody의 파라미터로 설정하면 JSON으로 들어오는 데이터 형태를 간단하게 컨트롤할 수 있게 된다.
이 방법 외에 JsonDeserializer라는 추상 클래스를 상속받아 Jackson Custom Deserializer를 만들 수 있다. Deserializer를 먼저 거치고 형태에 맞는 DTO에 값을 넣어주는 형태로 사용된다. 이 방법에 대한 내용은 추후 기회가 되면 올릴 예정이다.