diff --git a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy index 21a4ea89b..d03b00789 100644 --- a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy +++ b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy @@ -38,8 +38,12 @@ abstract class JsonCompileSpec extends AbstractTypeElementSpec implements JsonSp } ApplicationContext buildContext(String className, @Language("java") String source, Map properties) { + return buildContext(className, source, properties, [:]) + } + + ApplicationContext buildContext(String className, @Language("java") String source, Map properties, Map contextProperty) { ApplicationContext context = - buildContext(className, source, true) + buildContext(className, source, true, contextProperty) jsonMapper = context.getBean(JsonMapper) @@ -72,14 +76,18 @@ abstract class JsonCompileSpec extends AbstractTypeElementSpec implements JsonSp return context } - @Override - ApplicationContext buildContext(String className, @Language("java") String cls, boolean includeAllBeans) { - def context = super.buildContext(className, cls, true) + ApplicationContext buildContext(String className, @Language("java") String cls, boolean includeAllBeans, Map contextProperty) { + def context = super.buildContext(className, cls, true, contextProperty) Thread.currentThread().setContextClassLoader(context.classLoader) jsonMapper = context.getBean(JsonMapper) return context } + @Override + ApplicationContext buildContext(String className, @Language("java") String cls, boolean includeAllBeans) { + return buildContext(className, cls, includeAllBeans, [:]) + } + @Override ApplicationContext buildContext(String className, @Language("java") String cls) { def context = super.buildContext(className, cls, true) diff --git a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonFormatSpec.groovy b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonFormatSpec.groovy index 14ea1bc9a..80bbca806 100644 --- a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonFormatSpec.groovy +++ b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonFormatSpec.groovy @@ -15,145 +15,22 @@ */ package io.micronaut.serde.jackson - +import io.micronaut.serde.config.SerdeConfiguration.NumericTimeUnit import spock.lang.Unroll import java.sql.Timestamp import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime -import java.time.LocalTime import java.time.OffsetDateTime import java.time.Year -import java.time.ZoneOffset +import java.time.ZoneId import java.time.ZonedDateTime abstract class JsonFormatSpec extends JsonCompileSpec { - void "test disable validation"() { - when: - def i = buildBeanIntrospection('jsongetterrecord.Test', """ -package jsongetterrecord; - -import io.micronaut.serde.annotation.Serdeable; -import com.fasterxml.jackson.annotation.JsonFormat; - - -@Serdeable(validate=false) -record Test( - @JsonFormat(pattern="bunch 'o junk") - int value) { -} -""") - - then: - i != null - } - - @Unroll - void "test fail compilation when invalid format applied to number for type #type"() { - when: - buildBeanIntrospection('jsongetterrecord.Test', """ -package jsongetterrecord; - -import io.micronaut.serde.annotation.Serdeable; -import com.fasterxml.jackson.annotation.JsonFormat; - - -@Serdeable -record Test( - @JsonFormat(pattern="bunch 'o junk") - $type.name value) { -} -""") - - then: - def e = thrown(RuntimeException) - e.message.contains("Specified pattern [bunch 'o junk] is not a valid decimal format. See the javadoc for DecimalFormat: Malformed pattern \"bunch 'o junk\"") - - where: - type << [Integer, int.class] - } - - @Unroll - void "test fail compilation when invalid format applied to date for type #type"() { - when: - buildBeanIntrospection('jsongetterrecord.Test', """ -package jsongetterrecord; - -import io.micronaut.serde.annotation.Serdeable; -import com.fasterxml.jackson.annotation.JsonFormat; - - -@Serdeable -record Test( - @JsonFormat(pattern="bunch 'o junk") - $type.name value) { -} -""") - - then: - def e = thrown(RuntimeException) - e.message.contains("Specified pattern [bunch 'o junk] is not a valid date format. See the javadoc for DateTimeFormatter: Unknown pattern letter: b") - - where: - type << [LocalDateTime] - } - - @Unroll - void "test json format for #type and settings #settings with record"() { - given: - def context = buildContext(""" -package test; - -import io.micronaut.serde.annotation.Serdeable; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.OptBoolean; - -@Serdeable -record Test( - @JsonFormat(${settings.collect { "$it.key=\"$it.value\"" }.join(",")}) - $type.name value -) {} -""") - expect: - def beanUnderTest = newInstance(context, 'test.Test', value) - def typeUnderTest = argumentOf(context, 'test.Test') - writeJson(jsonMapper, beanUnderTest) == result - def read = jsonMapper.readValue(result, typeUnderTest) - typeUnderTest.type.isInstance(read) - read.value == value - - cleanup: - context.close() - - where: - type | value | settings | result - // locale - Double | 100000.12d | [pattern: '$###,###.###', locale: 'de_DE'] | '{"value":"$100.000,12"}' - - // without lo - byte | 10 as byte | [pattern: '$###,###.###'] | '{"value":"$10"}' - Byte | 10 as byte | [pattern: '$###,###.###'] | '{"value":"$10"}' - int | 10 | [pattern: '$###,###.###'] | '{"value":"$10"}' - Integer | 10 | [pattern: '$###,###.###'] | '{"value":"$10"}' - long | 100000l | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - Long | 100000l | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - short | 10000 as short | [pattern: '$###,###.###'] | '{"value":"$10,000"}' - Short | 10000 as short | [pattern: '$###,###.###'] | '{"value":"$10,000"}' - double | 100000.12d | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - Double | 100000.12d | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - float | 100000.12f | [pattern: '$###,###.###'] | '{"value":"$100,000.117"}' - Float | 100000.12f | [pattern: '$###,###.###'] | '{"value":"$100,000.117"}' - BigDecimal | new BigDecimal("100000.12") | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - BigDecimal | new BigDecimal("100000.12") | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - BigInteger | new BigInteger("100000") | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - BigInteger | new BigInteger("100000") | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - - } - @Unroll - void "test json format for #type and settings #settings"() { + void "test deserialize json number format for date #type"() { given: def context = buildContext('test.Test', """ package test; @@ -173,86 +50,31 @@ class Test { return value; } } -""", [value: value]) - expect: - writeJson(jsonMapper, beanUnderTest) == result - def read = jsonMapper.readValue(result, typeUnderTest) - typeUnderTest.type.isInstance(read) - read.value == value - - cleanup: - context.close() - - where: - type | value | settings | result - // locale - Double | 100000.12d | [pattern: '$###,###.###', locale: 'de_DE'] | '{"value":"$100.000,12"}' - - // without locale - byte | 10 | [pattern: '$###,###.###'] | '{"value":"$10"}' - Byte | 10 | [pattern: '$###,###.###'] | '{"value":"$10"}' - int | 10 | [pattern: '$###,###.###'] | '{"value":"$10"}' - Integer | 10 | [pattern: '$###,###.###'] | '{"value":"$10"}' - long | 100000l | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - Long | 100000l | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - short | 10000 | [pattern: '$###,###.###'] | '{"value":"$10,000"}' - Short | 10000 | [pattern: '$###,###.###'] | '{"value":"$10,000"}' - double | 100000.12d | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - Double | 100000.12d | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - float | 100000.12f | [pattern: '$###,###.###'] | '{"value":"$100,000.117"}' - Float | 100000.12f | [pattern: '$###,###.###'] | '{"value":"$100,000.117"}' - BigDecimal | new BigDecimal("100000.12") | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - BigDecimal | new BigDecimal("100000.12") | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' - BigInteger | new BigInteger("100000") | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - BigInteger | new BigInteger("100000") | [pattern: '$###,###.###'] | '{"value":"$100,000"}' +""", [:], ['micronaut.serde.numeric-time-unit': timeUnit]) - } - - @Unroll - void "test json format for date #type and settings #settings"() { - given: - def context = buildContext('test.Test', """ -package test; - -import io.micronaut.serde.annotation.Serdeable; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.OptBoolean; - -@Serdeable -class Test { - @JsonFormat(${settings.collect { "$it.key=\"$it.value\"" }.join(",")}) - private $type.name value; - public void setValue($type.name value) { - this.value = value; - } - public $type.name getValue() { - return value; - } + def jsonString = """ +{ + "value": ${value} } -""", [value: value]) - def result = writeJson(jsonMapper, beanUnderTest) - def read = jsonMapper.readValue(result, typeUnderTest) +""" + def read = jsonMapper.readValue(jsonString, typeUnderTest) expect: - result.startsWith('{"value":"') // was serialized as string, not long - typeUnderTest.type.isInstance(read) - resolver(read.value) == resolver(value) + resolver(read.value) == expected cleanup: context.close() where: - type | value | settings | resolver - Instant | Instant.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { Instant i -> i.toEpochMilli() } - Date | new Date() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { Date d -> d.time } - java.sql.Date | new java.sql.Date(2021, 9, 15) | [pattern: "yyyy-MM-dd"] | { java.sql.Date d -> d } - Timestamp | new Timestamp(System.currentTimeMillis()) | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { Timestamp d -> d } - LocalTime | LocalTime.now() | [pattern: "HH:mm:ss"] | { LocalTime i -> i.toSecondOfDay() } - LocalDate | LocalDate.now() | [pattern: "yyyy-MM-dd"] | { LocalDate d -> d } - LocalDateTime | LocalDateTime.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSS"] | { LocalDateTime i -> i.toInstant(ZoneOffset.from(ZoneOffset.UTC)).toEpochMilli() } - ZonedDateTime | ZonedDateTime.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { ZonedDateTime i -> i.toInstant().toEpochMilli() } - OffsetDateTime | OffsetDateTime.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { OffsetDateTime i -> i.toInstant().toEpochMilli() } - Year | Year.of(2021) | [pattern: "yyyy"] | { Year y -> y } + type | timeUnit | value | settings | resolver | expected + Instant | NumericTimeUnit.SECONDS | "1640995200" | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone: "UTC"] | { Instant i -> i.getEpochSecond() } | 1640995200 + Date | NumericTimeUnit.MILLISECONDS | "1640995200000" | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone: "UTC"] | { Date d -> d.getTime() } | 1640995200000 + Timestamp | NumericTimeUnit.MILLISECONDS | "1640995200000" | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone: "UTC"] | { Timestamp t -> t.getTime()} | 1640995200000 + LocalDate | NumericTimeUnit.SECONDS | "19974" | [pattern: "yyyy-MM-dd", timezone: "UTC"] | { LocalDate d -> d.toString() } | "2024-09-08" + LocalDateTime | NumericTimeUnit.SECONDS | "\"2024-10-18T23:06:24.722\"" | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone: "UTC"] | { LocalDateTime t -> t.atZone(ZoneId.of("UTC")).toInstant().toString() } | "2024-10-18T23:06:24.722Z" + ZonedDateTime | NumericTimeUnit.SECONDS | "1640995200" | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone: "UTC"] | { ZonedDateTime t -> t.toString() } | "2022-01-01T00:00Z" + OffsetDateTime | NumericTimeUnit.SECONDS | "1640995200" | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone: "UTC"] | { OffsetDateTime t -> t.toString() } | "2022-01-01T00:00Z" + Year | NumericTimeUnit.SECONDS | "2024" | [pattern: "yyyy", timezone: "UTC"] | { Year y -> y.toString() } | "2024" } } diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy index 88348bdcf..7481fe3f4 100644 --- a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/JsonCompileSpec.groovy @@ -27,8 +27,12 @@ class JsonCompileSpec extends AbstractTypeElementSpec implements JsonSpec { } ApplicationContext buildContext(String className, @Language("java") String source, Map properties) { + return buildContext(className, source, properties, [:]) + } + + ApplicationContext buildContext(String className, @Language("java") String source, Map properties, Map contextProperties) { ApplicationContext context = - buildContext(className, source, true) + buildContext(className, source, true, contextProperties) jsonMapper = context.getBean(ObjectMapper) @@ -51,6 +55,12 @@ class JsonCompileSpec extends AbstractTypeElementSpec implements JsonSpec { return Argument.of(context.classLoader.loadClass(name)) } + ApplicationContext buildContext(String className, @Language("java") String cls, boolean includeAllBeans, Map contextProperties) { + def context = super.buildContext(className, cls, true, contextProperties) + jsonMapper = context.getBean(JsonMapper) + return context + } + @Override ApplicationContext buildContext(@Language("java") String source) { ApplicationContext context = @@ -63,9 +73,7 @@ class JsonCompileSpec extends AbstractTypeElementSpec implements JsonSpec { @Override ApplicationContext buildContext(String className, @Language("java") String cls, boolean includeAllBeans) { - def context = super.buildContext(className, cls, true) - jsonMapper = context.getBean(JsonMapper) - return context + return buildContext(className, cls, includeAllBeans, [:]) } @Override diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/JsonFormatSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonFormatSpec.groovy similarity index 85% rename from serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/JsonFormatSpec.groovy rename to serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonFormatSpec.groovy index 724e1d08c..3932ff259 100644 --- a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/JsonFormatSpec.groovy +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonFormatSpec.groovy @@ -1,20 +1,12 @@ package io.micronaut.serde.jackson.annotation - -import io.micronaut.serde.jackson.JsonCompileSpec +import io.micronaut.serde.jackson.JsonFormatSpec import spock.lang.Unroll -import java.sql.Timestamp import java.time.Instant -import java.time.LocalDate import java.time.LocalDateTime -import java.time.LocalTime -import java.time.OffsetDateTime -import java.time.Year -import java.time.ZoneOffset -import java.time.ZonedDateTime -class JsonFormatSpec extends JsonCompileSpec { +class SerdeJsonFormatSpec extends JsonFormatSpec { void "test disable validation"() { when: @@ -191,11 +183,10 @@ class Test { BigDecimal | new BigDecimal("100000.12") | [pattern: '$###,###.###'] | '{"value":"$100,000.12"}' BigInteger | new BigInteger("100000") | [pattern: '$###,###.###'] | '{"value":"$100,000"}' BigInteger | new BigInteger("100000") | [pattern: '$###,###.###'] | '{"value":"$100,000"}' - } @Unroll - void "test json format for date #type and settings #settings"() { + void "INSTANT + SQL DATE test json format for date #type and settings #settings"() { given: def context = buildContext('test.Test', """ package test; @@ -230,15 +221,7 @@ class Test { where: type | value | settings | resolver Instant | Instant.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { Instant i -> i.toEpochMilli() } - Date | new Date() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { Date d -> d.time } java.sql.Date | new java.sql.Date(2021, 9, 15) | [pattern: "yyyy-MM-dd"] | { java.sql.Date d -> d } - Timestamp | new Timestamp(System.currentTimeMillis()) | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { Timestamp d -> d } - LocalTime | LocalTime.now() | [pattern: "HH:mm:ss"] | { LocalTime i -> i.toSecondOfDay() } - LocalDate | LocalDate.now() | [pattern: "yyyy-MM-dd"] | { LocalDate d -> d } - LocalDateTime | LocalDateTime.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSS"] | { LocalDateTime i -> i.toInstant(ZoneOffset.from(ZoneOffset.UTC)).toEpochMilli() } - ZonedDateTime | ZonedDateTime.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { ZonedDateTime i -> i.toInstant().toEpochMilli() } - OffsetDateTime | OffsetDateTime.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { OffsetDateTime i -> i.toInstant().toEpochMilli() } - Year | Year.of(2021) | [pattern: "yyyy"] | { Year y -> y } } } diff --git a/serde-support/src/main/java/io/micronaut/serde/support/serdes/FormattedTemporalSerde.java b/serde-support/src/main/java/io/micronaut/serde/support/serdes/FormattedTemporalSerde.java index 82a50b8f1..c0efe5217 100644 --- a/serde-support/src/main/java/io/micronaut/serde/support/serdes/FormattedTemporalSerde.java +++ b/serde-support/src/main/java/io/micronaut/serde/support/serdes/FormattedTemporalSerde.java @@ -25,6 +25,7 @@ import io.micronaut.serde.config.annotation.SerdeConfig; import java.io.IOException; +import java.time.DateTimeException; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; @@ -35,10 +36,12 @@ final class FormattedTemporalSerde implements TemporalSerde { final DateTimeFormatter formatter; final TemporalQuery query; + final TemporalSerde originalTemporalSerde; FormattedTemporalSerde(@NonNull String pattern, @NonNull AnnotationMetadata annotationMetadata, - TemporalQuery query) { + TemporalQuery query, + TemporalSerde originalTemporalSerde) { Locale locale = annotationMetadata.stringValue(SerdeConfig.class, SerdeConfig.LOCALE) .map(StringUtils::parseLocale) @@ -52,12 +55,16 @@ final class FormattedTemporalSerde implements Tempor this.formatter = f.withZone(zone); this.query = query; + this.originalTemporalSerde = originalTemporalSerde; + } FormattedTemporalSerde(DateTimeFormatter formatter, - TemporalQuery query) { + TemporalQuery query, + TemporalSerde originalTemporalSerde) { this.formatter = formatter; this.query = query; + this.originalTemporalSerde = originalTemporalSerde; } @Override @@ -70,7 +77,15 @@ public void serialize(Encoder encoder, EncoderContext context, Argument type) throws IOException { final String str = decoder.decodeString(); - return formatter.parse(str, query()); + try { + return formatter.parse(str, query()); + } catch (DateTimeException e) { + if (originalTemporalSerde instanceof DefaultFormattedTemporalSerde defaultFormattedTemporalSerde) { + return defaultFormattedTemporalSerde.deserializeFallback(e, str); + } else { + throw e; + } + } } @Override diff --git a/serde-support/src/main/java/io/micronaut/serde/support/serdes/TemporalSerde.java b/serde-support/src/main/java/io/micronaut/serde/support/serdes/TemporalSerde.java index 5dd3271e3..ab77ff09e 100644 --- a/serde-support/src/main/java/io/micronaut/serde/support/serdes/TemporalSerde.java +++ b/serde-support/src/main/java/io/micronaut/serde/support/serdes/TemporalSerde.java @@ -43,7 +43,7 @@ default Serializer createSpecific(EncoderContext context, Argument(pattern, annotationMetadata, query()); + return new FormattedTemporalSerde<>(pattern, annotationMetadata, query(), this); } return this; @@ -55,7 +55,7 @@ default Deserializer createSpecific(DecoderContext decoderContext, Argument(pattern, annotationMetadata, query()); + return new FormattedTemporalSerde<>(pattern, annotationMetadata, query(), this); } return this; } diff --git a/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonFormatSpec.groovy b/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonFormatSpec.groovy index 54f56f1cd..4f4f3ce59 100644 --- a/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonFormatSpec.groovy +++ b/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonFormatSpec.groovy @@ -2,7 +2,87 @@ package io.micronaut.serde.tck.jackson.databind import io.micronaut.serde.jackson.JsonFormatSpec import spock.lang.Ignore +import spock.lang.PendingFeature + +import java.time.Instant -@Ignore // TODO class DatabindJsonFormatSpec extends JsonFormatSpec { + + @PendingFeature + void "UNSUPPORTED Instant test json format for date #type and settings #settings"() { + given: + def context = buildContext('test.Test', """ +package test; + +import io.micronaut.serde.annotation.Serdeable; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.OptBoolean; + +@Serdeable +class Test { + @JsonFormat(${settings.collect { "$it.key=\"$it.value\"" }.join(",")}) + private $type.name value; + public void setValue($type.name value) { + this.value = value; + } + public $type.name getValue() { + return value; + } +} +""", [value: value]) + def result = writeJson(jsonMapper, beanUnderTest) + def read = jsonMapper.readValue(result, typeUnderTest) + + expect: + result.startsWith('{"value":"') // was serialized as string, not long + typeUnderTest.type.isInstance(read) + resolver(read.value) == resolver(value) + + cleanup: + context.close() + + where: + type | value | settings | resolver + Instant | Instant.now() | [pattern: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"] | { Instant i -> i.toEpochMilli() } + } + + @Ignore // Passes on CI fails locally ... + void "UNSUPPORTED SQL Date test json format for date #type and settings #settings"() { + given: + def context = buildContext('test.Test', """ +package test; + +import io.micronaut.serde.annotation.Serdeable; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.OptBoolean; + +@Serdeable +class Test { + @JsonFormat(${settings.collect { "$it.key=\"$it.value\"" }.join(",")}) + private $type.name value; + public void setValue($type.name value) { + this.value = value; + } + public $type.name getValue() { + return value; + } +} +""", [value: value]) + def result = writeJson(jsonMapper, beanUnderTest) + def read = jsonMapper.readValue(result, typeUnderTest) + + expect: + result.startsWith('{"value":"') // was serialized as string, not long + typeUnderTest.type.isInstance(read) + resolver(read.value) == resolver(value) + + cleanup: + context.close() + + where: + type | value | settings | resolver + java.sql.Date | new java.sql.Date(2021, 9, 15) | [pattern: "yyyy-MM-dd"] | { java.sql.Date d -> d } + } + + }