JSON Jackson으로 날짜 형식 매핑


154

API에서 오는 날짜 형식이 다음과 같습니다.

"start_time": "2015-10-1 3:00 PM GMT+1:00"

YYYY-DD-MM HH : MM am / pm GMT 타임 스탬프입니다. 이 값을 POJO의 Date 변수에 매핑하고 있습니다. 분명히 변환 오류를 표시합니다.

두 가지를 알고 싶습니다.

  1. Jackson으로 변환하기 위해 사용해야하는 형식은 무엇입니까? Date가 이것에 적합한 필드 유형입니까?
  2. 일반적으로 Jackson이 객체 멤버에 매핑하기 전에 변수를 처리하는 방법이 있습니까? 형식 변경, 계산 등

이것은 정말 좋은 예를 들어, 클래스의 필드 위의 주석입니다 : java.dzone.com/articles/how-serialize-javautildate
digz6666

이제 날짜 처리를위한 위키 페이지가 있습니다. wiki.fasterxml.com/JacksonFAQDateHandling
Sutra

요즘에는 더 이상 Date수업을 사용하지 않아야합니다 . 현대 Java 날짜 및 시간 API 인 java.time 은 거의 5 년 전에이를 대체했습니다. 그것을 사용하고 FasterXML / jackson-modules-java8을 사용하십시오 .
Ole VV 2018

답변:


124

Jackson으로 변환하기 위해 사용해야하는 형식은 무엇입니까? Date가 이것에 적합한 필드 유형입니까?

Date이에 대한 훌륭한 필드 유형입니다. 다음을 사용하여 JSON 구문 분석 가능을 쉽게 만들 수 있습니다 ObjectMapper.setDateFormat.

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

일반적으로 Jackson이 객체 멤버에 매핑하기 전에 변수를 처리하는 방법이 있습니까? 형식 변경, 계산 등

예. JsonDeserializer예를 들어 확장과 같은 사용자 정의 구현을 포함하여 몇 가지 옵션이 있습니다 JsonDeserializer<Date>. 이것은 좋은 시작입니다.


2
형식에 AM / PM 지정도 포함되어 있으면 12 시간 형식이 더 좋습니다. DateFormat df = new SimpleDateFormat ( "yyyy-MM-dd hh : mm a z");
John Scattergood

이 모든 솔루션을 시도했지만 내 POJO의 Date 변수를 Map 키 값에 날짜로 저장할 수 없습니다. 그런 다음 맵에서 BasicDbObject (MongoDB API)를 인스턴스화하고 결과적으로 변수를 MongoDB DB 컬렉션에 날짜 (Long 또는 String이 아닌)로 저장하려고합니다. 이것이 가능합니까? 감사합니다
RedEagle

1
그냥 자바 8 쉽게 사용할 수 LocalDateTime또는 ZonedDateTime대신 Date? 때문에 Date기본적으로 사용되지 않습니다 (또는 그 방법 중 적어도 많은에), 나는 그 대안을 사용하고 싶습니다.
houcros 2016

setSateFormat ()에 대한 javadocs는이 호출로 인해 ObjectMapper가 더 이상 스레드 안전하지 않다고 말합니다. JsonSerializer 및 JsonDeserializer 클래스를 만들었습니다.
MiguelMunoz

질문에 java.util.Date명시 적으로 언급하지 않았으므로 이것이 효과가 없다는 것을 지적하고 싶습니다 java.sql.Date.. 아래 답변도 참조하십시오.
황동 원숭이

329

Jackson v2.0부터 @JsonFormat 주석을 Object 멤버에서 직접 사용할 수 있습니다.

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;

61
시간대를 포함 시키려면 :@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK

안녕하세요, 어떤 주석에 주석이 있습니다. jackson-mapper-asl 1.9.10 버전을 사용하고 있습니다. 나는 그것을 얻지 못하고있다
krishna Ram

1
@Ramki : jackson-annotations> = 2.0
Olivier Lecrivain

3
이 주석은 직렬화 단계에서만 완벽하게 작동하지만 역 직렬화 중에는 시간대 및 로캘 정보가 전혀 사용되지 않습니다. locale = "hu"를 사용하여 timezone = "CET"및 "Europe / Budapest"시간대를 시도했지만 달력에서 이상한 시간 대화가 발생하지 않습니다. 역 직렬화를 사용한 사용자 지정 직렬화 만 필요에 따라 시간대를 처리하는 데 효과적이었습니다. 다음은 사용을해야하는지 완벽한 튜토리얼 baeldung.com/jackson-serialize-dates은
미클로스 Krivan

1
이것은 Jackson Annotations 라는 별도의 jar 프로젝트 입니다. 샘플 폼 입력
bub

52

물론 직렬화 및 역 직렬화라는 자동화 된 방법 이 있으며 특정 주석 ( @JsonSerialize , @JsonDeserialize)으로 정의 할 수 있습니다. 언급 )으로 있습니다.

java.util.Date 및 java.util.Calendar ...와 아마도 JodaTime을 모두 사용할 수 있습니다.

역 직렬화 (직렬화가 완벽하게 작동 ) 중에 @JsonFormat 주석이 원하는대로 작동하지 않았습니다 ( 시간대 를 다른 값으로 조정 했습니다).

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

예측 된 결과를 원하면 @JsonFormat 주석 대신 사용자 정의 직렬 변환기 및 사용자 정의 디시리얼라이저를 사용해야합니다. 여기에서 훌륭한 튜토리얼과 솔루션을 찾았습니다.http://www.baeldung.com/jackson-serialize-dates에서 .

Date 필드 에는 예제가 있지만 Calendar 필드에는 필요 하므로 구현은 다음과 같습니다.

시리얼 라이저 클래스 :

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

디시리얼라이저 클래스 :

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

위 클래스 의 사용법 :

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

이 구현을 사용하면 직렬화 및 역 직렬화 프로세스를 연속적으로 실행하면 원점 값이 생성됩니다.

@JsonFormat 주석 만 사용하면 deserialization은 라이브러리 내부 표준 시간대 기본 설정 때문에 주석 매개 변수로 변경할 수없는 결과 (Jackson 라이브러리 2.5.3 및 2.6.3 버전에서도 경험 한 결과)가 다른 결과를 낳습니다 .


4
어제 대답에 대한 공감대를 얻었습니다. 나는이 주제에 대해 많은 일을 했으므로 이해할 수 없습니다. 그로부터 배울 피드백이 있습니까? downvote의 경우 약간의 메모를 부탁드립니다. 이런 식으로 우리는 서로에게서 더 많은 것을 배울 수 있습니다.
Miklos Krivan

좋은 답변, 정말 도움이되었습니다! 사소한 제안-CustomCalendarSerializer 및 CustomCalendarDeserializer를 포함 상위 클래스 내에 정적 클래스로 배치하는 것이 좋습니다. 나는 이것이 코드를 조금 더 좋게 만들 것이라고 생각한다 :)
Stuart

@Stuart-코드의 재구성을 다른 답변으로 제공하거나 편집을 제안해야합니다. Miklos는 시간이 없을 수도 있습니다.
ocodo

@MiklosKrivan 나는 여러 가지 이유로 당신을 downvoted했습니다. SimpleDateFormat은 스레드로부터 안전하지 않으며 솔직히 날짜 형식 지정을위한 대체 라이브러리 (Joda, Commons-lang FastDateFormat 등)를 사용해야합니다. 다른 이유는 시간대 및 로케일 설정 때문입니다. 직렬화 환경에서 GMT를 사용하는 것이 훨씬 더 바람직하며 클라이언트가 시간대를 말하거나 선호하는 tz를 별도의 문자열로 첨부하도록하십시오. 서버를 UTC (일명 UTC)로 설정하십시오. Jackson에는 ISO 형식이 내장되어 있습니다.
Adam Gent

1
귀하의 의견에 대한 Thx @AdamGent. 귀하의 제안을 이해하고 동의합니다. 그러나이 특별한 경우에는 로케일 정보가있는 JsonFormat 주석이 예상대로 작동하지 않는다는 것에 집중하고 싶었습니다. 그리고 어떻게 해결할 수 있습니다.
Miklos Krivan

4

날짜 시간 형식의 스프링 부트 응용 프로그램에 대한 완벽한 예RFC3339

package bj.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import java.text.SimpleDateFormat;

/**
 * Created by BaiJiFeiLong@gmail.com at 2018/5/4 10:22
 */
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(BarApp.class, args);
    }

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }
}

4

날짜에 T 및 Z와 같은 문자를 추가하려면

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;

산출

{
    "currentTime": "2019-12-11T11:40:49Z"
}

3

나를 위해 일하고 있습니다. 스프링 부트.

 import com.alibaba.fastjson.annotation.JSONField;

 @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
 private Date createTime;

산출:

{ 
   "createTime": "2019-06-14 13:07:21"
}

2

@ miklov-kriven의 매우 유용한 답변을 바탕 으로이 두 가지 추가 고려 사항이 누군가에게 도움이되기를 바랍니다.

(1) serializer와 de-serializer를 동일한 클래스의 정적 내부 클래스로 포함시키는 것이 좋습니다. NB, ThreadD를 사용하여 SimpleDateFormat의 스레드 안전성.

public class DateConverter {

    private static final ThreadLocal<SimpleDateFormat> sdf = 
        ThreadLocal.<SimpleDateFormat>withInitial(
                () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});

    public static class Serialize extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
            if (value == null) {
                jgen.writeNull();
            }
            else {
                jgen.writeString(sdf.get().format(value));
            }
        }
    }

    public static class Deserialize extends JsonDeserializer<Date> {
        @Overrride
        public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
            String dateAsString = jp.getText();
            try {
                if (Strings.isNullOrEmpty(dateAsString)) {
                    return null;
                }
                else {
                    return new Date(sdf.get().parse(dateAsString).getTime());
                }
            }
            catch (ParseException pe) {
                throw new RuntimeException(pe);
            }
        }
    }
}

(2) 각 개별 클래스 멤버에서 @JsonSerialize 및 @JsonDeserialize 주석을 사용하는 대신 애플리케이션 레벨에서 사용자 정의 직렬화를 적용하여 Jackson의 기본 직렬화를 대체 할 수도 있습니다. 즉, 날짜 유형의 모든 클래스 멤버가 Jackson에 의해 직렬화됩니다. 각 필드에 명시적인 주석없이이 사용자 지정 직렬화를 사용합니다. 예를 들어 Spring Boot를 사용하는 경우 한 가지 방법은 다음과 같습니다.

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Module customModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new DateConverter.Serialize());
        module.addDeserializer(Date.class, new Dateconverter.Deserialize());
        return module;
    }
}

난 당신을 downvoted (나는 downvoting 싫어하지만 난 그냥 사람들이 당신의 답변을 사용하고 싶지 않습니다). SimpleDateFormat은 스레드 세이프가 아닙니다. 2016 년입니다 (2016 년에 답변했습니다). 더 빠르고 스레드 안전 옵션이 많으면 SimpleDateFormat을 사용하지 않아야합니다. Q / A를 제안하는 것에 대한 정확한 오용도 여기 있습니다 : stackoverflow.com/questions/25680728/…
Adam Gent

2
@AdamGent 이것을 지적 해 주셔서 감사합니다. 이와 관련하여 Jackson을 사용하여 ObjectMapper 클래스는 스레드로부터 안전하므로 중요하지 않습니다. 그러나 코드가 스레드가 아닌 안전한 컨텍스트에서 복사되어 사용될 수 있다고 생각합니다. 그래서 SimpleDateFormat 스레드에 안전하게 액세스 할 수 있도록 답변을 편집했습니다. 또한 대안으로 주로 java.time 패키지가 있음을 인정합니다.
스튜어트

2

누구나 java.sql.Date에 대한 사용자 정의 날짜 형식을 사용하는 데 문제가있는 경우 가장 간단한 해결책입니다.

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);

(이 SO- 응답은 나에게 많은 문제를 저장했습니다 : https://stackoverflow.com/a/35212795/3149048 )

Jackson은 기본적으로 java.sql.Date에 대해 SqlDateSerializer를 사용하지만 현재이 시리얼 라이저는 날짜 형식을 고려하지 않습니다 (이 문제 참조 : https://github.com/FasterXML/jackson-databind/issues/1407) . 해결 방법은 코드 예제에 표시된대로 java.sql.Date에 다른 직렬 변환기를 등록하는 것입니다.


1

SimpleDateFormat다른 답변에 설명 된 것과 같은 설정 java.util.Date은 질문에서 의미하는 것으로 만 작동 한다는 것을 지적하고 싶습니다 . 그러나 java.sql.Date포맷터가 작동하지 않습니다. 필자의 경우 직렬화 해야하는 모델에서 필드가 실제로 a java.utl.Date였지만 실제 객체가 a가되어서 포맷터가 작동하지 않는 이유는 분명하지 않았습니다 java.sql.Date. 이것은 가능하기 때문에

public class java.sql extends java.util.Date

그래서 이것은 실제로 유효합니다

java.util.Date date = new java.sql.Date(1542381115815L);

따라서 날짜 필드가 올바르게 형식화되지 않은 이유가 궁금하다면 객체가 실제로 형식인지 확인하십시오 java.util.Date.

여기 에 처리 java.sql.Date가 추가되지 않는 이유도 언급되어 있습니다.

이것은 변화를 깨뜨릴 것이며, 그것이 보증되지 않는다고 생각합니다. 우리가 처음부터 시작했다면 나는 그 변화에 동의 할 것이지만, 그렇게 많은 것은 아닙니다.


1
서로 다른 두 Date클래스 의 잘못된 디자인으로 인해 한 가지 결과를 지적 해 주셔서 감사합니다 . 요즘은 그들 중 어느 것도해서는 안됩니다 SimpleDateFormat. 현대 Java 날짜 및 시간 API 인 java.time 은 거의 5 년 전에이를 대체했습니다. 그것을 사용하고 FasterXML / jackson-modules-java8을 사용하십시오 .
Ole VV
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.