You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/04/24 07:49:27 UTC
(causeway) 17/28: CAUSEWAY-3989: [v2] fixes localdate marshalling
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3989
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 98c06bce31a4d5ccb9fa0cbc3c3f16a20882a582
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Apr 22 11:41:03 2026 +0100
CAUSEWAY-3989: [v2] fixes localdate marshalling
---
.../applib/util/schema/CommandDtoUtils.java | 72 +++++++++++++++++++++-
.../CommandDtoUtils_toYaml_fromYaml_Test.java | 11 ++--
2 files changed, 77 insertions(+), 6 deletions(-)
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommandDtoUtils.java b/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommandDtoUtils.java
index 8fec5928b9d..85db27142b8 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommandDtoUtils.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommandDtoUtils.java
@@ -18,10 +18,15 @@
*/
package org.apache.causeway.applib.util.schema;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
+import javax.xml.datatype.DatatypeConstants;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.commons.internal.base._Lazy;
import org.apache.causeway.commons.internal.base._NullSafe;
@@ -44,7 +49,15 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import lombok.experimental.UtilityClass;
@@ -178,12 +191,69 @@ private abstract class AbstractDtoMixIn {}
// Mix-in to ignore unknown properties for ValueDto
@JsonIgnoreProperties(ignoreUnknown = true)
- private abstract class AbstractValueDtoMixIn {}
+ private abstract class AbstractValueDtoMixIn {
+ @JsonSerialize(using = LocalDateXmlGregorianCalendarSerializer.class)
+ abstract XMLGregorianCalendar getLocalDate();
+
+ @JsonDeserialize(using = LocalDateXmlGregorianCalendarDeserializer.class)
+ abstract void setLocalDate(XMLGregorianCalendar localDate);
+ }
private void valueDtoSupport(final ObjectMapper mb) {
mb.addMixIn(ValueDto.class, AbstractValueDtoMixIn.class);
}
+ private static final DatatypeFactory DATATYPE_FACTORY = datatypeFactory();
+
+ private static DatatypeFactory datatypeFactory() {
+ try {
+ return DatatypeFactory.newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException("Failed to initialize DatatypeFactory", ex);
+ }
+ }
+
+ private static final class LocalDateXmlGregorianCalendarSerializer
+ extends JsonSerializer<XMLGregorianCalendar> {
+
+ @Override
+ public void serialize(
+ final XMLGregorianCalendar value,
+ final JsonGenerator gen,
+ final SerializerProvider serializers) throws IOException {
+ if (value == null) {
+ gen.writeNull();
+ return;
+ }
+ final XMLGregorianCalendar dateOnly = DATATYPE_FACTORY.newXMLGregorianCalendarDate(
+ value.getYear(),
+ value.getMonth(),
+ value.getDay(),
+ DatatypeConstants.FIELD_UNDEFINED);
+ gen.writeString(dateOnly.toXMLFormat());
+ }
+ }
+
+ private static final class LocalDateXmlGregorianCalendarDeserializer
+ extends JsonDeserializer<XMLGregorianCalendar> {
+
+ @Override
+ public XMLGregorianCalendar deserialize(
+ final JsonParser p,
+ final DeserializationContext ctxt) throws IOException {
+ final String text = p.getValueAsString();
+ if (_Strings.isNullOrEmpty(text)) {
+ return null;
+ }
+ final XMLGregorianCalendar parsed = DATATYPE_FACTORY.newXMLGregorianCalendar(text);
+ return DATATYPE_FACTORY.newXMLGregorianCalendarDate(
+ parsed.getYear(),
+ parsed.getMonth(),
+ parsed.getDay(),
+ DatatypeConstants.FIELD_UNDEFINED);
+ }
+ }
+
private void memberDtoSupport(final ObjectMapper mb) {
// add mix-in so MemberDto carries @JsonTypeInfo without modifying source
mb.addMixIn(MemberDto.class, AbstractDtoMixIn.class);
diff --git a/api/applib/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_fromYaml_Test.java b/api/applib/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_fromYaml_Test.java
index 43ef012b482..f06a622e968 100644
--- a/api/applib/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_fromYaml_Test.java
+++ b/api/applib/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_toYaml_fromYaml_Test.java
@@ -41,7 +41,7 @@
class CommandDtoUtils_toYaml_fromYaml_Test {
@Test
- void localDate_roundtrip_is_broken_due_to_timezone_marshalling() throws Exception {
+ void localDate_roundtrips_as_date_only_without_timezone() throws Exception {
TimeZone originalDefault = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Amsterdam"));
try {
@@ -84,14 +84,15 @@ void localDate_roundtrip_is_broken_due_to_timezone_marshalling() throws Exceptio
.get(0)
.getLocalDate();
- // Demonstrates the current bug: localDate gets marshalled as date-time with timezone.
+ // Verify fixed behavior: localDate is emitted and roundtripped as date-only.
Assertions.assertThat(yaml)
- .containsPattern("localDate:\\s+\"\\d{4}-\\d{2}-\\d{2}T.*(Z|[+-]\\d{2}:\\d{2})\"");
+ .contains("localDate: \"2026-07-01\"")
+ .doesNotContain("localDate: \"2026-06-30T22:00:00.000+00:00\"")
+ .doesNotContain("localDate: \"2026-07-01T");
Assertions.assertThat(roundtrippedDate.toXMLFormat())
- .isNotEqualTo(originalDate.toXMLFormat());
+ .isEqualTo(originalDate.toXMLFormat());
} finally {
TimeZone.setDefault(originalDefault);
}
}
}
-