Skip to content

Seed groovy-json facade over org.grails.web.json#15958

Open
jamesfredley wants to merge 1 commit into
8.0.xfrom
feat/json-org-groovy-json-facade-seed
Open

Seed groovy-json facade over org.grails.web.json#15958
jamesfredley wants to merge 1 commit into
8.0.xfrom
feat/json-org-groovy-json-facade-seed

Conversation

@jamesfredley

Copy link
Copy Markdown
Contributor

Description

What was found

Problem Impact
org.grails.web.json is a vendored JSON.org-era fork Maintenance and security risk
Converters still depend on public facade types Cannot rip out internals in one step
Codebase 4.1 marked this strategic 8.x/9.0 work Needs a deprecation facade seed

What changed

Area Change
Facade Add GroovyJsonFacade bridging toward groovy-json
Public API Keep existing org.grails.web.json types
Docs Upgrade note with 9.0 removal plan
Tests Facade parse/serialize coverage

Out of scope / follow-up

Topic Status
Full internal rewrite of all converters Follow-up
Removing public JSONObject/JSONArray 9.0
Benchmarks vs current fork Follow-up

Related MD topics

Source Topic
Codebase 4.1 Vendored JSON.org-era fork

Contributor Checklist

Issue and Scope

  • Starter deprecation path.
  • Public API preserved.
  • Single focused seed.
  • Targets 8.0.x.

Code Quality

  • Tests added.
  • Focused web-common tests run.
  • No mass reformatting.
  • AI starting point labeled.

Licensing and Attribution

  • Apache License 2.0.
  • Contributor rights confirmed.
  • ai-generated-starting-point label applied.

Documentation

  • Upgrade note added.
  • Scope explained.

Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]

Introduce GroovyJsonFacade and begin deprecation path for the vendored JSON.org fork.

Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR seeds a GroovyJsonFacade in grails-web-common to start migrating Grails’ vendored org.grails.web.json JSON.org-era fork toward groovy-json, while keeping the existing JSONElement / JSONObject / JSONArray public facade types in place.

Changes:

  • Added GroovyJsonFacade to parse/serialize via groovy.json.JsonSlurper / JsonOutput while returning existing Grails JSON facade types.
  • Deprecated the internal JSONTokener as part of the planned removal path.
  • Added an upgrade note and initial Spock coverage for the new facade.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
grails-web-common/src/main/groovy/org/grails/web/json/GroovyJsonFacade.java Introduces the new groovy-json–backed facade to bridge toward replacing the internal JSON.org fork.
grails-web-common/src/main/groovy/org/grails/web/json/JSONTokener.java Deprecates the legacy tokener to steer usage toward the new facade path.
grails-web-common/src/test/groovy/org/grails/web/json/GroovyJsonFacadeSpec.groovy Adds initial tests for parsing/serialization via the new facade (needs adjustments per review comments).
grails-web-common/build.gradle Adds groovy-json dependency to support the new facade.
grails-doc/src/en/guide/upgrading/jsonGroovyJsonFacade.adoc Documents the migration seed and 9.0 removal intent.
grails-doc/src/en/guide/toc.yml Adds the new upgrading guide entry to the TOC.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +37 to +44
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");
}
Comment on lines +41 to +42
expect:
GroovyJsonFacade.toJson(object) == '{"name":"Grails","active":true}'
Comment on lines +34 to +35

void 'toJson renders existing JSONObject facade through groovy-json'() {
Comment on lines 51 to 53
api 'org.apache.groovy:groovy'
api 'org.apache.groovy:groovy-json'
api 'org.apache.groovy:groovy-templates'
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.5665%. Comparing base (b00e83e) to head (f4e8cc7).
⚠️ Report is 8 commits behind head on 8.0.x.

Files with missing lines Patch % Lines
...n/groovy/org/grails/web/json/GroovyJsonFacade.java 72.7273% 4 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #15958        +/-   ##
==================================================
+ Coverage     49.5403%   49.5665%   +0.0262%     
- Complexity      16922      16943        +21     
==================================================
  Files            1999       2000         +1     
  Lines           93754      93775        +21     
  Branches        16420      16423         +3     
==================================================
+ Hits            46446      46481        +35     
+ Misses          40145      40125        -20     
- Partials         7163       7169         +6     
Files with missing lines Coverage Δ
...c/main/groovy/org/grails/web/json/JSONTokener.java 16.2651% <ø> (ø)
...n/groovy/org/grails/web/json/GroovyJsonFacade.java 72.7273% <72.7273%> (ø)

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jdaugherty

Copy link
Copy Markdown
Contributor

Instead of using our vendored version we should just adopt the upstream version. I disagree with this change.

@testlens-app

testlens-app Bot commented Jul 10, 2026

Copy link
Copy Markdown

🚨 TestLens detected 1 failed test 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

CI - Groovy Joint Validation Build / build_grails > :grails-test-examples-spring-security-core-misc-functional-test-app-group:integrationTest

Test Runs Flakiness
SecuredControllerSpec > test login as sherlock, sherlock belongs to detective groups. All detectives have the role ADMIN 0% 🟢

🏷️ Commit: f4e8cc7
▶️ Tests: 9966 executed
⚪️ Checks: 60/60 completed

Test Failures

SecuredControllerSpec > test login as sherlock, sherlock belongs to detective groups. All detectives have the role ADMIN (:grails-test-examples-spring-security-core-misc-functional-test-app-group:integrationTest in CI - Groovy Joint Validation Build / build_grails)
Condition not satisfied:

pageSource.contains('you have ROLE_ADMIN')
|          |
|          false
<html lang="en" class="no-js"><head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>
        Login
    </title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
	<meta name="layout" content="main">
	
	<style type="text/css" media="screen">
 
	#login {
		margin: 15px 0px;
		padding: 0px;
		text-align: center;
		font-family: Arial, Helvetica, sans-serif;
	}
 
	#login .inner {
		width: 340px;
		padding-bottom: 6px;
		margin: 60px auto;
		text-align: left;
		border: 1px solid #aab;
		background-color: #f0f0fa;
		-moz-box-shadow: 2px 2px 2px #eee;
		-webkit-box-shadow: 2px 2px 2px #eee;
		-khtml-box-shadow: 2px 2px 2px #eee;
		box-shadow: 2px 2px 2px #eee;
	}
 
	#login .inner .fheader {
		padding: 18px 26px 14px 26px;
		background-color: #f7f7ff;
		margin: 0px 0 14px 0;
		color: #2e3741;
		font-size: 18px;
		font-weight: bold;
	}
 
	#login .inner .cssform p {
		clear: left;
		margin: 0;
		padding: 4px 0 3px 0;
		padding-left: 105px;
		margin-bottom: 20px;
		height: 1%;
	}
 
	#login .inner .cssform input[type="text"] {
		width: 75%;
	}
 
	#login .inner .cssform label {
		font-weight: bold;
		float: left;
		text-align: right;
		margin-left: -105px;
		width: 110px;
		padding-top: 3px;
		padding-right: 10px;
	}
 
	#remember_me_holder {
		padding-left: 120px;
	}
 
	#submit {
		margin-left: 15px;
	}
 
	#remember_me_holder label {
		float: none;
		margin-left: 0;
		text-align: left;
		width: 200px
	}
 
	#login .inner .login_message {
		padding: 6px 25px 20px 25px;
		color: #c33;
	}
 
	#login .inner .text_ {
		width: 75%;
	}
 
	#login .inner .chk {
		height: 12px;
	}
 
	#passwordToggler {
		padding: 3px 4px;
		cursor: pointer;
	}
	</style>
 
</head>
<body>
 
<div id="login">
	<div class="inner">
		<div class="fheader">Please Login</div>
 
		
 
		<form action="/login/authenticate" method="POST" id="loginForm" class="cssform" autocomplete="off">
			<p>
				<label for="username">Username:</label>
				<input type="text" class="text_" name="username" id="username" autocapitalize="none">
			</p>
 
			<p>
				<label for="password">Password:</label>
				<input type="password" class="text_" name="password" id="password">
				<i id="passwordToggler" title="toggle password display" onclick="passwordDisplayToggle()">👁</i>
			</p>
 
			<p id="remember_me_holder">
				<input type="checkbox" class="chk" name="remember-me" id="remember_me">
				<label for="remember_me">Remember me</label>
			</p>
 
			<p>
				<input type="submit" id="submit" value="Login">
			</p>
		</form>
	</div>
</div>
<script type="text/javascript">
	document.addEventListener("DOMContentLoaded", function(event) {
		document.forms['loginForm'].elements['username'].focus();
	});
 
	function passwordDisplayToggle() {
		var toggleEl = document.getElementById("passwordToggler");
		var eyeIcon = '\u{1F441}';
		var xIcon = '\u{2715}';
		var passEl = document.getElementById("password");
 
		if (passEl.type === "password") {
			toggleEl.innerHTML = xIcon;
			passEl.type = "text";
		} else {
			toggleEl.innerHTML = eyeIcon;
			passEl.type = "password";
		}
	}
</script>
 
 
 
</body></html>

	at demo.SecuredControllerSpec.$tt__$spock_feature_1_0(SecuredControllerSpec.groovy:52)
	at demo.SecuredControllerSpec.test login as sherlock, sherlock belongs to detective groups. All detectives have the role ADMIN_closure2(SecuredControllerSpec.groovy)
	at grails.gorm.transactions.GrailsTransactionTemplate$1.doInTransaction(GrailsTransactionTemplate.groovy:72)
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:137)
	at grails.gorm.transactions.GrailsTransactionTemplate.executeAndRollback(GrailsTransactionTemplate.groovy:69)
	at demo.SecuredControllerSpec.test login as sherlock, sherlock belongs to detective groups. All detectives have the role ADMIN(SecuredControllerSpec.groovy)

Muted Tests

Select tests to mute in this pull request:

  • SecuredControllerSpec > test login as sherlock, sherlock belongs to detective groups. All detectives have the role ADMIN

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants