Repository navigation
CAMEL-25900: camel-jbang - AI tools: lead to the jsonpath filter with resultType java.util.Map for the one element of a list - #28210
Conversation
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
|
🧪 CI tested the following changed modules:
🔁 4 tests passed only after a retry on JDK 21 (4 retried attempts) Recovered flaky tests on JDK 21 (4)
🔬 Scalpel shadow comparison — Scalpel: 490 of 707 tested, 26 compile-only — current: 492 all testedMaveniverse Scalpel detected 490 affected modules (current approach: 492). Skip-tests mode would test 490 modules (3 direct + 488 downstream), skip tests for 26 (generated code, meta-modules) Modules only in current approach (2)
Modules Scalpel would test (490)
Modules with tests skipped (26)
💡 Manual integration tests recommended:
Build reactor — dependencies compiled but only changed modules were tested (3 modules, 9.3s total)Total reactor time: 9.3s
Top 20 slowest modules:
|
df5effa to
6dc6f6e
Compare
… resultType java.util.Map for the one element of a list - The source check reports an index after a jsonpath filter (an empty list) and && or || outside a filter (no JsonPath), and names the recipe: a filter with resultType: java.util.Map gives the element, or null. - camel_eval_expression of a jsonpath filter notes that a filter gives a list also for one match, and that a route gets the one element with resultType: java.util.Map. - camel_catalog_doc jsonpath answers the recipe in usage; camel_catalog_find of CamelHttpStatusCode (and similar) names CamelHttpResponseCode. - camel-bean: the hint for an OGNL read on a List leads with the jsonpath recipe; [0] comes after it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
6dc6f6e to
cce53b7
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
LGTM. No issues found.
The hint reordering in BeanExpression.listHint() correctly prioritises the jsonpath-filter-with-resultType recipe over the [0] indexing shortcut, directly addressing the root cause (models took [0] from the hint and ended up with ${body[0].orderId} instead of the filter recipe).
JsonPathChecks.filterProblem() and outsideFilters() are correct:
- The
INDEX_AFTER_FILTERregex (\[\?\(.*\]\s*\[\s*-?\d+\s*]) fires correctly on$[?(@.orderId==...)][0]and does not false-positive on chained filters like$[?(@.a==1)][?(@.b==2)](no digit after the second bracket). outsideFilters()depth tracking is sound:jstarts at the(of[?(, depth increments on nested(, decrements on)until 0, andi = j+2correctly skips past)and].filterNote()inExpressionEvaluatorsafely handlesnullstatus ("ok".equals(null)isfalse).
Test coverage is solid: 3 new JsonPathChecksTest cases, 2 CatalogDocsTest, 1 ExpressionEvaluatorLanguageTest, plus the updated SimpleSyntaxHintsTest assertions all track the changed behaviour.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
oscerd
left a comment
There was a problem hiding this comment.
TL;DR
The recipe is right where it applies: a filter on a unique field that ends the path gives the element or null (checked against JsonPathExpression.evaluate on main and the jsonpath page). Two of the new messages also offer it where it does not hold, so they are worth tightening before merge. CI is green; the earlier red run came from main's JsonDataFormatReifierTest, which this head's base fixes.
Blockers
- None
Other findings: 5 (2 non-blocking, 3 suggestions)
- Non-blocking —
JsonPathChecks.java:112-114,ExpressionEvaluator.java:101-102:INDEX_AFTER_FILTER.find()also matches$[?(@.sku == 'X')][0].qty. If we follow the message there, we get$[?(...)].qtywithresultType: java.util.Map, which gives[42].JsonPathExpression.evaluate(main :182-190) then converts each item to Map. Integer→Map is a converter miss, so the item becomes null, andcol.stream().findFirst().get()throws an NPE, which surfaces as anExpressionEvaluationException. Could we give the Map advice only when the index ends the path (e.g.…\]\s*$), and for[0].fieldsay "leave the index out and set resultType to the field's type"? The eval note has the same gap, because it is added for any filter. The NPE itself has been there since CAMEL-20508, so that part is a follow-up Jira. - Non-blocking —
JsonPathChecks.java:118-119: the message says "to test whether a filter matched, set resultType: java.util.Map … check ${body} == null". In awhen/filter(whereandOutsideAFilterIsNoJsonPathputs it), the filter alone is already that test:ExpressionSupport.matches→ObjectHelper.evaluateValuePredicatereads an empty list as false, as the page'swhen().jsonpath("$.store.book[?(@.price < 10)]")shows. Map also gives null when several elements match: onlysize() == 1is unwrapped, asjsonpath-language.adoc:237-239notes. So for the message's own example,@.status == 'paid' && @.qty > 0, "matched" would read as no. Suggestion: whenSimpleChecks.isPredicate(...)(already used at :85) is true, say "drop the && part, the filter alone is the condition". And add "null also when several match" toONE_ELEMENT,JSONPATH_ONE_ELEMENTandfilterNote. - Suggestion —
BeanExpression.java:788-790:noKeysHintstill answers${body[orderId]}on a List with "pick an element first, such as [0][orderId]". That is the same pull toward the first element that this PR removes fromlistHint, andSimpleTest:549-557asserts it. - Suggestion — tests: the note's wiring in
evaluate()(ExpressionEvaluator.java:86-89) never runs in CI. The only jsonpathevaluatetest is gated bycamel.test.download, and CI shows ExpressionEvaluatorLanguageTest with "Tests run: 5, Skipped: 1". It would also help to have a[0].qtycase, an||case and a valid neighbour such as$.orders[?(@.id == 1)].items[0]to pin the regex. - Suggestion —
CatalogDocsTest.java:281: the new test uses FQCNs forCamelCatalog/DefaultCamelCatalog, although both are imported at :25-26.
Reviewed with Claude Code on behalf of oscerd. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.
|
Thanks @oscerd, the points hold. I merged it now so tonight's benchmark run has it. A follow-up PR will:
The NPE in Claude Code on behalf of davsclaus |
|
@oscerd the follow-up is #28253, and the NPE in Claude Code on behalf of davsclaus |
…path that goes on after the filter, and a when or filter (#28253) Follow-up of #28210, addressing its review. The resultType java.util.Map advice for a jsonpath filter is given only when an index ends the path; for a path that goes on after the index the answer is the path without the index and a resultType of the value's type. In a when or filter, an index or &&/|| outside the filter is answered with the filter alone as the condition. The no-keys hint for ${body[qty]} on a List leads to the jsonpath filter first, as the list hint does. BeanExpression unwraps a single match before the conversion. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
https://issues.apache.org/jira/browse/CAMEL-25900
The REST example of the benchmark (one order out of a JSON list by its id, 404 when unknown) failed 12 of 12 times on two nights. The model saw the earlier hints but wrote paths that give the wrong answer:
$[?(@.orderId == '${header.id}')][0]gives an empty list. Jayway applies an index after a filter to each element it matched.$[?(…)] && size() > 0is no JsonPath, and fails with 500.${body[0].orderId}takes the first element, because the List hint offered[0]first.The recipe is a filter with
resultType: java.util.Map: the element when one matches, null when none does. The tools now lead to it:JsonPathChecks): it reports an index after a filter, and&&or||outside a filter, and names the recipe.camel_eval_expression: for a jsonpath filter, the answer notes that a filter gives a list also for one match, and that a route gets the one element withresultType: java.util.Map.camel_catalog_doc jsonpath: it answers the recipe inusage.${body.orderId}on a List leads with the jsonpath recipe, and[0]comes after it.camel_catalog_find CamelHttpStatusCode(and similar names): it names CamelHttpResponseCode (Exchange.HTTP_RESPONSE_CODE).I did not add a
resultTypeparameter tocamel_eval_expression. With two new parameters, the prompt of the AI clients grew by about 60 tokens, past its budget with all tool groups loaded. The note in the answer teaches the same recipe at no prompt cost.Tests: new tests in
JsonPathChecksTest(3),CatalogDocsTest(2),ExpressionEvaluatorLanguageTest(1), and an updatedSimpleSyntaxHintsTest. camel-jbang-core 2406 + 237, camel-jbang-mcp 503 and the camel-core simple hint tests (33) pass, and the prompt budget is unchanged.Claude Code on behalf of davsclaus
🤖 Generated with Claude Code
https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m