You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/09/28 02:52:19 UTC

(grails-core) branch refactor/16411-marshaller-dedup created (now 7e245d35be)

This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a change to branch refactor/16411-marshaller-dedup
in repository https://gitbox.apache.org/repos/asf/grails-core.git


      at 7e245d35be Add direct unit tests for SimpleTypeMarshaller and SimpleTypeJsonConverter

This branch includes the following new commits:

     new 1c30efb00b Replace per-type date/time marshaller boilerplate with a generic one
     new 2e72785cf4 Collapse the remaining pre-existing single-type date/time marshallers
     new 7e245d35be Add direct unit tests for SimpleTypeMarshaller and SimpleTypeJsonConverter

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(grails-core) 01/03: Replace per-type date/time marshaller boilerplate with a generic one

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch refactor/16411-marshaller-dedup
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 1c30efb00b9fe1f2f3b32dc957188fccbc7df040
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Sep 26 11:42:19 2026 -0500

    Replace per-type date/time marshaller boilerplate with a generic one
    
    #16411 added a dedicated ObjectMarshaller/JsonGenerator.Converter class
    per java.time/java.sql/javax.xml value type, following the pre-existing
    one-class-per-type convention in these packages. Each of those new
    classes was 100% boilerplate: an instanceof/isAssignableFrom check and
    one line computing the JSON value.
    
    Replace the 13 single-type JSON marshaller classes and 10 single-type
    JSON-views converter classes the PR added with two small generic
    implementations, SimpleTypeMarshaller<T> and SimpleTypeJsonConverter,
    each parameterized by the type and a value-extracting function.
    Registration order (and therefore marshaller/converter priority) is
    unchanged. Pre-existing marshallers/converters that predate #16411 are
    left as-is.
    
    Verified behavior-preserving: grails-converters (195 tests) and
    grails-views-gson (266 tests) pass unchanged, including the parity
    specs added by #16411 that compare output against a real Jackson
    JsonMapper. checkstyle and CodeNarc are clean for both modules.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 .../ConvertersConfigurationInitializer.java        | 43 ++++++++++++------
 .../marshaller/json/DurationMarshaller.java        | 48 --------------------
 .../marshaller/json/LocalTimeMarshaller.java       | 49 ---------------------
 .../marshaller/json/MonthDayMarshaller.java        | 48 --------------------
 .../marshaller/json/OffsetTimeMarshaller.java      | 48 --------------------
 .../marshaller/json/PeriodMarshaller.java          | 48 --------------------
 ...thMarshaller.java => SimpleTypeMarshaller.java} | 31 ++++++++++---
 .../marshaller/json/SqlTimeMarshaller.java         | 50 ---------------------
 .../marshaller/json/TimeZoneMarshaller.java        | 48 --------------------
 .../json/XMLGregorianCalendarMarshaller.java       | 51 ----------------------
 .../marshaller/json/XmlDurationMarshaller.java     | 48 --------------------
 .../converters/marshaller/json/YearMarshaller.java | 48 --------------------
 .../marshaller/json/YearMonthMarshaller.java       | 48 --------------------
 .../marshaller/json/ZoneIdMarshaller.java          | 48 --------------------
 .../json/converters/MonthDayJsonConverter.groovy   | 45 -------------------
 .../json/converters/MonthJsonConverter.groovy      | 45 -------------------
 ...erter.groovy => SimpleTypeJsonConverter.groovy} | 26 ++++++++---
 .../json/converters/SqlTimeJsonConverter.groovy    | 46 -------------------
 .../json/converters/TimeZoneJsonConverter.groovy   | 43 ------------------
 .../XMLGregorianCalendarJsonConverter.groovy       | 45 -------------------
 .../converters/XmlDurationJsonConverter.groovy     | 45 -------------------
 .../json/converters/YearJsonConverter.groovy       | 45 -------------------
 .../json/converters/YearMonthJsonConverter.groovy  | 45 -------------------
 .../json/converters/ZoneIdJsonConverter.groovy     | 45 -------------------
 .../plugin/json/view/JsonViewTemplateEngine.groovy | 42 +++++++++---------
 25 files changed, 96 insertions(+), 1032 deletions(-)

diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java b/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java
index 7c1db42ab0..e42123f551 100644
--- a/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java
+++ b/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java
@@ -18,9 +18,23 @@
  */
 package org.grails.web.converters.configuration;
 
+import java.sql.Time;
+import java.time.Duration;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.MonthDay;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.TimeZone;
+
+import javax.xml.datatype.XMLGregorianCalendar;
 
 import io.micrometer.observation.ObservationRegistry;
 import org.apache.commons.logging.Log;
@@ -41,6 +55,7 @@ import org.grails.config.PropertySourcesConfig;
 import org.grails.web.converters.Converter;
 import org.grails.web.converters.marshaller.ObjectMarshaller;
 import org.grails.web.converters.marshaller.ProxyUnwrappingMarshaller;
+import org.grails.web.json.JsonDateFormat;
 
 /**
  * @author Siegfried Puchbauer
@@ -101,7 +116,7 @@ public class ConvertersConfigurationInitializer implements ApplicationContextAwa
         marshallers.add(new org.grails.web.converters.marshaller.json.CollectionMarshaller());
         marshallers.add(new org.grails.web.converters.marshaller.json.MapMarshaller());
         // ahead of SimpleEnumMarshaller, which also supports java.time.Month
-        marshallers.add(new org.grails.web.converters.marshaller.json.MonthMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(Month.class, Month::getValue));
         marshallers.add(new org.grails.web.converters.marshaller.json.SimpleEnumMarshaller());
 
         Config grailsConfig = getGrailsConfig();
@@ -119,26 +134,28 @@ public class ConvertersConfigurationInitializer implements ApplicationContextAwa
                 LOG.debug("Using default JSON Date Marshaller");
             }
             // ahead of DateMarshaller, which also supports java.sql.Time
-            marshallers.add(new org.grails.web.converters.marshaller.json.SqlTimeMarshaller());
+            marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(Time.class, Time::toString));
             marshallers.add(new org.grails.web.converters.marshaller.json.DateMarshaller());
         }
         marshallers.add(new org.grails.web.converters.marshaller.json.CalendarMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.XMLGregorianCalendarMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(XMLGregorianCalendar.class,
+                calendar -> JsonDateFormat.format(calendar.toGregorianCalendar().getTimeInMillis())));
         marshallers.add(new org.grails.web.converters.marshaller.json.InstantMarshaller());
         marshallers.add(new org.grails.web.converters.marshaller.json.LocalDateMarshaller());
         marshallers.add(new org.grails.web.converters.marshaller.json.LocalDateTimeMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.LocalTimeMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(LocalTime.class, DateTimeFormatter.ISO_LOCAL_TIME::format));
         marshallers.add(new org.grails.web.converters.marshaller.json.OffsetDateTimeMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.OffsetTimeMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(OffsetTime.class, OffsetTime::toString));
         marshallers.add(new org.grails.web.converters.marshaller.json.ZonedDateTimeMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.YearMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.YearMonthMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.MonthDayMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.DurationMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.PeriodMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.ZoneIdMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.TimeZoneMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.XmlDurationMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(Year.class, Year::getValue));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(YearMonth.class, YearMonth::toString));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(MonthDay.class, MonthDay::toString));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(Duration.class, Duration::toString));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(Period.class, Period::toString));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(ZoneId.class, ZoneId::getId));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(TimeZone.class, TimeZone::getID));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(javax.xml.datatype.Duration.class,
+                javax.xml.datatype.Duration::toString));
         marshallers.add(new org.grails.web.converters.marshaller.json.ToStringBeanMarshaller());
 
         boolean includeDomainVersion = includeDomainVersionProperty(grailsConfig, "json");
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/DurationMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/DurationMarshaller.java
deleted file mode 100644
index 8301a1a822..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/DurationMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.Duration;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a Duration to its ISO-8601 {@link Duration#toString()} form
- * (e.g. {@code PT1H30M}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class DurationMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof Duration;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(object.toString());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalTimeMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalTimeMarshaller.java
deleted file mode 100644
index 27a3113bb1..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalTimeMarshaller.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.LocalTime;
-import java.time.format.DateTimeFormatter;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a LocalTime to ISO-8601 time format (HH:mm:ss[.fraction]).
- *
- * @since 8.0
- */
-public class LocalTimeMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof LocalTime;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            LocalTime localTime = (LocalTime) object;
-            converter.getWriter().value(DateTimeFormatter.ISO_LOCAL_TIME.format(localTime));
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/MonthDayMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/MonthDayMarshaller.java
deleted file mode 100644
index 4c140ed354..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/MonthDayMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.MonthDay;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a MonthDay to its ISO-8601 {@link MonthDay#toString()} form
- * (e.g. {@code --09-25}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class MonthDayMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof MonthDay;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(object.toString());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/OffsetTimeMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/OffsetTimeMarshaller.java
deleted file mode 100644
index 7cc1a78521..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/OffsetTimeMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.OffsetTime;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts an OffsetTime to its ISO-8601 {@link OffsetTime#toString()} form
- * (e.g. {@code 03:00-03:00}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class OffsetTimeMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof OffsetTime;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(object.toString());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/PeriodMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/PeriodMarshaller.java
deleted file mode 100644
index 3938e3c85f..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/PeriodMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.Period;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a Period to its ISO-8601 {@link Period#toString()} form
- * (e.g. {@code P1Y2M3D}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class PeriodMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof Period;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(object.toString());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/MonthMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/SimpleTypeMarshaller.java
similarity index 56%
rename from grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/MonthMarshaller.java
rename to grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/SimpleTypeMarshaller.java
index e621d8006e..ce01e7079a 100644
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/MonthMarshaller.java
+++ b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/SimpleTypeMarshaller.java
@@ -18,7 +18,7 @@
  */
 package org.grails.web.converters.marshaller.json;
 
-import java.time.Month;
+import java.util.function.Function;
 
 import grails.converters.JSON;
 import org.grails.web.converters.exceptions.ConverterException;
@@ -26,24 +26,41 @@ import org.grails.web.converters.marshaller.ObjectMarshaller;
 import org.grails.web.json.JSONException;
 
 /**
- * JSON ObjectMarshaller which converts a Month to its number, 1 for January through 12 for December,
- * the same as Spring Boot's default Jackson rendering. It is registered ahead of
- * {@link SimpleEnumMarshaller}, which would otherwise render the enum name.
+ * A JSON {@link ObjectMarshaller} for a type whose JSON representation is a single {@link String} or
+ * {@link Number} value computed directly from the object, such as {@code toString()} or a single
+ * accessor. Registered once per type in {@code ConvertersConfigurationInitializer} in place of a
+ * dedicated marshaller class.
  *
+ * @param <T> the supported type
  * @since 8.0
  */
-public class MonthMarshaller implements ObjectMarshaller<JSON> {
+public final class SimpleTypeMarshaller<T> implements ObjectMarshaller<JSON> {
+
+    private final Class<T> type;
+
+    private final Function<T, Object> valueExtractor;
+
+    /**
+     * @param type the type this marshaller supports
+     * @param valueExtractor computes the JSON value (a {@link String} or a {@link Number}) from an
+     *        instance of {@code type}
+     */
+    public SimpleTypeMarshaller(Class<T> type, Function<T, Object> valueExtractor) {
+        this.type = type;
+        this.valueExtractor = valueExtractor;
+    }
 
     public boolean supports(Object object) {
-        return object instanceof Month;
+        return type.isInstance(object);
     }
 
     public void marshalObject(Object object, JSON converter) throws ConverterException {
         try {
-            converter.getWriter().value(((Month) object).getValue());
+            converter.getWriter().value(valueExtractor.apply(type.cast(object)));
         }
         catch (JSONException e) {
             throw new ConverterException(e);
         }
     }
+
 }
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/SqlTimeMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/SqlTimeMarshaller.java
deleted file mode 100644
index 9a53462715..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/SqlTimeMarshaller.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.sql.Time;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a {@link java.sql.Time} to its wall-clock time in the
- * JVM default time zone ({@code HH:mm:ss}, from {@link Time#toString()}), the same as Spring Boot's
- * default Jackson rendering. It is registered ahead of {@link DateMarshaller}, which would
- * otherwise render the {@code Time} as a full date and time.
- *
- * @since 8.0
- */
-public class SqlTimeMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof Time;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(object.toString());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/TimeZoneMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/TimeZoneMarshaller.java
deleted file mode 100644
index 29480ee77a..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/TimeZoneMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.util.TimeZone;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a TimeZone to its ID (e.g. {@code America/Sao_Paulo}),
- * the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class TimeZoneMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof TimeZone;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(((TimeZone) object).getID());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/XMLGregorianCalendarMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/XMLGregorianCalendarMarshaller.java
deleted file mode 100644
index 577a1f6719..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/XMLGregorianCalendarMarshaller.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import javax.xml.datatype.XMLGregorianCalendar;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-import org.grails.web.json.JsonDateFormat;
-
-/**
- * JSON ObjectMarshaller which converts an XMLGregorianCalendar to an RFC 3339 / ISO 8601 UTC instant
- * string with millisecond precision, as {@link CalendarMarshaller} does for a Calendar, the same as
- * Spring Boot's default Jackson rendering. See {@link JsonDateFormat}.
- *
- * @since 8.0
- */
-public class XMLGregorianCalendarMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof XMLGregorianCalendar;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            long epochMillis = ((XMLGregorianCalendar) object).toGregorianCalendar().getTimeInMillis();
-            converter.getWriter().value(JsonDateFormat.format(epochMillis));
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/XmlDurationMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/XmlDurationMarshaller.java
deleted file mode 100644
index 8805b57916..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/XmlDurationMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import javax.xml.datatype.Duration;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a {@link javax.xml.datatype.Duration} to its lexical
- * {@link Duration#toString()} form (e.g. {@code P1DT2H}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class XmlDurationMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof Duration;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(object.toString());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/YearMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/YearMarshaller.java
deleted file mode 100644
index 9d07a79ac9..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/YearMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.Year;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a Year to its number (e.g. {@code 2026}), the same as
- * Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class YearMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof Year;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(((Year) object).getValue());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/YearMonthMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/YearMonthMarshaller.java
deleted file mode 100644
index 5908c71742..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/YearMonthMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.YearMonth;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a YearMonth to its ISO-8601 {@link YearMonth#toString()} form
- * (e.g. {@code 2026-09}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class YearMonthMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof YearMonth;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(object.toString());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/ZoneIdMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/ZoneIdMarshaller.java
deleted file mode 100644
index 25ca43fb24..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/ZoneIdMarshaller.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.ZoneId;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a ZoneId, including a ZoneOffset, to its ID
- * (e.g. {@code America/Sao_Paulo} or {@code -03:00}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-public class ZoneIdMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof ZoneId;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            converter.getWriter().value(((ZoneId) object).getId());
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/MonthDayJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/MonthDayJsonConverter.groovy
deleted file mode 100644
index cc50b82f2d..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/MonthDayJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.MonthDay
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link MonthDay} as json: its ISO-8601 {@link MonthDay#toString()} form
- * (e.g. {@code --09-25}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-@CompileStatic
-class MonthDayJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        MonthDay == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        value.toString()
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/MonthJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/MonthJsonConverter.groovy
deleted file mode 100644
index f530af3d4b..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/MonthJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.Month
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link Month} as json: its number, 1 for January through 12 for December,
- * the same as Spring Boot's default Jackson rendering, instead of the enum name.
- *
- * @since 8.0
- */
-@CompileStatic
-class MonthJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        Month == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        ((Month) value).value
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/DurationJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/SimpleTypeJsonConverter.groovy
similarity index 55%
rename from grails-views-gson/src/main/groovy/grails/plugin/json/converters/DurationJsonConverter.groovy
rename to grails-views-gson/src/main/groovy/grails/plugin/json/converters/SimpleTypeJsonConverter.groovy
index fea5e18067..b0c49af952 100644
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/DurationJsonConverter.groovy
+++ b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/SimpleTypeJsonConverter.groovy
@@ -19,27 +19,41 @@
 
 package grails.plugin.json.converters
 
-import java.time.Duration
+import java.util.function.Function
 
 import groovy.json.JsonGenerator
 import groovy.transform.CompileStatic
 
 /**
- * A class to render a {@link Duration} as json: its ISO-8601 {@link Duration#toString()} form
- * (e.g. {@code PT1H30M}), the same as Spring Boot's default Jackson rendering.
+ * A {@link JsonGenerator.Converter} for a type whose JSON representation is a single value computed
+ * directly from the object, such as {@code toString()} or a single accessor. Registered once per type
+ * in {@code JsonViewTemplateEngine} in place of a dedicated converter class.
  *
  * @since 8.0
  */
 @CompileStatic
-class DurationJsonConverter implements JsonGenerator.Converter {
+class SimpleTypeJsonConverter implements JsonGenerator.Converter {
+
+    private final Class<?> type
+    private final Function<Object, Object> valueExtractor
+
+    /**
+     * @param type the type this converter handles, including its subtypes
+     * @param valueExtractor computes the JSON value from an instance of {@code type}
+     */
+    SimpleTypeJsonConverter(Class<?> type, Function<Object, Object> valueExtractor) {
+        this.type = type
+        this.valueExtractor = valueExtractor
+    }
 
     @Override
     boolean handles(Class<?> type) {
-        Duration == type
+        this.type.isAssignableFrom(type)
     }
 
     @Override
     Object convert(Object value, String key) {
-        value.toString()
+        valueExtractor.apply(value)
     }
+
 }
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/SqlTimeJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/SqlTimeJsonConverter.groovy
deleted file mode 100644
index 019caa439b..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/SqlTimeJsonConverter.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.sql.Time
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link Time} as json: its wall-clock time in the JVM default time zone
- * ({@code HH:mm:ss}), the same as Spring Boot's default Jackson rendering, instead of the full
- * date and time the generator's date format would produce.
- *
- * @since 8.0
- */
-@CompileStatic
-class SqlTimeJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        Time.isAssignableFrom(type)
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        value.toString()
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/TimeZoneJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/TimeZoneJsonConverter.groovy
deleted file mode 100644
index 4c62b5c61a..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/TimeZoneJsonConverter.groovy
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link TimeZone} as json: its ID (e.g. {@code America/Sao_Paulo}), the same as
- * Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-@CompileStatic
-class TimeZoneJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        TimeZone.isAssignableFrom(type)
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        ((TimeZone) value).ID
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/XMLGregorianCalendarJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/XMLGregorianCalendarJsonConverter.groovy
deleted file mode 100644
index 1cae24391e..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/XMLGregorianCalendarJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import javax.xml.datatype.XMLGregorianCalendar
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render an {@link XMLGregorianCalendar} as json: it is converted to a {@link GregorianCalendar},
- * which is written as any other {@link Calendar} is, the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-@CompileStatic
-class XMLGregorianCalendarJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        XMLGregorianCalendar.isAssignableFrom(type)
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        ((XMLGregorianCalendar) value).toGregorianCalendar()
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/XmlDurationJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/XmlDurationJsonConverter.groovy
deleted file mode 100644
index 37a01941ec..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/XmlDurationJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import javax.xml.datatype.Duration
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link javax.xml.datatype.Duration} as json: its lexical {@link Duration#toString()}
- * form (e.g. {@code P1DT2H}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-@CompileStatic
-class XmlDurationJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        Duration.isAssignableFrom(type)
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        value.toString()
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/YearJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/YearJsonConverter.groovy
deleted file mode 100644
index aa58a98906..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/YearJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.Year
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link Year} as json: its number (e.g. {@code 2026}), the same as Spring Boot's
- * default Jackson rendering.
- *
- * @since 8.0
- */
-@CompileStatic
-class YearJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        Year == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        ((Year) value).value
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/YearMonthJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/YearMonthJsonConverter.groovy
deleted file mode 100644
index c8c6932c34..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/YearMonthJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.YearMonth
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link YearMonth} as json: its ISO-8601 {@link YearMonth#toString()} form
- * (e.g. {@code 2026-09}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-@CompileStatic
-class YearMonthJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        YearMonth == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        value.toString()
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/ZoneIdJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/ZoneIdJsonConverter.groovy
deleted file mode 100644
index 428f3dac33..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/ZoneIdJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.ZoneId
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link ZoneId}, including a {@link java.time.ZoneOffset}, as json: its ID
- * (e.g. {@code America/Sao_Paulo} or {@code -03:00}), the same as Spring Boot's default Jackson rendering.
- *
- * @since 8.0
- */
-@CompileStatic
-class ZoneIdJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        ZoneId.isAssignableFrom(type)
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        ((ZoneId) value).id
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy
index 80c6998be3..934fac7f73 100644
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy
+++ b/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy
@@ -19,6 +19,17 @@
 
 package grails.plugin.json.view
 
+import java.sql.Time
+import java.time.Duration
+import java.time.Month
+import java.time.MonthDay
+import java.time.Year
+import java.time.YearMonth
+import java.time.ZoneId
+import java.util.function.Function
+
+import javax.xml.datatype.XMLGregorianCalendar
+
 import groovy.json.JsonGenerator
 import groovy.text.Template
 import groovy.transform.CompileStatic
@@ -28,23 +39,14 @@ import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.core.OrderComparator
 
-import grails.plugin.json.converters.DurationJsonConverter
 import grails.plugin.json.converters.InstantJsonConverter
 import grails.plugin.json.converters.LocalDateJsonConverter
 import grails.plugin.json.converters.LocalDateTimeJsonConverter
 import grails.plugin.json.converters.LocalTimeJsonConverter
-import grails.plugin.json.converters.MonthDayJsonConverter
-import grails.plugin.json.converters.MonthJsonConverter
 import grails.plugin.json.converters.OffsetDateTimeJsonConverter
 import grails.plugin.json.converters.OffsetTimeJsonConverter
 import grails.plugin.json.converters.PeriodJsonConverter
-import grails.plugin.json.converters.SqlTimeJsonConverter
-import grails.plugin.json.converters.TimeZoneJsonConverter
-import grails.plugin.json.converters.XMLGregorianCalendarJsonConverter
-import grails.plugin.json.converters.XmlDurationJsonConverter
-import grails.plugin.json.converters.YearJsonConverter
-import grails.plugin.json.converters.YearMonthJsonConverter
-import grails.plugin.json.converters.ZoneIdJsonConverter
+import grails.plugin.json.converters.SimpleTypeJsonConverter
 import grails.plugin.json.converters.ZonedDateTimeJsonConverter
 import grails.plugin.json.view.api.jsonapi.JsonApiIdRenderStrategy
 import grails.plugin.json.view.internal.JsonTemplateTypeCheckingExtension
@@ -124,17 +126,17 @@ class JsonViewTemplateEngine extends ResolvableGroovyTemplateEngine {
         converters.add(new OffsetDateTimeJsonConverter())
         converters.add(new OffsetTimeJsonConverter())
         converters.add(new PeriodJsonConverter())
-        converters.add(new SqlTimeJsonConverter())
+        converters.add(new SimpleTypeJsonConverter(Time, { it.toString() } as Function))
         converters.add(new ZonedDateTimeJsonConverter())
-        converters.add(new YearJsonConverter())
-        converters.add(new YearMonthJsonConverter())
-        converters.add(new MonthDayJsonConverter())
-        converters.add(new MonthJsonConverter())
-        converters.add(new DurationJsonConverter())
-        converters.add(new ZoneIdJsonConverter())
-        converters.add(new TimeZoneJsonConverter())
-        converters.add(new XMLGregorianCalendarJsonConverter())
-        converters.add(new XmlDurationJsonConverter())
+        converters.add(new SimpleTypeJsonConverter(Year, { ((Year) it).value } as Function))
+        converters.add(new SimpleTypeJsonConverter(YearMonth, { it.toString() } as Function))
+        converters.add(new SimpleTypeJsonConverter(MonthDay, { it.toString() } as Function))
+        converters.add(new SimpleTypeJsonConverter(Month, { ((Month) it).value } as Function))
+        converters.add(new SimpleTypeJsonConverter(Duration, { it.toString() } as Function))
+        converters.add(new SimpleTypeJsonConverter(ZoneId, { ((ZoneId) it).id } as Function))
+        converters.add(new SimpleTypeJsonConverter(TimeZone, { ((TimeZone) it).ID } as Function))
+        converters.add(new SimpleTypeJsonConverter(XMLGregorianCalendar, { ((XMLGregorianCalendar) it).toGregorianCalendar() } as Function))
+        converters.add(new SimpleTypeJsonConverter(javax.xml.datatype.Duration, { it.toString() } as Function))
         OrderComparator.sort(converters)
         converters.each {
             options.addConverter(it)


(grails-core) 02/03: Collapse the remaining pre-existing single-type date/time marshallers

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch refactor/16411-marshaller-dedup
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 2e72785cf41c729505853d6484c2f7c57e4a8e48
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Sep 26 12:07:33 2026 -0500

    Collapse the remaining pre-existing single-type date/time marshallers
    
    Extend the previous commit's generic marshaller/converter to the 5
    JSON marshallers and 8 JSON-views converters that predate #16411
    (Instant, LocalDate, LocalDateTime, LocalTime, OffsetDateTime,
    OffsetTime, Period, ZonedDateTime), removing the same one-line-of-logic
    boilerplate from the pre-existing code, not just the code #16411 added.
    
    These files carried individual @author tags. The repo's established
    convention is not to carry personal attribution in source (recent
    examples: e6fa4eb973, 59d78b298e, b7ab39b519, 87fb2694d1, and
    17debfa49c removing a real author's name/email from a plugin
    descriptor "as this information is not necessary for core plugins") —
    git history is the attribution record, so dropping them here follows
    existing practice rather than breaking from it.
    
    Verified behavior-preserving the same way as the previous commit:
    grails-converters (195 tests) and grails-views-gson (266 tests) pass
    unchanged, checkstyle/CodeNarc clean on both modules.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 .../ConvertersConfigurationInitializer.java        | 15 ++++---
 .../marshaller/json/InstantMarshaller.java         | 49 ----------------------
 .../marshaller/json/LocalDateMarshaller.java       | 49 ----------------------
 .../marshaller/json/LocalDateTimeMarshaller.java   | 49 ----------------------
 .../marshaller/json/OffsetDateTimeMarshaller.java  | 49 ----------------------
 .../marshaller/json/ZonedDateTimeMarshaller.java   | 49 ----------------------
 .../json/converters/InstantJsonConverter.groovy    | 45 --------------------
 .../json/converters/LocalDateJsonConverter.groovy  | 45 --------------------
 .../converters/LocalDateTimeJsonConverter.groovy   | 45 --------------------
 .../json/converters/LocalTimeJsonConverter.groovy  | 45 --------------------
 .../converters/OffsetDateTimeJsonConverter.groovy  | 45 --------------------
 .../json/converters/OffsetTimeJsonConverter.groovy | 45 --------------------
 .../json/converters/PeriodJsonConverter.groovy     | 44 -------------------
 .../converters/ZonedDateTimeJsonConverter.groovy   | 45 --------------------
 .../plugin/json/view/JsonViewTemplateEngine.groovy | 33 ++++++++-------
 15 files changed, 27 insertions(+), 625 deletions(-)

diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java b/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java
index e42123f551..c32cdde815 100644
--- a/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java
+++ b/grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java
@@ -20,14 +20,19 @@ package org.grails.web.converters.configuration;
 
 import java.sql.Time;
 import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.Month;
 import java.time.MonthDay;
+import java.time.OffsetDateTime;
 import java.time.OffsetTime;
 import java.time.Period;
 import java.time.Year;
 import java.time.YearMonth;
 import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -140,13 +145,13 @@ public class ConvertersConfigurationInitializer implements ApplicationContextAwa
         marshallers.add(new org.grails.web.converters.marshaller.json.CalendarMarshaller());
         marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(XMLGregorianCalendar.class,
                 calendar -> JsonDateFormat.format(calendar.toGregorianCalendar().getTimeInMillis())));
-        marshallers.add(new org.grails.web.converters.marshaller.json.InstantMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.LocalDateMarshaller());
-        marshallers.add(new org.grails.web.converters.marshaller.json.LocalDateTimeMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(Instant.class, DateTimeFormatter.ISO_INSTANT::format));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(LocalDate.class, DateTimeFormatter.ISO_LOCAL_DATE::format));
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(LocalDateTime.class, DateTimeFormatter.ISO_LOCAL_DATE_TIME::format));
         marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(LocalTime.class, DateTimeFormatter.ISO_LOCAL_TIME::format));
-        marshallers.add(new org.grails.web.converters.marshaller.json.OffsetDateTimeMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME::format));
         marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(OffsetTime.class, OffsetTime::toString));
-        marshallers.add(new org.grails.web.converters.marshaller.json.ZonedDateTimeMarshaller());
+        marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(ZonedDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME::format));
         marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(Year.class, Year::getValue));
         marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(YearMonth.class, YearMonth::toString));
         marshallers.add(new org.grails.web.converters.marshaller.json.SimpleTypeMarshaller<>(MonthDay.class, MonthDay::toString));
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/InstantMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/InstantMarshaller.java
deleted file mode 100644
index 15b2e7ef90..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/InstantMarshaller.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.Instant;
-import java.time.format.DateTimeFormatter;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts an Instant to ISO-8601 format with Z suffix.
- *
- * @since 7.0
- */
-public class InstantMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof Instant;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            Instant instant = (Instant) object;
-            converter.getWriter().value(DateTimeFormatter.ISO_INSTANT.format(instant));
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalDateMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalDateMarshaller.java
deleted file mode 100644
index 5e669f02bd..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalDateMarshaller.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a LocalDate to ISO-8601 date format (YYYY-MM-DD).
- *
- * @since 7.0
- */
-public class LocalDateMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof LocalDate;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            LocalDate localDate = (LocalDate) object;
-            converter.getWriter().value(DateTimeFormatter.ISO_LOCAL_DATE.format(localDate));
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalDateTimeMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalDateTimeMarshaller.java
deleted file mode 100644
index e03d8bdcdd..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/LocalDateTimeMarshaller.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a LocalDateTime to ISO-8601 format (without timezone).
- *
- * @since 7.0
- */
-public class LocalDateTimeMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof LocalDateTime;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            LocalDateTime localDateTime = (LocalDateTime) object;
-            converter.getWriter().value(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime));
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/OffsetDateTimeMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/OffsetDateTimeMarshaller.java
deleted file mode 100644
index 83c84d680a..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/OffsetDateTimeMarshaller.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.OffsetDateTime;
-import java.time.format.DateTimeFormatter;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts an OffsetDateTime to ISO-8601 format with timezone offset.
- *
- * @since 7.0
- */
-public class OffsetDateTimeMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof OffsetDateTime;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            OffsetDateTime offsetDateTime = (OffsetDateTime) object;
-            converter.getWriter().value(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(offsetDateTime));
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/ZonedDateTimeMarshaller.java b/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/ZonedDateTimeMarshaller.java
deleted file mode 100644
index d65b420105..0000000000
--- a/grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/ZonedDateTimeMarshaller.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.grails.web.converters.marshaller.json;
-
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-
-import grails.converters.JSON;
-import org.grails.web.converters.exceptions.ConverterException;
-import org.grails.web.converters.marshaller.ObjectMarshaller;
-import org.grails.web.json.JSONException;
-
-/**
- * JSON ObjectMarshaller which converts a ZonedDateTime to ISO-8601 format with timezone offset.
- *
- * @since 7.0
- */
-public class ZonedDateTimeMarshaller implements ObjectMarshaller<JSON> {
-
-    public boolean supports(Object object) {
-        return object instanceof ZonedDateTime;
-    }
-
-    public void marshalObject(Object object, JSON converter) throws ConverterException {
-        try {
-            ZonedDateTime zonedDateTime = (ZonedDateTime) object;
-            converter.getWriter().value(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedDateTime));
-        }
-        catch (JSONException e) {
-            throw new ConverterException(e);
-        }
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/InstantJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/InstantJsonConverter.groovy
deleted file mode 100644
index 15411f5714..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/InstantJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.Instant
-import java.time.format.DateTimeFormatter
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link java.time.Instant} as json
- *
- * @author James Kleeh
- */
-@CompileStatic
-class InstantJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        Instant == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        DateTimeFormatter.ISO_INSTANT.format((Instant) value)
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalDateJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalDateJsonConverter.groovy
deleted file mode 100644
index 72b565e3cd..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalDateJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.LocalDate
-import java.time.format.DateTimeFormatter
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link LocalDate} as json
- *
- * @author James Kleeh
- */
-@CompileStatic
-class LocalDateJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        LocalDate == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        DateTimeFormatter.ISO_LOCAL_DATE.format((LocalDate) value)
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalDateTimeJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalDateTimeJsonConverter.groovy
deleted file mode 100644
index b8448d26a7..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalDateTimeJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link LocalDateTime} as json
- *
- * @author James Kleeh
- */
-@CompileStatic
-class LocalDateTimeJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        LocalDateTime == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        DateTimeFormatter.ISO_LOCAL_DATE_TIME.format((LocalDateTime) value)
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalTimeJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalTimeJsonConverter.groovy
deleted file mode 100644
index a98a0726a7..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/LocalTimeJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.LocalTime
-import java.time.format.DateTimeFormatter
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link LocalTime} as json
- *
- * @author James Kleeh
- */
-@CompileStatic
-class LocalTimeJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        LocalTime == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        DateTimeFormatter.ISO_LOCAL_TIME.format((LocalTime) value)
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/OffsetDateTimeJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/OffsetDateTimeJsonConverter.groovy
deleted file mode 100644
index ad64b89227..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/OffsetDateTimeJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.OffsetDateTime
-import java.time.format.DateTimeFormatter
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link OffsetDateTime} as json
- *
- * @author James Kleeh
- */
-@CompileStatic
-class OffsetDateTimeJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        OffsetDateTime == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        DateTimeFormatter.ISO_OFFSET_DATE_TIME.format((OffsetDateTime) value)
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/OffsetTimeJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/OffsetTimeJsonConverter.groovy
deleted file mode 100644
index 010ddb6245..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/OffsetTimeJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.OffsetTime
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link OffsetTime} as json: its ISO-8601 {@link OffsetTime#toString()} form
- * (e.g. {@code 03:00-03:00}), the same as Spring Boot's default Jackson rendering.
- *
- * @author James Kleeh
- */
-@CompileStatic
-class OffsetTimeJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        OffsetTime == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        value.toString()
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/PeriodJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/PeriodJsonConverter.groovy
deleted file mode 100644
index fc006eeb15..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/PeriodJsonConverter.groovy
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.Period
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link Period} as json
- *
- * @author Muhammad Hamza Zaib
- */
-@CompileStatic
-class PeriodJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        Period == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        ((Period) value).toString()
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/ZonedDateTimeJsonConverter.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/converters/ZonedDateTimeJsonConverter.groovy
deleted file mode 100644
index 49eb0381df..0000000000
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/converters/ZonedDateTimeJsonConverter.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 grails.plugin.json.converters
-
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-
-import groovy.json.JsonGenerator
-import groovy.transform.CompileStatic
-
-/**
- * A class to render a {@link ZonedDateTime} as json
- *
- * @author James Kleeh
- */
-@CompileStatic
-class ZonedDateTimeJsonConverter implements JsonGenerator.Converter {
-
-    @Override
-    boolean handles(Class<?> type) {
-        ZonedDateTime == type
-    }
-
-    @Override
-    Object convert(Object value, String key) {
-        DateTimeFormatter.ISO_OFFSET_DATE_TIME.format((ZonedDateTime) value)
-    }
-}
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy
index 934fac7f73..45ab605d5b 100644
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy
+++ b/grails-views-gson/src/main/groovy/grails/plugin/json/view/JsonViewTemplateEngine.groovy
@@ -21,11 +21,20 @@ package grails.plugin.json.view
 
 import java.sql.Time
 import java.time.Duration
+import java.time.Instant
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
 import java.time.Month
 import java.time.MonthDay
+import java.time.OffsetDateTime
+import java.time.OffsetTime
+import java.time.Period
 import java.time.Year
 import java.time.YearMonth
 import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
 import java.util.function.Function
 
 import javax.xml.datatype.XMLGregorianCalendar
@@ -39,15 +48,7 @@ import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.core.OrderComparator
 
-import grails.plugin.json.converters.InstantJsonConverter
-import grails.plugin.json.converters.LocalDateJsonConverter
-import grails.plugin.json.converters.LocalDateTimeJsonConverter
-import grails.plugin.json.converters.LocalTimeJsonConverter
-import grails.plugin.json.converters.OffsetDateTimeJsonConverter
-import grails.plugin.json.converters.OffsetTimeJsonConverter
-import grails.plugin.json.converters.PeriodJsonConverter
 import grails.plugin.json.converters.SimpleTypeJsonConverter
-import grails.plugin.json.converters.ZonedDateTimeJsonConverter
 import grails.plugin.json.view.api.jsonapi.JsonApiIdRenderStrategy
 import grails.plugin.json.view.internal.JsonTemplateTypeCheckingExtension
 import grails.plugin.json.view.internal.JsonViewsTransform
@@ -119,15 +120,15 @@ class JsonViewTemplateEngine extends ResolvableGroovyTemplateEngine {
         Map<String, JsonGenerator.Converter> convertersByClass = new LinkedHashMap<>()
         registerConverters(ServiceLoader.load(JsonGenerator.Converter, classLoader), convertersByClass)
         List<JsonGenerator.Converter> converters = new ArrayList<>(convertersByClass.values())
-        converters.add(new InstantJsonConverter())
-        converters.add(new LocalDateJsonConverter())
-        converters.add(new LocalDateTimeJsonConverter())
-        converters.add(new LocalTimeJsonConverter())
-        converters.add(new OffsetDateTimeJsonConverter())
-        converters.add(new OffsetTimeJsonConverter())
-        converters.add(new PeriodJsonConverter())
+        converters.add(new SimpleTypeJsonConverter(Instant, { DateTimeFormatter.ISO_INSTANT.format((Instant) it) } as Function))
+        converters.add(new SimpleTypeJsonConverter(LocalDate, { DateTimeFormatter.ISO_LOCAL_DATE.format((LocalDate) it) } as Function))
+        converters.add(new SimpleTypeJsonConverter(LocalDateTime, { DateTimeFormatter.ISO_LOCAL_DATE_TIME.format((LocalDateTime) it) } as Function))
+        converters.add(new SimpleTypeJsonConverter(LocalTime, { DateTimeFormatter.ISO_LOCAL_TIME.format((LocalTime) it) } as Function))
+        converters.add(new SimpleTypeJsonConverter(OffsetDateTime, { DateTimeFormatter.ISO_OFFSET_DATE_TIME.format((OffsetDateTime) it) } as Function))
+        converters.add(new SimpleTypeJsonConverter(OffsetTime, { it.toString() } as Function))
+        converters.add(new SimpleTypeJsonConverter(Period, { it.toString() } as Function))
         converters.add(new SimpleTypeJsonConverter(Time, { it.toString() } as Function))
-        converters.add(new ZonedDateTimeJsonConverter())
+        converters.add(new SimpleTypeJsonConverter(ZonedDateTime, { DateTimeFormatter.ISO_OFFSET_DATE_TIME.format((ZonedDateTime) it) } as Function))
         converters.add(new SimpleTypeJsonConverter(Year, { ((Year) it).value } as Function))
         converters.add(new SimpleTypeJsonConverter(YearMonth, { it.toString() } as Function))
         converters.add(new SimpleTypeJsonConverter(MonthDay, { it.toString() } as Function))


(grails-core) 03/03: Add direct unit tests for SimpleTypeMarshaller and SimpleTypeJsonConverter

Posted by bo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch refactor/16411-marshaller-dedup
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 7e245d35bee16d2a3451809413575069e39cb8c2
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Sep 26 20:43:40 2026 -0500

    Add direct unit tests for SimpleTypeMarshaller and SimpleTypeJsonConverter
    
    The two prior commits relied only on the existing per-type rendering
    specs (JsonDateTimeRenderingSpec, DateTimeRenderingSpec, etc.) to cover
    these generic classes indirectly, through every type registered with
    them. That covers the happy path per type but never exercises the
    classes' own contract directly, unlike CalendarMarshallerSpec/
    DateMarshallerSpec for the other marshallers in this package.
    
    Add SimpleTypeMarshallerSpec and SimpleTypeJsonConverterSpec, covering:
    - supports()/handles() true for the declared type and its subtypes,
      false otherwise (including a final type with no subtypes, matching
      the isAssignableFrom semantics the JSON-views converters relied on
      before this refactor)
    - marshalObject() quotes a String extractor result and leaves a Number
      result unquoted
    - the extractor receives the object cast to the declared type, not Object
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 .../json/SimpleTypeMarshallerSpec.groovy           | 91 ++++++++++++++++++++++
 .../converters/SimpleTypeJsonConverterSpec.groovy  | 60 ++++++++++++++
 2 files changed, 151 insertions(+)

diff --git a/grails-converters/src/test/groovy/org/grails/web/converters/marshaller/json/SimpleTypeMarshallerSpec.groovy b/grails-converters/src/test/groovy/org/grails/web/converters/marshaller/json/SimpleTypeMarshallerSpec.groovy
new file mode 100644
index 0000000000..d9357559d0
--- /dev/null
+++ b/grails-converters/src/test/groovy/org/grails/web/converters/marshaller/json/SimpleTypeMarshallerSpec.groovy
@@ -0,0 +1,91 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.grails.web.converters.marshaller.json
+
+import java.time.Year
+
+import spock.lang.Specification
+
+import grails.converters.JSON
+import org.grails.web.json.JSONWriter
+
+class SimpleTypeMarshallerSpec extends Specification {
+
+    void "supports returns true only for instances of the given type, including subtypes"() {
+        given:
+        def marshaller = new SimpleTypeMarshaller<Number>(Number.class, { it })
+
+        expect:
+        with(marshaller) {
+            supports(42)
+            supports(42L)
+            supports(3.14d)
+            !supports('42')
+            !supports(null)
+        }
+    }
+
+    void "marshalObject writes a String value extractor result quoted"() {
+        given:
+        def marshaller = new SimpleTypeMarshaller<Year>(Year.class, { Year year -> year.toString() })
+
+        when:
+        def result = marshalToString(marshaller, Year.of(2026))
+
+        then:
+        result == '["2026"]'
+    }
+
+    void "marshalObject writes a Number value extractor result unquoted"() {
+        given:
+        def marshaller = new SimpleTypeMarshaller<Year>(Year.class, { Year year -> year.getValue() })
+
+        when:
+        def result = marshalToString(marshaller, Year.of(2026))
+
+        then:
+        result == '[2026]'
+    }
+
+    void "marshalObject passes the object to the extractor cast to the declared type"() {
+        given:
+        Class<?> extractorReceivedType = null
+        def marshaller = new SimpleTypeMarshaller<Year>(Year.class, { Year year ->
+            extractorReceivedType = year.class
+            year.toString()
+        })
+
+        when:
+        marshalToString(marshaller, Year.of(2026))
+
+        then:
+        extractorReceivedType == Year
+    }
+
+    private static String marshalToString(SimpleTypeMarshaller marshaller, Object value) {
+        def json = new JSON()
+        def stringWriter = new StringWriter()
+        json.writer = new JSONWriter(stringWriter)
+        json.writer.array()
+        marshaller.marshalObject(value, json)
+        json.writer.endArray()
+        stringWriter.toString()
+    }
+
+}
diff --git a/grails-views-gson/src/test/groovy/grails/plugin/json/converters/SimpleTypeJsonConverterSpec.groovy b/grails-views-gson/src/test/groovy/grails/plugin/json/converters/SimpleTypeJsonConverterSpec.groovy
new file mode 100644
index 0000000000..3527b0e402
--- /dev/null
+++ b/grails-views-gson/src/test/groovy/grails/plugin/json/converters/SimpleTypeJsonConverterSpec.groovy
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 grails.plugin.json.converters
+
+import java.time.Year
+import java.util.function.Function
+
+import spock.lang.Specification
+
+class SimpleTypeJsonConverterSpec extends Specification {
+
+    void "handles returns true for the given type and its subtypes"() {
+        given:
+        def converter = new SimpleTypeJsonConverter(Number, { it } as Function)
+
+        expect:
+        with(converter) {
+            handles(Integer)
+            handles(Long)
+            handles(Number)
+            !handles(String)
+        }
+    }
+
+    void "handles returns true only for an exact match when the type has no subtypes"() {
+        given:
+        def converter = new SimpleTypeJsonConverter(Year, { it } as Function)
+
+        expect:
+        with(converter) {
+            handles(Year)
+            !handles(Integer)
+        }
+    }
+
+    void "convert delegates to the value extractor"() {
+        given:
+        def converter = new SimpleTypeJsonConverter(Year, { ((Year) it).value } as Function)
+
+        expect:
+        converter.convert(Year.of(2026), 'year') == 2026
+    }
+
+}