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

gnodet pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new 36f8ecacce88 [backport camel-4.22.x] CAMEL-26585: camel-aws2-sns - 
serialize array attributes as valid JSON (#29182)
36f8ecacce88 is described below

commit 36f8ecacce882e54f0787c8f771369412d25902e
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Nov 4 20:07:46 2026 +0100

    [backport camel-4.22.x] CAMEL-26585: camel-aws2-sns - serialize array 
attributes as valid JSON (#29182)
---
 .../camel/component/aws2/sns/Sns2Producer.java     | 13 +++++-
 .../camel/component/aws2/sns/SnsProducerTest.java  | 50 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    | 11 +++++
 3 files changed, 73 insertions(+), 1 deletion(-)

diff --git 
a/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java
 
b/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java
index ed7a28661363..cdc8035fe2a4 100644
--- 
a/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java
+++ 
b/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Producer.java
@@ -35,6 +35,7 @@ import org.apache.camel.spi.HeaderFilterStrategy;
 import org.apache.camel.support.DefaultProducer;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.URISupport;
+import org.apache.camel.util.json.Jsoner;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import software.amazon.awssdk.core.SdkBytes;
@@ -153,7 +154,7 @@ public class Sns2Producer extends DefaultProducer {
                     result.put(entry.getKey(), mav.build());
                 } else if (value instanceof List) {
                     String resultString = ((List<?>) value).stream()
-                            .map(o -> o instanceof String ? 
String.format("\"%s\"", o) : Objects.toString(o))
+                            .map(Sns2Producer::toStringArrayValue)
                             .collect(Collectors.joining(", "));
                     MessageAttributeValue.Builder mav = 
MessageAttributeValue.builder();
                     mav.dataType("String.Array");
@@ -170,6 +171,16 @@ public class Sns2Producer extends DefaultProducer {
         return result;
     }
 
+    private static String toStringArrayValue(Object value) {
+        boolean jsonPrimitive = value == null || value instanceof Boolean || 
value instanceof Number;
+        if (value instanceof Double number) {
+            jsonPrimitive = Double.isFinite(number);
+        } else if (value instanceof Float number) {
+            jsonPrimitive = Float.isFinite(number);
+        }
+        return jsonPrimitive ? Objects.toString(value) : "\"" + 
Jsoner.escape(value.toString()) + "\"";
+    }
+
     private void configureFifoAttributes(PublishRequest.Builder request, 
Exchange exchange) {
         if (getEndpoint().getConfiguration().isFifoTopic()) {
             // use strategies
diff --git 
a/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java
 
b/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java
index a780c490cb2d..f9154b68c223 100644
--- 
a/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java
+++ 
b/components/camel-aws/camel-aws2-sns/src/test/java/org/apache/camel/component/aws2/sns/SnsProducerTest.java
@@ -17,10 +17,14 @@
 package org.apache.camel.component.aws2.sns;
 
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 import org.apache.camel.Exchange;
+import org.apache.camel.util.json.Jsoner;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -48,6 +52,52 @@ public class SnsProducerTest {
         when(endpoint.getHeaderFilterStrategy()).thenReturn(new 
Sns2HeaderFilterStrategy());
     }
 
+    @Test
+    public void translateStringArrayEscapesJsonCharacters() throws Exception {
+        List<String> values = List.of("a\"b", "C:\\temp", "line\nbreak", 
"tab\tvalue", "\b\f\r\u0001", "caf\u00e9",
+                "application/json", "\u007f", "\u2000");
+
+        MessageAttributeValue attribute = 
producer.translateAttributes(Map.of("values", values), exchange).get("values");
+
+        assertThat(attribute.dataType(), is("String.Array"));
+        assertThat(Jsoner.deserialize(attribute.stringValue()), is(values));
+    }
+
+    @Test
+    public void translateStringArrayQuotesOtherMemberTypes() throws Exception {
+        UUID id = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+        Date date = new Date(0);
+        List<Object> values = List.of(Membership.GOLD, 'x', id, date);
+
+        MessageAttributeValue attribute = 
producer.translateAttributes(Map.of("values", values), exchange).get("values");
+
+        assertThat(Jsoner.deserialize(attribute.stringValue()), 
is(List.of("GOLD", "x", id.toString(), date.toString())));
+    }
+
+    @Test
+    public void translateStringArrayQuotesNonFiniteNumbers() throws Exception {
+        List<Number> values = List.of(Double.NaN, Double.POSITIVE_INFINITY, 
Double.NEGATIVE_INFINITY,
+                Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY);
+
+        MessageAttributeValue attribute = 
producer.translateAttributes(Map.of("values", values), exchange).get("values");
+
+        assertThat(Jsoner.deserialize(attribute.stringValue()),
+                is(List.of("NaN", "Infinity", "-Infinity", "NaN", "Infinity", 
"-Infinity")));
+    }
+
+    @Test
+    public void translateStringArrayKeepsJsonPrimitives() {
+        List<Object> values = Arrays.asList(12, 1.5D, 2.5F, true, null);
+
+        MessageAttributeValue attribute = 
producer.translateAttributes(Map.of("values", values), exchange).get("values");
+
+        assertThat(attribute.stringValue(), is("[12, 1.5, 2.5, true, null]"));
+    }
+
+    private enum Membership {
+        GOLD
+    }
+
     @Test
     public void translateAttributes() {
         Map<String, Object> headers = new HashMap<>();
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 4ffee56c75ca..3afd44e3029f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -1600,6 +1600,17 @@ or when the queue is FIFO (AWS rejects per-message delay 
with `InvalidParameterV
 and was redundant on delay queues. If your route relied on per-message delay 
overrides on a delay queue,
 the override will now be silently ignored.
 
+=== camel-aws2-sns - string array message attributes
+
+String values in list-valued message headers are now JSON-escaped when mapped 
to SNS
+`String.Array` message attributes. Quotes, backslashes and control characters 
are preserved
+as string data. Routes should supply the original strings rather than 
pre-escaping them.
+Other member types, such as enums, characters, UUIDs and dates, are serialized 
as JSON strings.
+Non-finite floating-point values (`NaN` and infinities) are also serialized as 
strings; finite numbers,
+booleans and null remain JSON primitives.
+The JSON utility also escapes forward slashes and some Unicode characters, so 
raw `String.Array`
+attribute text may differ even when its decoded string values are unchanged.
+
 === camel-xslt-saxon - secure processing now applied unconditionally
 
 The `secureProcessing` option (default `true`) is now applied to the Saxon 
`TransformerFactory`

Reply via email to