package roomescape.common.config import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer import com.fasterxml.jackson.module.kotlin.kotlinModule import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import roomescape.common.exception.CommonErrorCode import roomescape.common.exception.RoomescapeException import java.time.* import java.time.format.DateTimeFormatter @Configuration class JacksonConfig { companion object { private val ISO_OFFSET_DATE_TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX") } @Bean fun objectMapper(): ObjectMapper = ObjectMapper() .registerModule(javaTimeModule()) .registerModule(dateTimeModule()) .registerModule(kotlinModule()) .registerModule(longIdModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) private fun javaTimeModule(): JavaTimeModule = JavaTimeModule() .addSerializer( LocalDate::class.java, LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE) ) .addDeserializer( LocalDate::class.java, LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE) ) .addSerializer( LocalTime::class.java, LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm")) ) .addDeserializer( LocalTime::class.java, LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm")) ) as JavaTimeModule private fun longIdModule(): SimpleModule { val simpleModule = SimpleModule() simpleModule.addSerializer(Long::class.java, LongToStringSerializer()) simpleModule.addDeserializer(Long::class.java, StringToLongDeserializer()) return simpleModule } private fun dateTimeModule(): SimpleModule { val simpleModule = SimpleModule() simpleModule.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer()) simpleModule.addSerializer(OffsetDateTime::class.java, OffsetDateTimeSerializer()) return simpleModule } class LongToStringSerializer : JsonSerializer() { override fun serialize(value: Long?, gen: JsonGenerator, serializers: SerializerProvider) { if (value == null) { gen.writeNull() } else { gen.writeString(value.toString()) } } } class StringToLongDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Long? { val text = p.text if (text.isNullOrBlank()) { return null } return try { text.toLong() } catch (_: NumberFormatException) { throw RoomescapeException(CommonErrorCode.INVALID_INPUT_VALUE) } } } class LocalDateTimeSerializer : JsonSerializer() { override fun serialize( value: LocalDateTime, gen: JsonGenerator, serializers: SerializerProvider ) { value.atZone(ZoneId.systemDefault()) .toOffsetDateTime() .also { gen.writeString(it.format(ISO_OFFSET_DATE_TIME_FORMATTER)) } } } class OffsetDateTimeSerializer : JsonSerializer() { override fun serialize( value: OffsetDateTime, gen: JsonGenerator, serializers: SerializerProvider ) { gen.writeString(value.format(ISO_OFFSET_DATE_TIME_FORMATTER)) } } }