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}' + } +}
