@@ -4,26 +4,40 @@ package com.lithic.api.core
44
55import com.fasterxml.jackson.annotation.JsonInclude
66import com.fasterxml.jackson.core.JsonGenerator
7+ import com.fasterxml.jackson.core.JsonParseException
8+ import com.fasterxml.jackson.core.JsonParser
9+ import com.fasterxml.jackson.databind.DeserializationContext
710import com.fasterxml.jackson.databind.DeserializationFeature
811import com.fasterxml.jackson.databind.MapperFeature
912import com.fasterxml.jackson.databind.SerializationFeature
1013import com.fasterxml.jackson.databind.SerializerProvider
1114import com.fasterxml.jackson.databind.cfg.CoercionAction
1215import com.fasterxml.jackson.databind.cfg.CoercionInputShape
16+ import com.fasterxml.jackson.databind.deser.std.StdDeserializer
1317import com.fasterxml.jackson.databind.json.JsonMapper
1418import com.fasterxml.jackson.databind.module.SimpleModule
1519import com.fasterxml.jackson.databind.type.LogicalType
1620import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
1721import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
1822import com.fasterxml.jackson.module.kotlin.kotlinModule
1923import java.io.InputStream
24+ import java.time.DateTimeException
25+ import java.time.LocalDate
26+ import java.time.LocalDateTime
27+ import java.time.ZonedDateTime
28+ import java.time.format.DateTimeFormatter
29+ import java.time.temporal.ChronoField
2030
2131fun jsonMapper (): JsonMapper =
2232 JsonMapper .builder()
2333 .addModule(kotlinModule())
2434 .addModule(Jdk8Module ())
2535 .addModule(JavaTimeModule ())
26- .addModule(SimpleModule ().addSerializer(InputStreamJsonSerializer ))
36+ .addModule(
37+ SimpleModule ()
38+ .addSerializer(InputStreamSerializer )
39+ .addDeserializer(LocalDateTime ::class .java, LenientLocalDateTimeDeserializer ())
40+ )
2741 .withCoercionConfig(LogicalType .Boolean ) {
2842 it.setCoercion(CoercionInputShape .Integer , CoercionAction .Fail )
2943 .setCoercion(CoercionInputShape .Float , CoercionAction .Fail )
@@ -91,7 +105,10 @@ fun jsonMapper(): JsonMapper =
91105 .disable(MapperFeature .AUTO_DETECT_SETTERS )
92106 .build()
93107
94- private object InputStreamJsonSerializer : BaseSerializer<InputStream>(InputStream : :class) {
108+ /* * A serializer that serializes [InputStream] to bytes. */
109+ private object InputStreamSerializer : BaseSerializer<InputStream>(InputStream : :class) {
110+
111+ private fun readResolve (): Any = InputStreamSerializer
95112
96113 override fun serialize (
97114 value : InputStream ? ,
@@ -105,3 +122,46 @@ private object InputStreamJsonSerializer : BaseSerializer<InputStream>(InputStre
105122 }
106123 }
107124}
125+
126+ /* *
127+ * A deserializer that can deserialize [LocalDateTime] from datetimes, dates, and zoned datetimes.
128+ */
129+ private class LenientLocalDateTimeDeserializer :
130+ StdDeserializer <LocalDateTime >(LocalDateTime ::class .java) {
131+
132+ companion object {
133+
134+ private val DATE_TIME_FORMATTERS =
135+ listOf (
136+ DateTimeFormatter .ISO_LOCAL_DATE_TIME ,
137+ DateTimeFormatter .ISO_LOCAL_DATE ,
138+ DateTimeFormatter .ISO_ZONED_DATE_TIME ,
139+ )
140+ }
141+
142+ override fun logicalType (): LogicalType = LogicalType .DateTime
143+
144+ override fun deserialize (p : JsonParser , context : DeserializationContext ? ): LocalDateTime {
145+ val exceptions = mutableListOf<Exception >()
146+
147+ for (formatter in DATE_TIME_FORMATTERS ) {
148+ try {
149+ val temporal = formatter.parse(p.text)
150+
151+ return when {
152+ ! temporal.isSupported(ChronoField .HOUR_OF_DAY ) ->
153+ LocalDate .from(temporal).atStartOfDay()
154+ ! temporal.isSupported(ChronoField .OFFSET_SECONDS ) ->
155+ LocalDateTime .from(temporal)
156+ else -> ZonedDateTime .from(temporal).toLocalDateTime()
157+ }
158+ } catch (e: DateTimeException ) {
159+ exceptions.add(e)
160+ }
161+ }
162+
163+ throw JsonParseException (p, " Cannot parse `LocalDateTime` from value: ${p.text} " ).apply {
164+ exceptions.forEach { addSuppressed(it) }
165+ }
166+ }
167+ }
0 commit comments