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 c69f26ab4d23 CAMEL-26324: camel-core - type converter from JsonObject
and JsonArray to their JSON text (#28520)
c69f26ab4d23 is described below
commit c69f26ab4d23633a9ee3c9b0878987ace3012814
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 26 16:25:10 2026 +0100
CAMEL-26324: camel-core - type converter from JsonObject and JsonArray to
their JSON text (#28520)
Co-Authored-By: Claude Opus 5.5 (1M context) <[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 d289ebdbd6fe..dac0f26b258c 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,