Java의 특정 Class를 통째로 또는 Enum, Json array, Point.class를 deserialize 하기

728x90

역직렬화하는 과정에서 ClassCastException이 뜬 것을 경험했다면, 잘 찾아오셨다. 각 데이터 타입별로 역직렬화하는 방법을 간략하게 소개하고자 한다.

서론 - Json을 역직렬화할 때 겪는 문제


MSA에서 각 서비스간 비동기 메세지를 주고 받을 때 Json 형식으로 데이터를 주로 보내게 된다. Json으로 보낼 때는 Java 객체의 정보를 직렬화(serialization)하여 보내고, Json으로 받으면 다시 역직렬화(deserialization)하여 java 객체로 돌려놓게 된다.

java에서는 아래 dependency를 사용하면 json을 java로 쉽게 매핑 할 수 있다.

 

implementation 'org.json:json:20230227'



해당 dependency는 JSONObject 객체를 지원해준다. 아래와 같이 사용할 수 있다.

 

public void processMessage(String message) throws JsonProcessingException {
    JSONObject jsonObject = new JSONObject(message);
    JSONObject data = new JSONObject(jsonObject.get("data").toString());
    String storeId = (String) data.get("storeId");
}



이와 같이 하면 JSON으로 받은 message에 있는 storeId에 해당하는 문자열을 꺼내서 사용할 수 있다.

복잡한 JSON은 어떻게?

문제는 복잡한 JSON 형태를 역직렬화할 때이다. 데이터를 직렬화할 때는 아래와 같이 그냥 클래스를 통째로 JSONObject 객체에 주입해도 알아서 JSON으로 직렬화가 진행된다.

 

public class StoreSqsDto {
    private String storeId;
    private String name;
    private FoodKind foodKind;
    private String phoneNumber;
    private String address;
    private String addressDetail;
    private Point location;
    private String introduction;
    private boolean open;
}

 

public class SendingMessageConverter {
    public String createMessageToCreateStore(StoreSqsDto storeSqsDto){
        JSONObject jsonObject = new JSONObject();
        JSONObject data = new JSONObject(storeSqsDto);
        jsonObject.put("dataType", "store");
        jsonObject.put("method", "create");
        jsonObject.put("data", data);
        return jsonObject.toString();
    }
}



하지만 해당 JSON을 그냥 JAVA로 가져오려고 앞서 사용한대로 get() 메서드를 통해 가져오려고 하면 에러가 발생한다.

 

StoreSqsDto storeSqsDto = (StoreSqsDto) jsonObject.get("data");

 



이처럼 ClassCastException이 뜨게 된다. Java는 해당 JSON이 어떠한 객체에 매핑해줘야할지 전혀 모르기 때문에 발생하는 문제다

물론 JSONObject에서 특정 데이터를 get하여 꺼내고 String화 하여 다시 그 데이터를 JSONObject로 감싸고 다시 get하고.. 하는 식으로 요소 하나하나를 뒤지는 식으로 각각의 property를 꺼내고 다시 생성자를 통해 클래스화하는 방법을 사용할 수도 있지만, 이는 상당히 번거롭다.

이 글에서는 Jackson Library를 통해 JSONObject내 데이터를 통째로 클래스에 매핑하는 방법을 알아볼 것이다.

추가적으로 Enum 객체와 JsonArray, 그리고 Spring에서 제공하는 Geo data인 Point 객체를 역직렬화 하는 방법도 알아볼 것이다

각 CASE 별 역직렬화 방식

특정 클래스 형태로 통째로 역직렬화하기


본론부터 말하자면 Jackson Library의 ObjectMapper를 사용해야 한다. spring boot를 사용한다면 해당 라이브러리는 포함되어있으므로 따로 dependency를 추가하지 않아도 된다.

 

public MenuSqsDto convertMenuData(JSONObject jsonObject) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        String data = jsonObject.get("data").toString();
        return objectMapper.readValue(data, MenuSqsDto.class);
}


ObjectMapper를 사용하는 형식은 이와 같다. ObjectMapper 객체를 생성하고 String 형태의 Json을 readValue에 넣어줌과 동시에 변환 결과값으로 원하는 클래스를 두번째 인자로 넣어주면 된다. (Json은 반드시 String 형태로 넣어줘야 한다.)

이렇게 하면 끝이다. 결과로 원하는 형태의 객체를 바로 사용할 수 있게 된다.

Enum 역직렬화하기

직렬화하여 보낸 데이터가 Enum 데이터일 수도 있다. 보내는 쪽과 받는 쪽 서버에서 아래와 같은 Enum 데이터를 선언했다고 가정하자.

 

public enum OrderStatus {
    ORDER_REQUEST, ORDER_ACCEPT, ORDER_DENY, RIDER_ASSIGNED, FOOD_READY, DELIVERY_IN_PROGRESS, DELIVERY_COMPLETE, ORDER_CANCELED
}



보내는 쪽에서 해당 데이터를 String으로 변환하지 않고 그냥 Enum 상태로 아래처럼 직렬화 하여 보냈다면

 

public String createMessageToChangeOrderStatus(String orderId, OrderStatus orderStatus){
    JSONObject jsonObject = new JSONObject();
    JSONObject data = new JSONObject();
    jsonObject.put("dataType", "order");
    jsonObject.put("method", "change");
    data.put("orderId", orderId);
    data.put("orderStatus", orderStatus);
    jsonObject.put("data", data);
    return jsonObject.toString();
}

 


받는 쪽에서는 이를 String으로 역직렬화할 수 없다. 이때는 일반적인 get()이나 getString()이 아니라 아래처럼 getEnum()을 사용해줘야한다.

 

public void processOrderData(JSONObject jsonObject) throws JsonProcessingException {
        JSONObject data = new JSONObject(jsonObject.get("data").toString());
        String orderId = (String) data.get("orderId");
        OrderStatus orderStatus = data.getEnum(OrderStatus.class, "orderStatus");



JSON library는 이렇게 getEnum()메서드를 제공하여 Enum의 역직렬화도 손쉽게 할 수 있도록 기능을 제공하고 있다.

Json Array 역직렬화하기


JsonArray를 역직렬화 하려면 각 JsonArray에 있는 JsonObject를 하나하나씩 가져와 역직렬화하여 다시 리스트에 담는 형식으로 역직렬화 해야한다. 그렇기 때문에 반복문을 사용해야 한다.

 

ObjectMapper objectMapper = new ObjectMapper();
JSONArray orderMenuJsonArray = data.getJSONArray("menuInBasketList");
List<OrderMenu> orderMenuList = new ArrayList<>();
for (int i = 0; i < orderMenuJsonArray.length(); i++){
     String orderMenuString = orderMenuJsonArray.getJSONObject(i).toString();
     OrderMenu orderMenu = objectMapper.readValue(orderMenuString, OrderMenu.class);
     orderMenuList.add(orderMenu);
}

 


JsonObject인 data로 부터 "menuInBasketList"라는 key를 사용하여 JsonArray를 추출한다. 

그리고 역직렬화한 데이터를 담기 위한 ArrayList를 하나 선언한다.

반복문을 돌리면서 JsonArray로부터 JsonObject를 하나씩 추출하여 String으로 변환하고 objectMapper를 사용하여 각각의 JsonObject를 Java객체로 변환시킨다음 리스트에 add하는 것으로 마무리한다.

이렇게 하면 JsonArray를 Java의 List 객체로 가져올 수 있다.

Point.class 역직렬화하기


좀 특별한 녀석이라서 언급을 하려고 한다. Spring에서 제공하는 Geo 객체인데, 앞서 언급한 ObjectMapper로는 역직렬화가 되지 않는다. 

그러므로 Point 객체는 그냥 아래처럼 하나씩 데이터를 꺼내서 생성자를 통해 객체 생성을 할 수 밖에 없다. 

 

double storeX = data.getJSONObject("storeLocation").getDouble("x");
double storeY = data.getJSONObject("storeLocation").getDouble("y");
Point storeLocation = new Point(storeX, storeY);


여기서 storeLocation은 geo.Point 정보를 담고 있는 Json의 키를 의미한다. 이렇게 x값, y값을 각각 꺼내서 Point() 생성자를 통해 객체를 생성한다. 다행히 Point 객체 내 값은 x, y 두 개 밖에 없으므로 그리 힘들진(?) 않을 것이다.

글을 마치며

검색해보면 알겠지만, Deserialization의 방법은 정말 다양하고 많다. 다양한 라이브러리를 활용하거나 생성자나 기타 메서드에 애노테이션을 붙여서 직렬화 역직렬화 하는 방식 등의 자료는 찾아보기 어렵지 않다.

하지만 본 글에서는 Jackson 라이브러리와 JSON 라이브러리만을 활용하여 역직렬화하는 가장 기본적인 방법을 정리해두고 싶었다. (왜냐하면 그런 글이 없다.)

차후 기회가 되면 custom Deserializer를 구현하는 방법에 대해서도 글을 쓸 것이다.