본문 바로가기
Programing/Java

Jackson을 이용한 org.joda.time.DateTime 파싱하기

by Tomining 2017. 7. 27.
개발을 진행하다가 문득 "Jackson으로 JSON을 읽거나 쓸 때, DateTime 형은 어떤 포멧을 가질까? 그리고 이를 원하는 포멧으로 지정할 수 있을까?” 라는 의문이 생겼다.

그냥 무작정 해보자. DateTime 형 속성을 하나 갖고 있는 모델이 있다고 가정하자.
@Data
@AllArgsConstructor
public class DateTimeModel {
  DateTime ymdt;
}
public class TempTest {
  private ObjectMapper objectMapper = new ObjectMapper();

  @Test  
  public void deserialize() throws Exception {
    DateTimeModel model = new DateTimeModel(new DateTime());
    System.out.println(objectMapper.writeValueAsString(model));
  }

  @Test  
  public void serialize() throws Exception {
    String json = "{\"ymdt\": \"2017-07-27 23:59:59\"}";
    DateTimeModel model = objectMapper.readValue(json, DateTimeModel.class);
    System.out.println(model);
  }
}

TC를 수행해 보면 output은 아래와 같이 나온다.
{"ymdt":{"weekOfWeekyear":30,"millisOfDay":80139861,"monthOfYear":7,"hourOfDay":22,"minuteOfHour":15,"secondOfMinute":39,"millisOfSecond":861,"weekyear":2017,"yearOfEra":2017,"yearOfCentury":17,"centuryOfEra":20,"secondOfDay":80139,"minuteOfDay":1335,"era":1,"dayOfYear":208,"dayOfWeek":4,"dayOfMonth":27,"year":2017,"chronology":{"zone":{"fixed":false,"uncachedZone":{"fixed":false,"cachable":true,"id":"Asia/Seoul"},"id":"Asia/Seoul"}},"zone":{"fixed":false,"uncachedZone":{"fixed":false,"cachable":true,"id":"Asia/Seoul"},"id":"Asia/Seoul"},"millis":1501161339861,"afterNow":false,"beforeNow":true,"equalNow":false}}
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.joda.time.DateTime: no String-argument constructor/factory method to deserialize from String value ('2017-07-27 23:59:59')
at [Source: {"ymdt": "2017-07-27 23:59:59"}; line: 1, column: 10] (through reference chain: com.naver.storeplatform.api.common.DateTimeModel["ymdt"])

DateTime 형의 모든 속성이 Json에 담기는 듯 하다.
보통 날짜를 표현할 때 "년-월-일 시:분:초” 형식으로 많이 사용할 것이다.(한국시간 기준)
DateTime 포멧을 지정할 순 없을까?

ObjectMapper 생성시 module로서 DateTime Serializer/Deserializer를 등록할 수 있다.
아래와 같이 DateTimeFormatModule을 작성하고 ObjectMapper에 module을 등록해 주면 된다.
public class DateTimeFormatModule extends SimpleModule {
  public DateTimeFormatModule() {
    super();
    addSerializer(DateTime.class, new JsonSerializer<DateTime>() {
      public final DateTimeZone TIME_ZONE = DateTimeZone.forID("Asia/Seoul");
      public final DateTimeFormatter FORMATTER = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss").withZone(TIME_ZONE);

      @Override      
      public void serialize(DateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        if (value == null) {
          gen.writeNull();
        } else {
          gen.writeString(FORMATTER.print(value));
        }
      }
    });

    addDeserializer(DateTime.class, new JsonDeserializer<DateTime>() {
      public final DateTimeZone TIME_ZONE = DateTimeZone.forID("Asia/Seoul");
      public final DateTimeFormatter FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(TIME_ZONE);

      @Override      
      public DateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonToken t = p.getCurrentToken();

        if (t == JsonToken.VALUE_NUMBER_INT) {
          return new DateTime(p.getLongValue(), TIME_ZONE);
        }

        if (t == JsonToken.VALUE_STRING) {
          String str = p.getText();
          if (StringUtils.isEmpty(str)) {
            return null;
          }
          return FORMATTER.parseDateTime(str);
        }

        throw ctxt.mappingException(DateTime.class);
      }
    });
  }
}
public class TempTest {
  private ObjectMapper objectMapper = new ObjectMapper();

  @Before  public void setUp() {
    objectMapper.registerModule(new DateTimeFormatModule());
  }

  @Test  public void deserialize() throws Exception {
    DateTimeModel model = new DateTimeModel(new DateTime());
    System.out.println(objectMapper.writeValueAsString(model));
  }

  @Test  public void serialize() throws Exception {
    String json = "{\"ymdt\": \"2017-07-27 23:59:59\"}";
    DateTimeModel model = objectMapper.readValue(json, DateTimeModel.class);
    System.out.println(model);
  }
}

그런 다음 TC를 다시 수행해 보면 아래와 같이 결과가 잘 나오는 것을 확인할 수 있다.
{"ymdt":"2017-07-27 22:24:01"}
DateTimeModel(ymdt=2017-07-27T23:59:59.000+09:00)

즉, Serializer/Deserializer를 만들어서 ObjectMapper에 Module로 등록하면 POJO의 속성이 해당 타입인 경우 등록된 Serializer/Deserializer로 수행되는 것이다.
이를 활용하면 Spring Controller에서 @RequestParam이나 @ResponseBody에 사용될 날짜형 속성들에 대해서 포멧을 임의로 지정해 줄 수도 있다.

참고