본 포스트의 원문은 http://www.baeldung.com에서 확인할 수 있으며 원 저작자의 번역 포스팅 허락을 받았음을 알려드립니다.

1. Overview

이번 예제는 jackson(library) 를 통해 날짜를 직렬화(serialize) 하는 방법에 대해 살펴보겠습니다. java.util.Date 를 serializing 하고 Joda Time과 Java 8 의 DateTim도 같이 살펴보겠습니다.

2. Serialize Date with Jackson

First – let’s see how to serialize a simple java.util.Date with Jackson.

아래 예제는 “eventDate”라는 Date 타입 필드를 가진 Event 인스턴스를 serialize 하는 예제입니다 :

@Test
public void whenSerializingDateWithJackson_thenSerializedToTimestamp()
  throws JsonProcessingException, ParseException {
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
 
    Date date = df.parse("01-01-1970 01:00");
    Event event = new Event("party", date);
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.writeValueAsString(event);
}

여기서 중요한 점은 Jackson은 Date를 기본적으로 timestamp 형태(milliseconds 숫자는 1970년 1월 1일 UTC)으로 serialize 합니다.

“event”의 실제 serialization 결과는 :

{
   "name":"party",
   "eventDate":3600000
}
  1. Serialize Date to ISO-8601

우리가 원한것은 timestamp 형태로의 serializing이 최선의 결과가 아닐 수 있습니다. 이제 Date를 ISO-8601 포멧으로 serialize하는 방법을 살펴 보겠습니다.

timestamp를 사용하지 않도록 할 경우 아래와 같이 자동으로 ISO-8601 형태로 출력합니다.

@Test
public void whenSerializingDateToISO8601_thenSerializedToText()
  throws JsonProcessingException, ParseException {
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
 
    String toParse = "01-01-1970 02:30";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
 
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("1970-01-01T02:30:00.000+0000"));
}

이제 위와 같은 형태의 date 출력이 훨씬 더 가독성이 좋습니다. - 그럼에도 아직까진 깔끔하다고 생각되지 않을 수 있습니다.

4. Configure ObjectMapper DateFormat

이전 방법은 java.util.Date 인스턴스를 딱 맞는 형태로 표현하도록 선택할 수 있는 유연성이 부족한 것 같습니다.

이제 date를 우리가 원하는 형태로 설정하고 표현하는 방법에 대해 살펴보겠습니다.

@Test
public void whenSettingObjectMapperDateFormat_thenCorrect()
  throws JsonProcessingException, ParseException {
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
 
    String toParse = "20-12-2014 02:30";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(df);
 
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

이제 date 포멧을 좀 더 유연한 방법으로 표현했지만 전체 ObjectMapper를 통해 전역 설정으로 사용했습니다.

5. Use @JsonFormat to format Date

이제 @JsonFormat 어노테이션으로 어플리케이션 전역적으로 설정하는것이 아닌 각 개별적으로 date 형태를 지정하는 방법에 대해 살펴보겠습니다.

public class Event {
    public String name;
 
    @JsonFormat
      (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

이제 테스트 해 보겠습니다:

@Test
public void whenUsingJsonFormatAnnotationToFormatDate_thenCorrect()
  throws JsonProcessingException, ParseException {
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
 
    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);
 
    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

6. Custom Date Serializer

다음으로 Date를 위한 custom serializer 를 통해 모든 출력을 변경해 보겠습니다.

public class CustomDateSerializer extends JsonSerializer<Date> {
    private static SimpleDateFormat formatter = 
      new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
 
    @Override
    public void serialize (Date value, JsonGenerator gen, SerializerProvider arg2)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.format(value));
    }
}

다음은 “eventDate” 필드를 serializer 를 사용하도록 설정하였습니다 :

public class Event {
    public String name;
 
    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
}

마지막으로 테스트 해보겠습니다:

@Test
public void whenUsingCustomDateSerializer_thenCorrect()
  throws JsonProcessingException, ParseException {
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
 
    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);
 
    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

7. Serialize Joda-Time with Jackson

Date들이 항상 java.util.Date의 인스턴스일 필요는 없습니다. 이를 표현할 수 있는 다른 클래스들이 많이 있습니다. - 그리고 일반적인 방법 중 하나로 Joda-Time 라이브러리에서 구현한 DateTime 이 있습니다.

이제 Jackson으로 DateTime을 serialize하는 방법에 대해 살펴보겠습니다.

Joda-Time 출력을 지원하기 위해 jackson-datatype-joda 모듈을 사용하도록 하겠습니다.

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-joda</artifactId>
  <version>2.4.0</version>
</dependency>

이제 간단하게 JodaModule을 등록하면 됩니다.

@Test
public void whenSerializingJodaTime_thenCorrect() 
  throws JsonProcessingException {
    DateTime date = new DateTime(2014, 12, 20, 2, 30, 
      DateTimeZone.forID("Europe/London"));
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JodaModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
 
    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30:00.000Z"));
}

8. Serialize Joda DateTime with Custom Serializer

만약 추가된 Joda-Time과 Jackson 의존성을 원하지 않을 수 있습니다. - 그러면 custom serializer(이전 예제에서 살펴본 것과 비슷한) 를 통해 DateTime 인스턴스를 깔끔하게 serialize할 수 있습니다:

public class CustomDateTimeSerializer extends JsonSerializer<DateTime> {
 
    private static DateTimeFormatter formatter = 
      DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
 
    @Override
    public void serialize
      (DateTime value, JsonGenerator gen, SerializerProvider arg2)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.print(value));
    }
}

다음은 “eventDate” 속성을 serializer를 사용하겠습니다.

public class Event {
    public String name;
 
    @JsonSerialize(using = CustomDateTimeSerializer.class)
    public DateTime eventDate;
}

마지막으로 다함께 테스트 해보겠습니다.

@Test
public void whenSerializingJodaTimeWithJackson_thenCorrect() 
  throws JsonProcessingException {
    DateTime date = new DateTime(2014, 12, 20, 2, 30);
    Event event = new Event("party", date);
 
    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("2014-12-20 02:30"));
}

9. Serialize Java 8 Date with Jackson

다음으로 Java 8 DateTime을 어떻게 serialize 하는지 살펴보겠습니다. 이번 예제에서는 jackson-datatype-jsr310 module을 통해 Jackson과 LocalDateTime 을 사용하겠습니다.

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.4.0</version>
</dependency>

이제 JSR310Module 을 등록하면 jackson이 나머지를 처리하게 됩니다.

@Test
public void whenSerializingJava8Date_thenCorrect()
  throws JsonProcessingException {
    LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30);
 
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JSR310Module());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
 
    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30"));
}

10. Serialize Java 8 Date with Jackson

만약 의존성이 추가되는 것을 원치 않는다면 항상 custom serializer를 통해 Java 8 DateTime을 JSON으로 출력할 수 있습니다.

public class CustomLocalDateTimeSerializer 
  extends JsonSerializer<LocalDateTime> {
 
    private static DateTimeFormatter formatter = 
      DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
 
    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider arg2)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.format(value));
    }
}

이제 “eventDate” 필드에 serializer를 사용하겠습니다. :

public class Event {
    public String name;
 
    @JsonSerialize(using = CustomLocalDateTimeSerializer.class)
    public LocalDateTime eventDate;
}
@Test
public void whenSerializingJava8DateWithCustomSerializer_thenCorrect()
  throws JsonProcessingException {
    LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30);
    Event event = new Event("party", date);
 
    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("2014-12-20 02:30"));
}

11. Deserialize Date

다음으로는 Jackson을 통해 어떻게 Date를 deserialize 하는지 살펴보겠습니다. 다음 예제는 “Event” 인스턴스에 포함된 date를 deserialize합니다.

@Test
public void whenDeserializingDateWithJackson_thenCorrect()
  throws JsonProcessingException, IOException {
    String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(df);
 
    Event event = mapper.reader(Event.class).readValue(json);
    assertEquals("20-12-2014 02:30:00", df.format(event.eventDate));
}

12. Custom Date deserializer

이제 어떻게 custom Date deserializer를 사용하는지 살펴보겠습니다. “eventDate” 속성을 위해 custom deserializer를 만들겠습니다. :

public class CustomDateDeserializer extends JsonDeserializer<Date> {
 
    private static SimpleDateFormat formatter = 
      new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
 
    @Override
    public Date deserialize(JsonParser jsonparser, DeserializationContext context)
      throws IOException, JsonProcessingException {
        String date = jsonparser.getText();
        try {
            return formatter.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

다음으로 “eventDate”에 deserializer를 사용합니다.

public class Event {
    public String name;
 
    @JsonDeserialize(using = CustomDateDeserializer.class)
    public Date eventDate;
}

마지막으로 테스트 해 보겠습니다 :

@Test
public void whenDeserializingDateUsingCustomDeserializer_thenCorrect()
  throws JsonProcessingException, IOException {
    String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    ObjectMapper mapper = new ObjectMapper();
 
    Event event = mapper.reader(Event.class).readValue(json);
    assertEquals("20-12-2014 02:30:00", df.format(event.eventDate));
}

13. Conclusion

이번 Date 블로그에서 date를 JSON 원하는 형태로 marshalling과 unmarshall을 도와주는 jackson 을 사용하는 방법에 대해 살펴보았습니다.


thswave

try hard, try hard!