Repository: auth0/java-jwt (language: java)

Issue:
PublicClaims.AUDIENCE (aud) expected to be String or "Array of Strings", not Collection(aka List) by PayloadSerializer
### Describe the problem
By default jackson deserializes to ArrayList. Can not find an easy way to change this behavior.
so, having payload = `{exp=1630398915, aud=["firma_x","firma_y"]}` following code

```
    Map payloadMap = new ObjectMapper().readValue(payload, Map.class);
    String result = JWT.create()
                       .withPayload(payloadMap)
                       .sign(Algorithm.HMAC256("secret"));
```
will cause an exception:

```
Caused by: java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class [Ljava.lang.String; (java.util.ArrayList and [Ljava.lang.String; are in module java.base of loader 'bootstrap')
	at com.auth0.jwt.impl.PayloadSerializer.serialize(PayloadSerializer.java:40)
	at com.auth0.jwt.impl.PayloadSerializer.serialize(PayloadSerializer.java:18)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
```
Not sure, why special handling for PublicClaims.AUDIENCE is needed in first place, but it could be much more convenient  to support collections as well.

Note: no errors in other places(Claims) where collections are used.

### What was the expected behavior?

Collections can be handelt as well.

### Environment

- Version of this library used: 3.18.1
- Version of Java used: jdk-14.0.2
- Other modules/plugins/libraries that might be involved: com.fasterxml.jackson.core:jackson-databind:2.12.4 




Discussion / hints:
Update: 
Found jackson feature [USE_JAVA_ARRAY_FOR_JSON_ARRAY](https://fasterxml.github.io/jackson-core/javadoc/1.9/org/codehaus/jackson/map/DeserializationConfig.Feature.html#USE_JAVA_ARRAY_FOR_JSON_ARRAY)

Unfortunately it is even worse, as in this case another exception is produced and not for this single PublicClaims.AUDIENCE Claim, but everywhere, where collection(array) is used:

```
  Map payloadMap = new ObjectMapper()
      .configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true)
      .readValue(payload, Map.class);
  String result = JWT.create()
                       .withPayload(payloadMap)
                       .sign(Algorithm.HMAC256("secret"));
``` 
Exception:
```
java.lang.IllegalArgumentException: Claim values must only be of types Map, List, Boolean, Integer, Long, Double, String and Date

	at com.auth0.jwt.JWTCreator$Builder.withPayload(JWTCreator.java:383)
```
Thanks for the good details and steps to reproduce @yeDor! We have special handling in place for the `aud` claim as if it's a single value, we write that string; if it's multiple values (we use `String[]` internally) we write it as an array. The `withPayload` method exposes the issue you've discovered, but I think we should be able to handle both Lists and String arrays without issue when serializing the `aud` claim. I've added it to our backlog; hopefully if it's a straightforward change I can get a PR out this week.

Fix patch:
diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java
index 684137cc..8a86c04a 100644
--- a/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java
+++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadSerializer.java
@@ -5,8 +5,7 @@
 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 
 import java.io.IOException;
-import java.util.Date;
-import java.util.Map;
+import java.util.*;
 
 /**
  * Jackson serializer implementation for converting into JWT Payload parts.
@@ -30,40 +29,51 @@ public void serialize(ClaimsHolder holder, JsonGenerator gen, SerializerProvider
 
         gen.writeStartObject();
         for (Map.Entry<String, Object> e : holder.getClaims().entrySet()) {
-            switch (e.getKey()) {
-                case PublicClaims.AUDIENCE:
-                    if (e.getValue() instanceof String) {
-                        gen.writeFieldName(e.getKey());
-                        gen.writeString((String) e.getValue());
-                        break;
-                    }
-                    String[] audArray = (String[]) e.getValue();
-                    if (audArray.length == 1) {
-                        gen.writeFieldName(e.getKey());
-                        gen.writeString(audArray[0]);
-                    } else if (audArray.length > 1) {
-                        gen.writeFieldName(e.getKey());
-                        gen.writeStartArray();
-                        for (String aud : audArray) {
-                            gen.writeString(aud);
-                        }
-                        gen.writeEndArray();
-                    }
-                    break;
-                default:
-                    gen.writeFieldName(e.getKey());
-                    if (e.getValue() instanceof Date) { // true for EXPIRES_AT, ISSUED_AT, NOT_BEFORE
-                        gen.writeNumber(dateToSeconds((Date) e.getValue()));
-                    } else {
-                        gen.writeObject(e.getValue());
-                    }
-                    break;
+            if (PublicClaims.AUDIENCE.equals(e.getKey())) {
+                writeAudience(gen, e);
+            } else {
+                gen.writeFieldName(e.getKey());
+                if (e.getValue() instanceof Date) { // true for EXPIRES_AT, ISSUED_AT, NOT_BEFORE
+                    gen.writeNumber(dateToSeconds((Date) e.getValue()));
+                } else {
+                    gen.writeObject(e.getValue());
+                }
             }
         }
 
         gen.writeEndObject();
     }
 
+    private void writeAudience(JsonGenerator gen, Map.Entry<String, Object> e) throws IOException {
+        if (e.getValue() instanceof String) {
+            gen.writeFieldName(e.getKey());
+            gen.writeString((String) e.getValue());
+        } else {
+            List<String> audArray = new ArrayList<>();
+            if (e.getValue() instanceof String[]) {
+                audArray = Arrays.asList((String[]) e.getValue());
+            } else if (e.getValue() instanceof List) {
+                List<?> audList = (List<?>) e.getValue();
+                for (Object aud : audList) {
+                    if (aud instanceof String) {
+                        audArray.add((String)aud);
+                    }
+                }
+            }
+            if (audArray.size() == 1) {
+                gen.writeFieldName(e.getKey());
+                gen.writeString(audArray.get(0));
+            } else if (audArray.size() > 1) {
+                gen.writeFieldName(e.getKey());
+                gen.writeStartArray();
+                for (String aud : audArray) {
+                    gen.writeString(aud);
+                }
+                gen.writeEndArray();
+            }
+        }
+    }
+
     private long dateToSeconds(Date date) {
         return date.getTime() / 1000;
     }


Test patch:
diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java
index ebdd5864..3ebc29dd 100644
--- a/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java
+++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadSerializerTest.java
@@ -9,10 +9,7 @@
 import org.junit.Test;
 
 import java.io.StringWriter;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
@@ -80,6 +77,60 @@ public void shouldSkipSerializationOnEmptyAudience() throws Exception {
         assertThat(writer.toString(), is(equalTo("{}")));
     }
 
+    @Test
+    public void shouldSerializeSingleItemAudienceAsArrayWhenAList() throws Exception {
+        ClaimsHolder holder = holderFor("aud", Collections.singletonList("auth0"));
+        serializer.serialize(holder, jsonGenerator, serializerProvider);
+        jsonGenerator.flush();
+
+        assertThat(writer.toString(), is(equalTo("{\"aud\":\"auth0\"}")));
+    }
+
+    @Test
+    public void shouldSerializeMultipleItemsAudienceAsArrayWhenAList() throws Exception {
+        ClaimsHolder holder = holderFor("aud", Arrays.asList("auth0", "auth10"));
+        serializer.serialize(holder, jsonGenerator, serializerProvider);
+        jsonGenerator.flush();
+
+        assertThat(writer.toString(), is(equalTo("{\"aud\":[\"auth0\",\"auth10\"]}")));
+    }
+
+    @Test
+    public void shouldSkipSerializationOnEmptyAudienceWhenList() throws Exception {
+        ClaimsHolder holder = holderFor("aud", new ArrayList());
+        serializer.serialize(holder, jsonGenerator, serializerProvider);
+        jsonGenerator.flush();
+
+        assertThat(writer.toString(), is(equalTo("{}")));
+    }
+
+    @Test
+    public void shouldSkipNonStringsOnAudienceWhenSingleItemList() throws Exception {
+        ClaimsHolder holder = holderFor("aud", Collections.singletonList(2));
+        serializer.serialize(holder, jsonGenerator, serializerProvider);
+        jsonGenerator.flush();
+
+        assertThat(writer.toString(), is(equalTo("{}")));
+    }
+
+    @Test
+    public void shouldSkipNonStringsOnAudienceWhenList() throws Exception {
+        ClaimsHolder holder = holderFor("aud", Arrays.asList("auth0", 2, "auth10"));
+        serializer.serialize(holder, jsonGenerator, serializerProvider);
+        jsonGenerator.flush();
+
+        assertThat(writer.toString(), is(equalTo("{\"aud\":[\"auth0\",\"auth10\"]}")));
+    }
+
+    @Test
+    public void shouldSkipNonStringsOnAudience() throws Exception {
+        ClaimsHolder holder = holderFor("aud", 4);
+        serializer.serialize(holder, jsonGenerator, serializerProvider);
+        jsonGenerator.flush();
+
+        assertThat(writer.toString(), is(equalTo("{}")));
+    }
+
     @Test
     public void shouldSerializeNotBeforeDateInSeconds() throws Exception {
         ClaimsHolder holder = holderFor("nbf", new Date(1478874000));