From 0a6063f95340ce1d06ab2fdaa4197a0109a0c245 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:04:24 +0000 Subject: [PATCH 1/4] Initial plan From 1229135eb7d4592d86069873d2d5ecfbe64b6e81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:13:28 +0000 Subject: [PATCH 2/4] Add reproducer for Optional getter with String property Co-authored-by: graemerocher <66626+graemerocher@users.noreply.github.com> --- .../serde/jackson/OptionalGetterSpec.groovy | 62 +++++++++++++++++++ .../serde/jackson/optionalwrap/MyThing.java | 39 ++++++++++++ .../DatabindOptionalGetterSpec.groovy | 6 ++ 3 files changed, 107 insertions(+) create mode 100644 serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/OptionalGetterSpec.groovy create mode 100644 serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java create mode 100644 test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy diff --git a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/OptionalGetterSpec.groovy b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/OptionalGetterSpec.groovy new file mode 100644 index 000000000..b0d8131df --- /dev/null +++ b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/OptionalGetterSpec.groovy @@ -0,0 +1,62 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.serde.jackson + +import io.micronaut.context.ApplicationContext +import io.micronaut.core.type.Argument +import io.micronaut.json.JsonMapper +import io.micronaut.serde.jackson.optionalwrap.MyThing + +/** + * Tests serialization and deserialization of beans with Optional getters + * but plain String setters. + */ +abstract class OptionalGetterSpec extends JsonCompileSpec { + + void "test serialize and deserialize MyThing with Optional getter"() { + given: + ApplicationContext context = ApplicationContext.run() + jsonMapper = context.getBean(JsonMapper) + def myThing = new MyThing() + myThing.setValue("someValue") + + when: + String json = jsonMapper.writeValueAsString(myThing) + MyThing other = jsonMapper.readValue(json, MyThing) + + then: + other.getValue().orElse(null) == "someValue" + + cleanup: + context.close() + } + + void "test deserialize MyThing from JSON"() { + given: + ApplicationContext context = ApplicationContext.run() + jsonMapper = context.getBean(JsonMapper) + String json = '{"value":"myOtherValue"}' + + when: + MyThing other = jsonMapper.readValue(json, MyThing) + + then: + other.getValue().orElse(null) == "myOtherValue" + + cleanup: + context.close() + } +} diff --git a/serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java b/serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java new file mode 100644 index 000000000..cbc9c1de7 --- /dev/null +++ b/serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.serde.jackson.optionalwrap; + +import io.micronaut.serde.annotation.Serdeable; + +import java.util.Optional; + +/** + * Test class for Optional getter wrapping a String property. + * This tests the scenario where a getter returns Optional<String> but the + * setter accepts a plain String. + */ +@Serdeable +public class MyThing { + + private String value; + + public Optional getValue() { + return Optional.ofNullable(value); + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy b/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy new file mode 100644 index 000000000..2f56f7f69 --- /dev/null +++ b/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy @@ -0,0 +1,6 @@ +package io.micronaut.serde.tck.jackson.databind + +import io.micronaut.serde.jackson.OptionalGetterSpec + +class DatabindOptionalGetterSpec extends OptionalGetterSpec { +} From 55a20627a18b1fdd09cfa10639e0b7f3bc7496aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:24:29 +0000 Subject: [PATCH 3/4] Add equivalent test to test-suite-tck-serde Co-authored-by: graemerocher <66626+graemerocher@users.noreply.github.com> --- test-suite-tck-serde/build.gradle.kts | 3 +++ .../serde/tck/serde/SerdeOptionalGetterSpec.groovy | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy diff --git a/test-suite-tck-serde/build.gradle.kts b/test-suite-tck-serde/build.gradle.kts index b5e6d0e8b..9ab85482e 100644 --- a/test-suite-tck-serde/build.gradle.kts +++ b/test-suite-tck-serde/build.gradle.kts @@ -1,7 +1,10 @@ plugins { id("io.micronaut.build.internal.serde-tck-suite") + id("groovy") } dependencies { implementation(projects.micronautSerdeJackson) + + testImplementation(projects.micronautSerdeJacksonTck) } diff --git a/test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy b/test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy new file mode 100644 index 000000000..749d9f6fc --- /dev/null +++ b/test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy @@ -0,0 +1,8 @@ +package io.micronaut.serde.tck.serde + +import io.micronaut.serde.jackson.OptionalGetterSpec +import spock.lang.Ignore + +@Ignore("Reproducer for issue with Optional getter wrapping String property - fails in pure Serde, passes in Jackson Databind") +class SerdeOptionalGetterSpec extends OptionalGetterSpec { +} From 7de1cd518f7f2c43c20279801e24708db234608d Mon Sep 17 00:00:00 2001 From: Graeme Rocher Date: Mon, 19 Jan 2026 10:35:02 +0100 Subject: [PATCH 4/4] reproduce isse --- ...c.groovy => JsonOptionalGetterSpec.groovy} | 49 ++++++++++--------- .../serde/jackson/optionalwrap/MyThing.java | 39 --------------- .../SerdeJsonOptionalGetterSpec.groovy | 6 +++ .../DatabindJsonOptionalGetterSpec.groovy | 6 +++ .../DatabindOptionalGetterSpec.groovy | 6 --- test-suite-tck-serde/build.gradle.kts | 3 -- .../tck/serde/SerdeOptionalGetterSpec.groovy | 8 --- 7 files changed, 39 insertions(+), 78 deletions(-) rename serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/{OptionalGetterSpec.groovy => JsonOptionalGetterSpec.groovy} (58%) delete mode 100644 serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java create mode 100644 serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonOptionalGetterSpec.groovy create mode 100644 test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonOptionalGetterSpec.groovy delete mode 100644 test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy delete mode 100644 test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy diff --git a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/OptionalGetterSpec.groovy b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonOptionalGetterSpec.groovy similarity index 58% rename from serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/OptionalGetterSpec.groovy rename to serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonOptionalGetterSpec.groovy index b0d8131df..cbc4f921e 100644 --- a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/OptionalGetterSpec.groovy +++ b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonOptionalGetterSpec.groovy @@ -16,45 +16,50 @@ package io.micronaut.serde.jackson import io.micronaut.context.ApplicationContext -import io.micronaut.core.type.Argument -import io.micronaut.json.JsonMapper -import io.micronaut.serde.jackson.optionalwrap.MyThing /** * Tests serialization and deserialization of beans with Optional getters * but plain String setters. */ -abstract class OptionalGetterSpec extends JsonCompileSpec { +abstract class JsonOptionalGetterSpec extends JsonCompileSpec { void "test serialize and deserialize MyThing with Optional getter"() { given: - ApplicationContext context = ApplicationContext.run() - jsonMapper = context.getBean(JsonMapper) - def myThing = new MyThing() - myThing.setValue("someValue") + ApplicationContext context = buildContext('example.MyThing', ''' +package example; - when: - String json = jsonMapper.writeValueAsString(myThing) - MyThing other = jsonMapper.readValue(json, MyThing) +import io.micronaut.serde.annotation.Serdeable; - then: - other.getValue().orElse(null) == "someValue" +import java.util.Optional; - cleanup: - context.close() +/** + * Test class for Optional getter wrapping a String property. + * This tests the scenario where a getter returns Optional<String> but the + * setter accepts a plain String. + */ +@Serdeable +public class MyThing { + + private String value; + + public Optional getValue() { + return Optional.ofNullable(value); } - void "test deserialize MyThing from JSON"() { - given: - ApplicationContext context = ApplicationContext.run() - jsonMapper = context.getBean(JsonMapper) - String json = '{"value":"myOtherValue"}' + public void setValue(String value) { + this.value = value; + } +} +''') + def myThing = typeUnderTest.type.newInstance() + myThing.setValue("someValue") when: - MyThing other = jsonMapper.readValue(json, MyThing) + String json = jsonMapper.writeValueAsString(myThing) + def other = jsonMapper.readValue(json, typeUnderTest) then: - other.getValue().orElse(null) == "myOtherValue" + other.getValue().orElse(null) == "someValue" cleanup: context.close() diff --git a/serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java b/serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java deleted file mode 100644 index cbc9c1de7..000000000 --- a/serde-jackson-tck/src/main/java/io/micronaut/serde/jackson/optionalwrap/MyThing.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017-2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.serde.jackson.optionalwrap; - -import io.micronaut.serde.annotation.Serdeable; - -import java.util.Optional; - -/** - * Test class for Optional getter wrapping a String property. - * This tests the scenario where a getter returns Optional<String> but the - * setter accepts a plain String. - */ -@Serdeable -public class MyThing { - - private String value; - - public Optional getValue() { - return Optional.ofNullable(value); - } - - public void setValue(String value) { - this.value = value; - } -} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonOptionalGetterSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonOptionalGetterSpec.groovy new file mode 100644 index 000000000..8f6ff398b --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonOptionalGetterSpec.groovy @@ -0,0 +1,6 @@ +package io.micronaut.serde.jackson.annotation + +import io.micronaut.serde.jackson.JsonOptionalGetterSpec + +class SerdeJsonOptionalGetterSpec extends JsonOptionalGetterSpec { +} diff --git a/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonOptionalGetterSpec.groovy b/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonOptionalGetterSpec.groovy new file mode 100644 index 000000000..329afabd3 --- /dev/null +++ b/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindJsonOptionalGetterSpec.groovy @@ -0,0 +1,6 @@ +package io.micronaut.serde.tck.jackson.databind + +import io.micronaut.serde.jackson.JsonOptionalGetterSpec + +class DatabindJsonOptionalGetterSpec extends JsonOptionalGetterSpec { +} diff --git a/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy b/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy deleted file mode 100644 index 2f56f7f69..000000000 --- a/test-suite-tck-jackson-databind/src/test/groovy/io/micronaut/serde/tck/jackson/databind/DatabindOptionalGetterSpec.groovy +++ /dev/null @@ -1,6 +0,0 @@ -package io.micronaut.serde.tck.jackson.databind - -import io.micronaut.serde.jackson.OptionalGetterSpec - -class DatabindOptionalGetterSpec extends OptionalGetterSpec { -} diff --git a/test-suite-tck-serde/build.gradle.kts b/test-suite-tck-serde/build.gradle.kts index 9ab85482e..b5e6d0e8b 100644 --- a/test-suite-tck-serde/build.gradle.kts +++ b/test-suite-tck-serde/build.gradle.kts @@ -1,10 +1,7 @@ plugins { id("io.micronaut.build.internal.serde-tck-suite") - id("groovy") } dependencies { implementation(projects.micronautSerdeJackson) - - testImplementation(projects.micronautSerdeJacksonTck) } diff --git a/test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy b/test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy deleted file mode 100644 index 749d9f6fc..000000000 --- a/test-suite-tck-serde/src/test/groovy/io/micronaut/serde/tck/serde/SerdeOptionalGetterSpec.groovy +++ /dev/null @@ -1,8 +0,0 @@ -package io.micronaut.serde.tck.serde - -import io.micronaut.serde.jackson.OptionalGetterSpec -import spock.lang.Ignore - -@Ignore("Reproducer for issue with Optional getter wrapping String property - fails in pure Serde, passes in Jackson Databind") -class SerdeOptionalGetterSpec extends OptionalGetterSpec { -}