fix(block-editor): guard StoryBlock JSON parsing against non-object scalars#35469
fix(block-editor): guard StoryBlock JSON parsing against non-object scalars#35469
Conversation
…calars JsonUtil.isValidJSON() returns true for any valid JSON token, including bare scalars like 42, "foo", or [1,2,3]. After #35412 added recursive hydration of nested contentlet field values via refreshNestedStoryBlockValues, scalar field values from related contentlets started reaching toMap() and failing with "Cannot deserialize value of type LinkedHashMap from Integer", breaking /api/content/_search whenever a Story Block field referenced a contentlet with numeric/boolean fields. Add an isJsonObject() guard that requires the value to be a JSON object (starts with '{'), and apply it at the four entry points that feed toMap. HTML body content is correctly skipped (fails the '{' check). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Claude finished @fmontes's task in 2m 36s —— View job Review
Issues1.
} catch (final Exception e) {
...
throw new DotRuntimeException(errorMsg, e); // ← still throws
}
final StoryBlockReferenceResult storyBlockRefreshedResult =
APILocator.getStoryBlockAPI().refreshReferences(cachedContentlet); // no try-catchThe 2. Double Javadoc block in There are two consecutive 3. Missing (Copilot also flagged this.) 4. The only assertion is Minor
Item 1 is the real risk; 2–4 are test quality issues. |
There was a problem hiding this comment.
Pull request overview
Guards Story Block JSON parsing in StoryBlockAPIImpl so the transformer pipeline won’t attempt to deserialize non-object JSON tokens (scalars/arrays) into a LinkedHashMap, preventing /api/content/_search failures when related contentlets contain scalar defaults that look like valid JSON.
Changes:
- Introduced
isJsonObject(String)and applied it to all call sites that would otherwise feed non-object JSON intotoMap(). - Updated Story Block refresh/dependency extraction paths to skip parsing when the root JSON token isn’t an object.
- Added an integration test covering scalar/array/HTML inputs to ensure no exception is thrown.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| dotCMS/src/main/java/com/dotcms/contenttype/business/StoryBlockAPIImpl.java | Adds isJsonObject() and uses it to guard toMap() entry points against non-object JSON values. |
| dotcms-integration/src/test/java/com/dotcms/contenttype/business/StoryBlockAPITest.java | Adds regression test exercising numeric/string/boolean/array/HTML inputs for refreshStoryBlockValueReferences. |
…ntentlet and per field A single bad contentlet was able to abort the entire ContentletTransformer pipeline, taking down /api/content/_search and any other code path that materializes contentlets in bulk. Add two layers of failure isolation: 1. Outer (ContentletTransformer.refreshStoryBlockReferences): if the StoryBlock API throws for one contentlet, log and return that contentlet with un-refreshed (but valid) Story Block data instead of aborting the whole transform. 2. Inner (StoryBlockAPIImpl): wrap each child block in isRefreshed() and each field in refreshContentlet() so a single malformed nested reference or a single broken field on a related contentlet does not prevent the rest of the parent's hydration. Bad fields fall back to their raw value. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Inner: refreshStoryBlockValueReferences must not throw when one nested child block is malformed (missing 'type' key). Asserts the per-child isolation in StoryBlockAPIImpl.isRefreshed. - Outer: refreshReferences(Contentlet) must not propagate exceptions when the stored Story Block JSON contains a malformed nested child. This is the boundary ContentletTransformer relies on to keep a single bad contentlet from aborting an entire /api/content/_search response. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fixes regression from #35412:
/api/content/_searchthrowsCannot deserialize value of type LinkedHashMap from Integerwhen a Story Block references a contentlet with scalar field values.Fix
1. JSON guard.
JsonUtil.isValidJSON()returns true for scalars (42,"foo",[1,2]). Replaced withisJsonObject()(valid JSON + starts with{) at the 4 call sites feedingtoMap(). HTML strings correctly skip refresh (no{).2. Failure isolation. One bad contentlet was killing the whole search response. Now:
ContentletTransformer.refreshStoryBlockReferencescatches/logs — bad contentlet returns un-refreshed.StoryBlockAPIImpl.isRefreshedisolates each child block.StoryBlockAPIImpl.refreshContentletisolates each field (falls back to raw value).QA
Re-run QA from #35408 (PR #35412) and #35403 (PR #35413).
Plus: via Content REST API, create a contentlet with raw HTML as the Block Editor field value (e.g.
<p>hello</p>). Confirm create + update +_search+ edit-UI all work withoutMismatchedInputExceptionin logs.Tests
test_refreshStoryBlockValueReferences_with_non_object_json_scalars— numeric/string/boolean/array/HTML inputstest_refreshStoryBlockValueReferences_isolates_bad_child_block— inner isolationtest_refreshReferences_does_not_throw_on_malformed_nested_block— API-level resilience