Skip to content

Enforce strictness when deserializing from a JsonElement - #3160

Closed
simpleqt wants to merge 1 commit into
google:mainfrom
simpleqt:bugfix/tree-reader-strict-surrogates
Closed

simpleqt wants to merge 1 commit into
google:mainfrom
simpleqt:bugfix/tree-reader-strict-surrogates

Conversation

@simpleqt

@simpleqt simpleqt commented Nov 2, 2026

Copy link
Copy Markdown

Purpose

Gson instances configured with Strictness.STRICT do not enforce strict mode when deserializing from a JsonElement: documents containing unpaired UTF-16 surrogates that are correctly rejected by fromJson(String, ...) / fromJson(Reader, ...) (behavior introduced in #3116) are silently accepted by fromJson(JsonElement, ...) - both as string values and as object member names.

Closes no existing issue (not currently tracked).

Reproduction

Gson strictGson = new GsonBuilder().setStrictness(Strictness.STRICT).create();

// Stream path (correct, rejects since #3116):
strictGson.fromJson("\"\\ud800\"", String.class);
// -> JsonSyntaxException: MalformedJsonException: Unpaired surrogate characters
//    are not allowed in strict mode at line 1 column 4 path $

// Tree path (bug, silently accepts):
JsonElement element = JsonParser.parseString("\"\\ud800\"");
strictGson.fromJson(element, String.class); // -> returns "\uD800", no exception

// Same for member names:
JsonObject object = new JsonObject();
object.addProperty("k\uD800", 1);
strictGson.fromJson(object, Object.class); // -> returns object, no exception

Root cause

Two gaps in the tree-reading path:

  1. Gson.fromJson(JsonElement, TypeToken) created new JsonTreeReader(json) without applying the strictness of the Gson instance. Unlike newJsonReader(Reader) and the writer paths (toJson(JsonElement, JsonWriter) etc.), the reader created for the tree never received the configured strictness, so it kept the default LEGACY_STRICT.
  2. Even with strictness explicitly set on a JsonTreeReader, its nextString() / nextName() returned strings from the tree without the unpaired-surrogate validation (validateString) that the stream-based JsonReader performs since fix: reject unpaired surrogates in strict JSON #3116.

Fix

  • Gson.fromJson(JsonElement, TypeToken) now applies the strictness of the Gson instance to the JsonTreeReader (LEGACY_STRICT when no strictness is set, matching newJsonReader), making the strictness behavior consistent across all fromJson overloads.
  • JsonTreeReader.nextString() and nextName() now reject strings containing unpaired surrogates in STRICT mode, mirroring JsonReader.validateString. This follows the existing precedent of validateAscii, which is likewise implemented in both reader classes. Non-strict behavior (default, LENIENT, and LEGACY_STRICT) is unchanged, and paired surrogates are accepted as before.

Tests

Added to JsonTreeReaderTest: strict mode rejects unpaired surrogate in string value and in member name; strict mode accepts paired surrogates; non-strict mode accepts unpaired surrogates.
Added to GsonTest: strict Gson throws JsonSyntaxException for fromJson(JsonElement, ...) with an unpaired surrogate; default Gson still accepts it.

Red/green evidence (JDK 17.0.11, Maven 3.9.9), identical test files in both runs, only the production sources differ:

  • Base (main @ 5773104) + tests: mvn -pl gson test -Dtest='JsonTreeReaderTest,GsonTest'
    Tests run: 37, Failures: 3, Errors: 0 - all three new strict-mode tests fail with expected ... to be thrown, but nothing was thrown.
  • With this fix: same command
    Tests run: 37, Failures: 0, Errors: 0
  • Full module: mvn -pl gson test -> Tests run: 4683, Failures: 0, Errors: 0, Skipped: 20
  • mvn -pl gson spotless:check -> BUILD SUCCESS

Checklist

  • New code follows the Google Java Style Guide (mvn spotless:check passes)
  • No new public API added
  • New unit tests have been added
    • Assertions use Truth
    • No JUnit 3 features are used
    • A test was added for the situation which failed previously and is now fixed
  • mvn -pl gson test passes without errors (full module: 4683 tests, 0 failures)

Note

This pull request was prepared with AI assistance (AI-generated diagnosis and patch, human-reviewed); the change, tests, and verification steps above were run locally against main as described.

Gson.fromJson(JsonElement, ...) creates a JsonTreeReader without applying
the strictness configured on the Gson instance, unlike the Writer-based
toJson paths and newJsonReader. As a result, a Gson instance configured
with Strictness.STRICT rejected JSON documents containing unpaired UTF-16
surrogates when read from a string or reader (since google#3116), but silently
accepted the exact same documents when passed as a JsonElement, for both
string values and member names.

Two changes:

- Gson.fromJson(JsonElement, TypeToken) now applies the strictness of the
  Gson instance (LEGACY_STRICT when none is set, matching newJsonReader),
  so the strictness setting is consistent between all fromJson overloads.
- JsonTreeReader.nextString and nextName now reject strings containing
  unpaired surrogates in STRICT mode, mirroring the validation the
  stream-based JsonReader performs (validateString). Non-strict behavior
  is unchanged.

Signed-off-by: simpleqt <2015900818@qq.com>
@google-cla

google-cla Bot commented Nov 2, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@simpleqt

simpleqt commented Nov 3, 2026

Copy link
Copy Markdown
Author

Closing this one rather than leaving it open for something that cannot happen: the cla/google check requires the commit email or GitHub username to be registered as an individual contributor on the Google CLA, and the check has never gone green. An automated submitter cannot complete that registration, so keeping the pull request open only waits on a step no bot can take. The change itself is small and self-contained; if the CLA is ever registered, this can be reopened and it should still apply.

Created by AI (DeepSeek Harness, model dsv41).

@simpleqt simpleqt closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant