This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 1239c76ac3c1 CAMEL-25746: camel-bean - the error for a key read from a 
stream, JSON text or a List says what to do (#28042)
1239c76ac3c1 is described below

commit 1239c76ac3c17dbc468e107918c2e4adba7f8509
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 18 20:53:53 2026 +0200

    CAMEL-25746: camel-bean - the error for a key read from a stream, JSON text 
or a List says what to do (#28042)
    
    "Key: x not found in bean", which Simple raises for ${body[x]} and 
${variable.v[x]}, named the problem but not the
    fix. It now adds a hint for a value that has no keys: the message body as a 
stream or bytes (unmarshal it first; a
    variable keeps the value it had when it was set), JSON text (unmarshal it 
first), and a List, an array or another
    Collection (pick an element first, such as [0][qty], or split it). Other 
text keeps the message as it was.
    
    The hint for a Map or List as the platform-http response body, the first 
part of this change, is covered on main by
    CAMEL-25751 and CAMEL-25749.
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
 .../apache/camel/language/bean/BeanExpression.java | 27 +++++++++++-
 .../apache/camel/language/simple/SimpleTest.java   | 48 ++++++++++++++++++++++
 2 files changed, 74 insertions(+), 1 deletion(-)

diff --git 
a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
 
b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
index bdc6d8a8757e..9158f62e694d 100644
--- 
a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
+++ 
b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.language.bean;
 
+import java.io.InputStream;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -29,6 +31,7 @@ import org.apache.camel.ExpressionIllegalSyntaxException;
 import org.apache.camel.NoSuchBeanException;
 import org.apache.camel.Predicate;
 import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.StreamCache;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.component.bean.BeanComponent;
 import org.apache.camel.component.bean.BeanConstants;
@@ -687,11 +690,33 @@ public class BeanExpression implements Expression, 
Predicate {
             throw new IndexOutOfBoundsException(
                     "Key: " + key + " not found in bean: " + bean + " of type: 
"
                                                 + 
ObjectHelper.classCanonicalName(bean) + " using OGNL path [" + ognlPath
-                                                + "]");
+                                                + "]" + noKeysHint(result != 
null ? result : bean, key));
         } else {
             // null safe so we can return null
             return null;
         }
     }
 
+    /**
+     * What to do when a key is read from a value that has no keys 
(CAMEL-25746): the message body as a stream or bytes
+     * (a request body before it is unmarshalled, or a variable set from it), 
JSON text, or a List, array or other
+     * Collection, where [0] picks an element.
+     */
+    static String noKeysHint(Object value, String key) {
+        if (value instanceof StreamCache || value instanceof InputStream || 
value instanceof byte[]) {
+            return " (the value is the message body as a stream, not a Map: 
unmarshal it first, for example with json,"
+                   + " then read the key; a variable keeps the value it had 
when it was set)";
+        }
+        if (value instanceof CharSequence text) {
+            String trimmed = text.toString().strip();
+            if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
+                return " (the value is JSON text, not a Map: unmarshal it 
first, for example with json, then read the key)";
+            }
+        }
+        if (value instanceof Collection || value instanceof Object[]) {
+            String type = value instanceof List ? "a List" : value instanceof 
Object[] ? "an array" : "a Collection";
+            return " (" + type + " has no keys: pick an element first, such as 
[0][" + key + "], or split it)";
+        }
+        return "";
+    }
 }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index 3e29ee9a5e56..9ebb4de8394c 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.language.simple;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -521,6 +522,53 @@ public class SimpleTest extends LanguageTestSupport {
                 cause.getMessage());
     }
 
+    @Test
+    public void testOGNLKeyOnAStreamSaysToUnmarshal() {
+        // CAMEL-25746: the request body as a stream, before it is unmarshalled
+        exchange.getIn().setBody(new ByteArrayInputStream("{\"qty\": 
2}".getBytes(StandardCharsets.UTF_8)));
+        RuntimeBeanExpressionException e = 
assertThrows(RuntimeBeanExpressionException.class,
+                () -> assertExpression("${body[qty]}", null));
+
+        IndexOutOfBoundsException cause = 
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+        assertTrue(cause.getMessage().startsWith("Key: qty not found in bean: 
"), cause.getMessage());
+        assertTrue(cause.getMessage().contains("the value is the message body 
as a stream, not a Map: unmarshal it first"),
+                cause.getMessage());
+    }
+
+    @Test
+    public void testOGNLKeyOnJsonTextSaysToUnmarshal() {
+        exchange.getIn().setHeader("order", "{\"qty\": 2}");
+        RuntimeBeanExpressionException e = 
assertThrows(RuntimeBeanExpressionException.class,
+                () -> assertExpression("${header.order[qty]}", null));
+
+        IndexOutOfBoundsException cause = 
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+        assertTrue(cause.getMessage().contains("the value is JSON text, not a 
Map: unmarshal it first"), cause.getMessage());
+    }
+
+    @Test
+    public void testOGNLKeyOnAListSaysToPickAnElement() {
+        exchange.getIn().setBody(List.of(Map.of("sku", "CAMEL-MUG", "qty", 
42)));
+        RuntimeBeanExpressionException e = 
assertThrows(RuntimeBeanExpressionException.class,
+                () -> assertExpression("${body[qty]}", null));
+
+        IndexOutOfBoundsException cause = 
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+        assertTrue(cause.getMessage().contains("a List has no keys: pick an 
element first, such as [0][qty]"),
+                cause.getMessage());
+    }
+
+    @Test
+    public void testOGNLKeyOnAnArraySaysToPickAnElement() {
+        exchange.getIn().setBody(new Object[] { Map.of("sku", "CAMEL-MUG", 
"qty", 42) });
+        RuntimeBeanExpressionException e = 
assertThrows(RuntimeBeanExpressionException.class,
+                () -> assertExpression("${body[qty]}", null));
+
+        IndexOutOfBoundsException cause = 
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+        assertTrue(cause.getMessage().contains("an array has no keys: pick an 
element first, such as [0][qty]"),
+                cause.getMessage());
+        // [0] picks the element the hint points to
+        assertExpression("${body[0][qty]}", 42);
+    }
+
     @Test
     public void testOGNLPropertyMapIllegalSyntax() {
         ExpressionIllegalSyntaxException e = 
assertThrows(ExpressionIllegalSyntaxException.class,

Reply via email to