Skip to content

fix(deps): bump: bump com.cedarsoftware:json-io from 4.111.0 to 4.112.0 - #479

Merged
kvmw merged 1 commit into
3.xfrom
dependabot/gradle/3.x/com.cedarsoftware-json-io-4.112.0
Sep 30, 2026
Merged

kvmw merged 1 commit into
3.xfrom
dependabot/gradle/3.x/com.cedarsoftware-json-io-4.112.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 28, 2026

Copy link
Copy Markdown
Contributor

Bumps com.cedarsoftware:json-io from 4.111.0 to 4.112.0.

Release notes

Sourced from com.cedarsoftware:json-io's releases.

4.112.0

json-io 4.112.0

Maven Central:

  • com.cedarsoftware:json-io:4.112.0
  • com.cedarsoftware:json-io-spring-boot-starter:4.112.0
  • com.cedarsoftware:json-io-spring-ai-toon:4.112.0

Cyclic graphs no longer overflow the stack in the objects json-io hands back.

A cyclic graph read in Maps mode could not be hashed, compared, or put in a HashSet

In Maps mode (JsonIo.toMaps, returnAsJsonObjects(), returnAsNativeJsonObjects()), every @ref that points back up the document becomes a real reference to the ancestor. That includes a child's parent and the last next of a ring. JsonObject.hashCode() and equals() then followed those references forever, and died with StackOverflowError. The failure hit HashSet.add, toMaps(json).asClass(Map.class), and any comparison of two such reads. A cyclic complex key (@keys) failed the read itself.

  • hashCode() is now shallow. It covers an object's own keys and leaf values, and an array object's own items. For a nested map, collection, array or JsonObject, it folds in only the kind of value, never its contents. It never descends, so it cannot loop and has no depth limit. Before, acyclic nesting overflowed it at about 6,150 levels on a 1 MB stack. A cached parent hash also can no longer go stale when a nested value changes.
  • equals() is still deep. It compares every nested value, and which acyclic objects are equal does not change. A pair already taken as equal is taken as equal again, which is how a loop closes. So two reads of the same cyclic document are equal, while a difference anywhere on the loop still makes them unequal. If a comparison turns out false, everything it assumed after that pair is rolled back. That keeps the result correct even when a HashSet lookup discards a failed candidate. It also stays linear on densely cross-linked graphs, where a path-only guard is exponential.

toString() on json-io's own containers no longer overflows on a cyclic graph

These containers are what json-io returns when the read target is an unmodifiable or singleton JDK type, such as Collections.unmodifiableMap, List.of or Collections.singletonList. All eight overflowed on at least one cycle shape.

  • The five Sealable* wrappers delegated toString() to the container they wrap. That container's self-guard compares against itself, never the wrapper.
  • SingletonMap and SingletonList had no toString() at all. The default Object.toString() calls hashCode(), which hashes the element: in a cycle, the singleton again.
  • The singletons, and SingletonSet, now print the way Collections.singletonMap / singletonList / singleton do: {k=v} and [x]. Before, they printed SingletonList@1b6d3586.
  • The entries the maps hand out, SealableSet.SealAwareEntry and SingletonMap's entry, now print k=v.

The fix is java-util 4.112.0's cycle-safe walk, carried here as a package-private copy. A loop renders as (cycle) where it closes, and acyclic output is unchanged. The walk is bounded on densely cross-linked graphs: a clique of containers that each hold all the others renders in under 1 MB, instead of exhausting the heap.

Build

Runtime dependency java-util 4.111.0 → 4.112.0. Its own containers become cycle-safe in toString() too. It also fixes SafeSimpleDateFormat failing to parse patterns like yyyy-MM-dd HH:mm:ss.SSS and dd.MM.yyyy, which had been broken since java-util 4.1.0. Test scope: jackson-databind / jackson-datatype-jsr310 2.22.2 → 2.22.3. Held, with the reason in the poms: mockito 4.11.0, assertj-core 3.27.7 and junit-jupiter 5.14.4. The Spring modules follow the spring-boot-dependencies BOM pinned at the floor.

Changelog

Sourced from com.cedarsoftware:json-io's changelog.

4.112.0 - 2026-09-23

  • BUG FIX (robustness): A cyclic graph read in Maps mode could not be hashed, compared, or put in a HashSet -- and a cyclic complex key failed the read itself. In Maps mode (JsonIo.toMaps, returnAsJsonObjects(), returnAsNativeJsonObjects()) every @ref that points back up the document -- a child's parent, the last next of a ring -- becomes a real reference to the ancestor, so json-io builds cyclic JsonObject graphs as a matter of course. JsonObject.hashCode() and equals() (and the JsonObjectArray / JsonObjectMap overrides) recursed that graph with no guard, so hashCode(), equals() between two reads of one document, and HashSet.add() all died with StackOverflowError. The documented toMaps(json).asClass(Map.class) was no escape: the JDK map at the root hashes and compares the JsonObjects below it. And a document whose @keys held a cyclic object failed during the READ, because the reader hashes each key as it builds the map.

    • hashCode() is now shallow: an object's own keys and leaf values (and an array object's own items), with only the KIND of a nested map, collection, array or JsonObject folded in. It never descends, so it cannot loop and has no depth limit (it overflowed at about 6,150 levels of acyclic nesting on a 1 MB stack). It also closes a quieter defect: the hash is cached, and it used to fold in nested objects, so a parent hashed before a nested object changed disagreed with an equal parent hashed after -- equal objects with different hashes, which a HashSet then cannot find. Hash VALUES change; they were never specified.
    • equals() is still deep: every nested value is compared, and which acyclic objects are equal does not change. A pair the comparison has already taken as equal -- still being compared further up, or already found equal -- is taken as equal again. That is how a loop closes, so two reads of the same cyclic document are equal while a difference anywhere on the loop still makes them unequal. It also means each pair of objects is compared once: a densely cross-linked graph (a clique of 24 nodes in the tests) compares in linear time, where guarding only the current path would have visited every path through it and never finished. A pair found unequal is forgotten along with everything decided after it, so a HashSet lookup that tries a candidate, fails, and moves on cannot leave a false "equal" behind for a later comparison. The deepest acyclic chain it can compare is a little shallower than before -- about 5,800 levels on a 1 MB stack, against 6,650 -- still far beyond the reader's default maxDepth of 1,000.
  • BUG FIX (robustness): toString() on json-io's own containers no longer dies with StackOverflowError on a cyclic graph. These are what json-io hands back when a read target is an unmodifiable or singleton JDK type (Collections.unmodifiableMap, List.of, Collections.singletonList ...), so a cyclic document read into one puts them straight into user code. All eight overflowed on at least one of four shapes (holding itself; a loop through a plain JDK map; through a plain JDK list; through a second instance of the same class):

    • SealableMap, SealableNavigableMap, SealableList, SealableSet and SealableNavigableSet delegated toString() to the container they wrap, whose self-guard compares against itself, never the wrapper -- so even a wrapper holding ITSELF overflowed.
    • SingletonMap and SingletonList had no toString() at all: Object.toString() prints the hash code, and their hashCode() hashes the element -- in a cycle, the singleton again. They, and SingletonSet, now print as Collections.singletonMap / singletonList / singleton do ({k=v}, [x]), where before they printed SingletonList@1b6d3586 -- so a singleton now prints the same after a round trip as it did before.
    • The entries the maps hand out had the same gap. SealableSet.SealAwareEntry -- what SealableMap.entrySet() iterates and SealableNavigableMap.firstEntry() returns -- had no toString(): it printed SealableSet$SealAwareEntry@1d, and overflowed when its value led back into the map. SingletonMap's entry printed SingletonMap$1@2810f49c. Both now print k=v, on the same walk.
    • The fix is java-util 4.112.0's cycle-safe walk, carried as a package-private copy (util.CycleSafeToString) so json-io does not depend on that release. The containers being rendered sit on a per-thread identity path; a loop renders (cycle) where it closes, direct self-containment keeps (this Map) / (this Collection), and anything acyclic renders exactly as before -- a wrapper renders as the container it wraps.
    • Bounded on a densely cross-linked graph. A path prints every route through a graph that does not repeat a container, and in a clique -- containers that each hold all the others -- the number of routes grows factorially, enough to exhaust the heap at a dozen. So once a render has printed (cycle), which the JDK never prints, and has rendered 100,000 values, every container it has not yet expanded prints as .... Anything the JDK can print never meets the budget, however large, and a loop that is not dense, such as a tree whose nodes point back to their parent, renders in full up to that size.
  • Checked and already correct: the writers (cycleSupport(true) writes @id/@ref; cycleSupport(false) reports the cycle with a JsonIoException rather than recursing, for JSON and TOON alike), formatJson() on cyclic text, a cyclic POJO round trip, and JsonObject.toString(), which prints a one-line summary and never walks its contents.

  • TESTING: JsonObjectCycleTest (16) -- cyclic reads through toMaps(), a cyclic complex key, every hand-built cycle shape, array and complex-key objects, the clique, the set-lookup case, hash/equals consistency after a nested change -- and ContainerToStringCycleTest (73) -- every container against all four shapes plus a loop through a java-util map, the entries the maps hand out, the render budget, and acyclic output. Against the previous code 12 of 16 and 68 of 72 fail, every one a cycle case, and the ones that pass there are the preservation checks; the 73rd, a 150,000-entry wrapper holding itself, exhausts the heap there and takes the test JVM with it. With the render budget switched off, the clique case exhausts the heap too.

  • BUILD: Runtime dependency java-util 4.111.0 → 4.112.0, which makes java-util's own containers cycle-safe in toString() (15 of its 24 Map/Set/List implementations overflowed on a cyclic graph) -- the same family as this release's own fixes -- and fixes SafeSimpleDateFormat failing to parse a numeric field followed by the locale's decimal separator (yyyy-MM-dd HH:mm:ss.SSS, dd.MM.yyyy), broken since java-util 4.1.0. Test-scope jackson-databind / jackson-datatype-jsr310 2.22.2 → 2.22.3. Held deliberately, as in 4.111.0: mockito 4.11.0 (JDK support), assertj-core 3.27.7 (4.0.0-M1 is a milestone), junit-jupiter 5.14.4 (JUnit 6 requires Java 17; this project compiles release 8); the Spring modules' dependencies follow the spring-boot-dependencies BOM pinned at the floor. Every plugin is already at its latest version.

Commits
  • f0ef3a0 Release: json-io 4.112.0
  • 2bf3172 Fix: cyclic graphs overflowed JsonObject hashCode()/equals() and json-io cont...
  • 10b92ee Update version to 4.112.0 for next development cycle
  • See full diff in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps [com.cedarsoftware:json-io](https://github.com/jdereg/json-io) from 4.111.0 to 4.112.0.
- [Release notes](https://github.com/jdereg/json-io/releases)
- [Changelog](https://github.com/jdereg/json-io/blob/master/changelog.md)
- [Commits](jdereg/json-io@4.111.0...4.112.0)

---
updated-dependencies:
- dependency-name: com.cedarsoftware:json-io
  dependency-version: 4.112.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file java Pull requests that update java code labels Sep 28, 2026
@kvmw
kvmw merged commit cc806e0 into 3.x Sep 30, 2026
2 checks passed
@kvmw
kvmw deleted the dependabot/gradle/3.x/com.cedarsoftware-json-io-4.112.0 branch September 30, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file java Pull requests that update java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant