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

jamesfredley pushed a commit to branch feat/json-org-groovy-json-facade-seed
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit f4e8cc7900e84859a8fdd9cd875cba805875348c
Author: James Fredley <[email protected]>
AuthorDate: Fri Jul 10 12:57:52 2026 -0400

    Seed groovy-json facade over org.grails.web.json
    
    Introduce GroovyJsonFacade and begin deprecation path for the vendored 
JSON.org fork.
    
    Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]
---
 grails-doc/src/en/guide/toc.yml                    |  1 +
 .../en/guide/upgrading/jsonGroovyJsonFacade.adoc   |  7 ++
 grails-web-common/build.gradle                     |  3 +-
 .../org/grails/web/json/GroovyJsonFacade.java      | 75 ++++++++++++++++++++++
 .../groovy/org/grails/web/json/JSONTokener.java    |  1 +
 .../grails/web/json/GroovyJsonFacadeSpec.groovy    | 44 +++++++++++++
 6 files changed, 130 insertions(+), 1 deletion(-)

diff --git a/grails-doc/src/en/guide/toc.yml b/grails-doc/src/en/guide/toc.yml
index b0e4a5c3ff..eecfb8bcb6 100644
--- a/grails-doc/src/en/guide/toc.yml
+++ b/grails-doc/src/en/guide/toc.yml
@@ -37,6 +37,7 @@ gettingStarted:
 upgrading:
   title: Upgrading from the previous versions
   upgrading80x: Upgrading from Grails 7 to Grails 8
+  jsonGroovyJsonFacade: JSON.org fork replacement seed
   upgrading72x: Upgrading from Grails 7.1 to Grails 7.2
   upgrading71x: Upgrading from Grails 7.0 to Grails 7.1
   upgrading70x: Upgrading from Grails 6 to Grails 7.0
diff --git a/grails-doc/src/en/guide/upgrading/jsonGroovyJsonFacade.adoc 
b/grails-doc/src/en/guide/upgrading/jsonGroovyJsonFacade.adoc
new file mode 100644
index 0000000000..df75fac316
--- /dev/null
+++ b/grails-doc/src/en/guide/upgrading/jsonGroovyJsonFacade.adoc
@@ -0,0 +1,7 @@
+=== JSON.org fork replacement seed
+
+Grails 8.1 starts the migration away from the internal `org.grails.web.json` 
JSON.org fork by adding a `GroovyJsonFacade` parser and renderer backed by 
`groovy-json`.
+The public `JSONObject`, `JSONArray`, and `JSONElement` facade types remain 
available, and this first slice converts groovy-json parsed objects back into 
those facade types.
+
+The internal `JSONTokener` path is deprecated for removal in Grails 9.0.
+Follow-up work should move remaining converter internals to the facade and 
keep application-facing APIs stable during the Grails 8.x line.
diff --git a/grails-web-common/build.gradle b/grails-web-common/build.gradle
index 7b7c814f79..db13ed9ec2 100644
--- a/grails-web-common/build.gradle
+++ b/grails-web-common/build.gradle
@@ -49,6 +49,7 @@ dependencies {
     }
 
     api 'org.apache.groovy:groovy'
+    api 'org.apache.groovy:groovy-json'
     api 'org.apache.groovy:groovy-templates'
     compileOnly 'jakarta.servlet:jakarta.servlet-api'
     testCompileOnly 'org.springframework:spring-test'
@@ -83,4 +84,4 @@ dependencies {
 apply {
     from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
     from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
-}
\ No newline at end of file
+}
diff --git 
a/grails-web-common/src/main/groovy/org/grails/web/json/GroovyJsonFacade.java 
b/grails-web-common/src/main/groovy/org/grails/web/json/GroovyJsonFacade.java
new file mode 100644
index 0000000000..d2d2587e40
--- /dev/null
+++ 
b/grails-web-common/src/main/groovy/org/grails/web/json/GroovyJsonFacade.java
@@ -0,0 +1,75 @@
+/*
+ *  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.json;
+
+import java.util.Collection;
+import java.util.Map;
+
+import groovy.json.JsonOutput;
+import groovy.json.JsonSlurper;
+
+/**
+ * Facade for moving JSON parsing and rendering toward groovy-json while 
preserving Grails JSONElement types.
+ *
+ * @since 8.1
+ */
+public final class GroovyJsonFacade {
+
+    private GroovyJsonFacade() {
+    }
+
+    public static JSONElement parse(String json) {
+        Object value = new JsonSlurper().parseText(json);
+        Object converted = toGrailsJson(value);
+        if (converted instanceof JSONElement) {
+            return (JSONElement) converted;
+        }
+        throw new JSONException("JSON text must describe an object or array");
+    }
+
+    public static String toJson(Object value) {
+        return JsonOutput.toJson(fromGrailsJson(value));
+    }
+
+    private static Object toGrailsJson(Object value) {
+        if (value instanceof Map) {
+            JSONObject object = new JSONObject();
+            ((Map<?, ?>) value).forEach((key, mapValue) -> 
object.put(String.valueOf(key), toGrailsJson(mapValue)));
+            return object;
+        }
+        if (value instanceof Collection) {
+            JSONArray array = new JSONArray();
+            for (Object element : (Collection<?>) value) {
+                array.put(toGrailsJson(element));
+            }
+            return array;
+        }
+        return value;
+    }
+
+    private static Object fromGrailsJson(Object value) {
+        if (value instanceof JSONObject) {
+            return value;
+        }
+        if (value instanceof JSONArray) {
+            return value;
+        }
+        return value;
+    }
+}
diff --git 
a/grails-web-common/src/main/groovy/org/grails/web/json/JSONTokener.java 
b/grails-web-common/src/main/groovy/org/grails/web/json/JSONTokener.java
index 2b0018e8b9..d14308ba32 100644
--- a/grails-web-common/src/main/groovy/org/grails/web/json/JSONTokener.java
+++ b/grails-web-common/src/main/groovy/org/grails/web/json/JSONTokener.java
@@ -17,6 +17,7 @@ import java.util.regex.Pattern;
  * @author JSON.org
  * @version 2
  */
+@Deprecated(since = "8.1", forRemoval = true)
 public class JSONTokener {
 
     /**
diff --git 
a/grails-web-common/src/test/groovy/org/grails/web/json/GroovyJsonFacadeSpec.groovy
 
b/grails-web-common/src/test/groovy/org/grails/web/json/GroovyJsonFacadeSpec.groovy
new file mode 100644
index 0000000000..5712c03d87
--- /dev/null
+++ 
b/grails-web-common/src/test/groovy/org/grails/web/json/GroovyJsonFacadeSpec.groovy
@@ -0,0 +1,44 @@
+/*
+ *  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.json
+
+import spock.lang.Specification
+
+class GroovyJsonFacadeSpec extends Specification {
+
+    void 'parse returns existing JSONObject facade backed by groovy-json 
parsing'() {
+        when:
+        JSONElement element = 
GroovyJsonFacade.parse('{"name":"Grails","versions":[8,9]}')
+
+        then:
+        element instanceof JSONObject
+        element.get('name') == 'Grails'
+        element.getJSONArray('versions').getInt(0) == 8
+    }
+
+    void 'toJson renders existing JSONObject facade through groovy-json'() {
+        given:
+        JSONObject object = new JSONObject()
+        object.put('name', 'Grails')
+        object.put('active', true)
+
+        expect:
+        GroovyJsonFacade.toJson(object) == '{"name":"Grails","active":true}'
+    }
+}

Reply via email to