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,
