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

davsclaus pushed a commit to branch CAMEL-26324-json-object-to-text
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 32d26d24009c1f2637bf53c507e0d45f607f6f6c
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 26 12:40:15 2026 +0100

    CAMEL-26324: camel-core - type converter from JsonObject and JsonArray to 
their JSON text
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/converter/json/JsonConverterTest.java    | 106 +++++++++++++++++++++
 .../stream/CamelSupportBulkConverterLoader.java    |  48 +++++++++-
 .../apache/camel/converter/json/JsonConverter.java |  53 +++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   9 ++
 4 files changed, 215 insertions(+), 1 deletion(-)

diff --git 
a/core/camel-core/src/test/java/org/apache/camel/converter/json/JsonConverterTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/converter/json/JsonConverterTest.java
index 9ebc749e7522..65da353fe19a 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/converter/json/JsonConverterTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/converter/json/JsonConverterTest.java
@@ -17,12 +17,17 @@
 package org.apache.camel.converter.json;
 
 import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
 import org.apache.camel.converter.stream.ByteArrayInputStreamCache;
 import org.apache.camel.model.ConvertBodyDefinition;
 import org.apache.camel.model.FromDefinition;
 import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.support.DefaultExchange;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.junit.jupiter.api.Assertions;
@@ -200,4 +205,105 @@ public class JsonConverterTest extends ContextTestSupport 
{
 
     }
 
+    @Test
+    public void testConvertObjectToString() throws Exception {
+        JsonObject jo = new JsonObject();
+        jo.put("sku", "CAMEL-MUG");
+        jo.put("qty", 3);
+        jo.put("tags", new JsonArray(List.of("a", "b")));
+
+        String json = context.getTypeConverter().convertTo(String.class, jo);
+        
Assertions.assertEquals("{\"sku\":\"CAMEL-MUG\",\"qty\":3,\"tags\":[\"a\",\"b\"]}",
 json);
+
+        // and back again
+        JsonObject jo2 = 
context.getTypeConverter().convertTo(JsonObject.class, json);
+        Assertions.assertEquals("CAMEL-MUG", jo2.getString("sku"));
+        Assertions.assertEquals(3, jo2.getInteger("qty"));
+        Assertions.assertEquals(2, jo2.getCollection("tags").size());
+    }
+
+    @Test
+    public void testConvertArrayToString() throws Exception {
+        JsonArray arr = context.getTypeConverter().convertTo(JsonArray.class, 
BOOKS_ARR);
+
+        String json = context.getTypeConverter().convertTo(String.class, arr);
+        Assertions.assertTrue(json.startsWith("[{\"title\":\"No Title\""), 
json);
+
+        JsonArray arr2 = context.getTypeConverter().convertTo(JsonArray.class, 
json);
+        Assertions.assertEquals(arr, arr2);
+    }
+
+    @Test
+    public void testConvertObjectToByteArray() throws Exception {
+        JsonObject jo = new JsonObject();
+        jo.put("city", "Århus");
+
+        Exchange exchange = new DefaultExchange(context);
+        exchange.setProperty(Exchange.CHARSET_NAME, "ISO-8859-1");
+        byte[] data = context.getTypeConverter().convertTo(byte[].class, 
exchange, jo);
+        
Assertions.assertArrayEquals("{\"city\":\"Århus\"}".getBytes(StandardCharsets.ISO_8859_1),
 data);
+
+        exchange.setProperty(Exchange.CHARSET_NAME, "UTF-8");
+        data = context.getTypeConverter().convertTo(byte[].class, exchange, 
jo);
+        
Assertions.assertArrayEquals("{\"city\":\"Århus\"}".getBytes(StandardCharsets.UTF_8),
 data);
+    }
+
+    @Test
+    public void testConvertArrayToByteArray() throws Exception {
+        JsonArray arr = new JsonArray(List.of(1, 2, 3));
+
+        byte[] data = context.getTypeConverter().convertTo(byte[].class, arr);
+        Assertions.assertEquals("[1,2,3]", new String(data));
+    }
+
+    @Test
+    public void testConvertObjectToInputStream() throws Exception {
+        JsonObject jo = new JsonObject();
+        jo.put("sku", "CAMEL-MUG");
+
+        InputStream is = 
context.getTypeConverter().convertTo(InputStream.class, jo);
+        Assertions.assertEquals("{\"sku\":\"CAMEL-MUG\"}", 
context.getTypeConverter().convertTo(String.class, is));
+    }
+
+    @Test
+    public void testConvertArrayToInputStream() throws Exception {
+        JsonArray arr = new JsonArray(List.of("a", "b"));
+
+        InputStream is = 
context.getTypeConverter().convertTo(InputStream.class, arr);
+        Assertions.assertEquals("[\"a\",\"b\"]", 
context.getTypeConverter().convertTo(String.class, is));
+    }
+
+    @Test
+    public void testConvertObjectWithPojoValue() throws Exception {
+        Object pojo = new Object() {
+            @Override
+            public String toString() {
+                return "MyPojo";
+            }
+        };
+        JsonObject jo = new JsonObject();
+        jo.put("item", pojo);
+        JsonArray arr = new JsonArray(List.of(pojo));
+
+        // not JSON so the toString as before
+        Assertions.assertEquals("{item=MyPojo}", 
context.getTypeConverter().convertTo(String.class, jo));
+        Assertions.assertEquals("[MyPojo]", 
context.getTypeConverter().convertTo(String.class, arr));
+
+        // and no conversion to bytes or a stream as before
+        
Assertions.assertNull(context.getTypeConverter().tryConvertTo(byte[].class, 
jo));
+        
Assertions.assertNull(context.getTypeConverter().tryConvertTo(InputStream.class,
 arr));
+    }
+
+    @Test
+    public void testConvertBodyToStringInRoute() throws Exception {
+        RouteDefinition rd = new RouteDefinition();
+        rd.setInput(new FromDefinition("direct:start"));
+        rd.addOutput(new ConvertBodyDefinition("JsonObject"));
+        rd.addOutput(new ConvertBodyDefinition("String"));
+        context.addRouteDefinition(rd);
+
+        String out = template.requestBody("direct:start", "{ \"sku\" : 
\"CAMEL-MUG\", \"qty\" : 3 }", String.class);
+        Assertions.assertEquals("{\"sku\":\"CAMEL-MUG\",\"qty\":3}", out);
+    }
+
 }
diff --git 
a/core/camel-support/src/generated/java/org/apache/camel/converter/stream/CamelSupportBulkConverterLoader.java
 
b/core/camel-support/src/generated/java/org/apache/camel/converter/stream/CamelSupportBulkConverterLoader.java
index b30536432bb4..665783a9dd77 100644
--- 
a/core/camel-support/src/generated/java/org/apache/camel/converter/stream/CamelSupportBulkConverterLoader.java
+++ 
b/core/camel-support/src/generated/java/org/apache/camel/converter/stream/CamelSupportBulkConverterLoader.java
@@ -41,7 +41,7 @@ public final class CamelSupportBulkConverterLoader implements 
TypeConverterLoade
 
     @Override
     public int size() {
-        return 21;
+        return 27;
     }
 
     @Override
@@ -67,6 +67,26 @@ public final class CamelSupportBulkConverterLoader 
implements TypeConverterLoade
             if (value instanceof org.apache.camel.StreamCache) {
                 return 
org.apache.camel.converter.stream.StreamCacheConverter.convertToByteArray((org.apache.camel.StreamCache)
 value, exchange);
             }
+            if (value instanceof org.apache.camel.util.json.JsonObject) {
+                return 
org.apache.camel.converter.json.JsonConverter.convertToByteArray((org.apache.camel.util.json.JsonObject)
 value, exchange);
+            }
+            if (value instanceof org.apache.camel.util.json.JsonArray) {
+                return 
org.apache.camel.converter.json.JsonConverter.convertToByteArray((org.apache.camel.util.json.JsonArray)
 value, exchange);
+            }
+        } else if (to == java.io.InputStream.class) {
+            if (value instanceof org.apache.camel.util.json.JsonObject) {
+                return 
org.apache.camel.converter.json.JsonConverter.convertToInputStream((org.apache.camel.util.json.JsonObject)
 value, exchange);
+            }
+            if (value instanceof org.apache.camel.util.json.JsonArray) {
+                return 
org.apache.camel.converter.json.JsonConverter.convertToInputStream((org.apache.camel.util.json.JsonArray)
 value, exchange);
+            }
+        } else if (to == java.lang.String.class) {
+            if (value instanceof org.apache.camel.util.json.JsonObject) {
+                return 
org.apache.camel.converter.json.JsonConverter.convertToString((org.apache.camel.util.json.JsonObject)
 value, exchange);
+            }
+            if (value instanceof org.apache.camel.util.json.JsonArray) {
+                return 
org.apache.camel.converter.json.JsonConverter.convertToString((org.apache.camel.util.json.JsonArray)
 value, exchange);
+            }
         } else if (to == java.nio.ByteBuffer.class) {
             if (value instanceof org.apache.camel.StreamCache) {
                 return 
org.apache.camel.converter.stream.StreamCacheConverter.convertToByteBuffer((org.apache.camel.StreamCache)
 value, exchange);
@@ -138,6 +158,12 @@ public final class CamelSupportBulkConverterLoader 
implements TypeConverterLoade
 
     private void doRegistration(TypeConverterRegistry registry) {
         registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.util.json.JsonObject.class, byte[].class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.util.json.JsonArray.class, byte[].class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.util.json.JsonObject.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.util.json.JsonArray.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.util.json.JsonObject.class, 
java.lang.String.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.util.json.JsonArray.class, 
java.lang.String.class), this);
         registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, 
java.nio.ByteBuffer.class), this);
         registry.addConverter(new 
TypeConvertible<>(java.io.ByteArrayInputStream.class, 
org.apache.camel.StreamCache.class), this);
         registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
org.apache.camel.StreamCache.class), this);
@@ -165,6 +191,26 @@ public final class CamelSupportBulkConverterLoader 
implements TypeConverterLoade
             if (from == org.apache.camel.StreamCache.class) {
                 return this;
             }
+            if (from == org.apache.camel.util.json.JsonObject.class) {
+                return this;
+            }
+            if (from == org.apache.camel.util.json.JsonArray.class) {
+                return this;
+            }
+        } else if (to == java.io.InputStream.class) {
+            if (from == org.apache.camel.util.json.JsonObject.class) {
+                return this;
+            }
+            if (from == org.apache.camel.util.json.JsonArray.class) {
+                return this;
+            }
+        } else if (to == java.lang.String.class) {
+            if (from == org.apache.camel.util.json.JsonObject.class) {
+                return this;
+            }
+            if (from == org.apache.camel.util.json.JsonArray.class) {
+                return this;
+            }
         } else if (to == java.nio.ByteBuffer.class) {
             if (from == org.apache.camel.StreamCache.class) {
                 return this;
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/converter/json/JsonConverter.java
 
b/core/camel-support/src/main/java/org/apache/camel/converter/json/JsonConverter.java
index 3e6a91f60ac8..8a91e4a614e6 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/converter/json/JsonConverter.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/converter/json/JsonConverter.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.converter.json;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
@@ -140,4 +141,56 @@ public final class JsonConverter {
         return 
exchange.getContext().getTypeConverter().convertTo(Jsonable.class, exchange, 
body);
     }
 
+    @Converter(order = 16)
+    public static String convertToString(JsonObject json, Exchange exchange) {
+        String answer = toJson(json);
+        // a value that is not JSON (such as a POJO) keeps the toString of the 
map as before
+        return answer != null ? answer : json.toString();
+    }
+
+    @Converter(order = 17)
+    public static String convertToString(JsonArray json, Exchange exchange) {
+        String answer = toJson(json);
+        // a value that is not JSON (such as a POJO) keeps the toString of the 
list as before
+        return answer != null ? answer : json.toString();
+    }
+
+    @Converter(order = 18)
+    public static byte[] convertToByteArray(JsonObject json, Exchange 
exchange) {
+        return toBytes(json, exchange);
+    }
+
+    @Converter(order = 19)
+    public static byte[] convertToByteArray(JsonArray json, Exchange exchange) 
{
+        return toBytes(json, exchange);
+    }
+
+    @Converter(order = 20)
+    public static InputStream convertToInputStream(JsonObject json, Exchange 
exchange) {
+        byte[] data = toBytes(json, exchange);
+        return data != null ? new ByteArrayInputStream(data) : null;
+    }
+
+    @Converter(order = 21)
+    public static InputStream convertToInputStream(JsonArray json, Exchange 
exchange) {
+        byte[] data = toBytes(json, exchange);
+        return data != null ? new ByteArrayInputStream(data) : null;
+    }
+
+    private static byte[] toBytes(Jsonable json, Exchange exchange) {
+        String answer = toJson(json);
+        return answer != null ? 
answer.getBytes(ExchangeHelper.getCharset(exchange)) : null;
+    }
+
+    /**
+     * The JSON text, or null when a value is not JSON (such as a POJO), so 
there is no conversion as before.
+     */
+    private static String toJson(Jsonable json) {
+        try {
+            return json.toJson();
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
 }
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 9af133837d12..cf65f7dae81d 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -82,6 +82,15 @@ successful exchange opened the circuit again as long as the 
failures that opened
 the defaults (a window of 60 seconds, a half open check after 30 seconds) a 
success in the first 30 seconds after the
 half open check therefore kept the circuit open. A `halfOpenHandler` decides 
as before.
 
+=== camel-core - JsonObject and JsonArray convert to their JSON text
+
+The camel-util-json types `org.apache.camel.util.json.JsonObject` and 
`JsonArray` (given by `convertBodyTo: JsonObject`,
+graphql, kubernetes-custom-resources and typesafe-ai) now have type converters 
to `String`, `byte[]` and
+`InputStream` that give their JSON text, using the charset of the exchange for 
the bytes. Before, a `String` was the
+`toString` of the map or list (`{sku=CAMEL-MUG, qty=3}`, which is not JSON), 
and there was no conversion to `byte[]`
+or `InputStream`, so such a body sent to http or a file failed. A `JsonObject` 
or `JsonArray` that holds a value that
+is not JSON, such as a POJO, converts as before.
+
 === camel-main - a failure to start is logged
 
 A Camel Main application that fails to start now logs why, at ERROR: `Error 
starting Camel: ...` with the stack trace,

Reply via email to