fix: JSONB ASM direct-write skips level tracking causing false 'level too large' on flat lists, for issue #3616#7635
Open
daguimu wants to merge 1 commit into
Open
Conversation
…acking, for issue alibaba#3616 When a Bean's field writers are split across direct-write and non-direct-write groups (e.g. a String field plus a BigDecimal field), the non-direct path emits the leading BC_OBJECT via startObject() and the direct path emits the trailing BC_OBJECT_END as a raw byte. startObject() bumps level but the raw-byte end never decrements it, so each serialized bean leaks 1 level. After ~2049 beans the JSONWriter throws "level too large : 2049" even though the structure has no real nesting. Mirror the level tracking on the direct-write path: increment level after writing BC_OBJECT raw, decrement after writing BC_OBJECT_END raw. New incrementLevel / decrementLevel helpers on JSONWriter expose just the level bookkeeping (no byte output), so the direct path keeps its raw-byte performance.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
JSONB.toBytes(list)on a flat list of JavaBeans throwsJSONException: level too large : 2049after ~2049 elements, even though the structure has no actual nesting. Reported reproducer (issue #3616):TestObjecthas aStringfield and aBigDecimalfield — both standard, both unrelated to nesting. Stack trace:Root cause
ObjectWriterCreatorASM.genMethodWriteJSONBpartitions the bean's field writers into "direct" and "non-direct" groups. The two paths emit the BC_OBJECT / BC_OBJECT_END framing differently:group.start): callsJSONWriter.startObject()— writes BC_OBJECT and incrementslevel.group.end): callsJSONWriter.endObject()— writes BC_OBJECT_END and decrementslevel.group.start): writes BC_OBJECT raw to the byte buffer — does not touchlevel.group.end): writes BC_OBJECT_END raw to the byte buffer — does not touchlevel.For
TestObject, the field groups order out as[BigDecimal c (non-direct, start=true)] + [String d (direct, end=true)]. SostartObject()incrementslevelbut the matching end is the raw-byte direct path, which leaveslevelincremented. Each bean in the list leaks 1 level. At iteration 2049 we hitcontext.maxLevelandoverflowLevel()fires.The same imbalance occurs on any field-group split where start and end fall on different paths.
Fix
Mirror the level bookkeeping on the direct-write path so it stays consistent with
startObject()/endObject():JSONWriter.incrementLevel()— bumpsleveland runs the same overflow check asstartObject(). No byte output.JSONWriter.decrementLevel()— decrementslevel. No byte output.ObjectWriterCreatorASMcallsincrementLevel()after writing BC_OBJECT raw anddecrementLevel()after writing BC_OBJECT_END raw. The direct path keeps its raw-byte field-value writes, so the performance optimisation is preserved.Tests
core/src/test/java/com/alibaba/fastjson2/issues_3600/Issue3616.java:testFlatListBigDecimalAndString— 2100-element list of(String, BigDecimal)beans; serialise to JSONB and round-trip back, asserting size and field values. Reproduces the issue exactly and verifies it is gone.testFlatListWithDateObject— 2100-element list of(String, Date)beans, exercising another non-direct field type to guard against future regressions.Existing regression tests still pass:
Issue3657(intentionallevel too largefor genuinely deep nesting) — 6 testsIssue7616(related fix for direct-write byte-frame capacity) — 3 tests*Writer*test suite — 394 tests, all greenImpact
Surgical: 2 helper methods on
JSONWriter(no behaviour change for existing callers) plus 6 lines of bytecode emission in the JSONB ASM generator. No JSON-text serialiser changes, no API surface broken.Fixes #3616