Skip to content

Commit 7be4b73

Browse files
lzswebzhansheng.lzs
andauthored
fix(multimodal): fix UnsupportedOperationException for BigDecimal/BigInteger in message content (#250) (#255)
JsonTreeWriter.jsonValue() throws UnsupportedOperationException, but MultiModalConversationParam.getInput() serializes messages via Gson.toJsonTree(), so a BigDecimal/BigInteger value (e.g. fps) in message content crashed the request before sending. Regression introduced by #244 in v2.22.25. Write numbers with value(Number) and serialize fallback objects directly into the writer instead. Co-authored-by: zhansheng.lzs <zhansheng.lzs@alibaba-inc.com>
1 parent 6b64c26 commit 7be4b73

2 files changed

Lines changed: 94 additions & 9 deletions

File tree

src/main/java/com/alibaba/dashscope/common/MultiModalMessageAdapter.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,10 @@ private void writeValue(JsonWriter out, Object value) throws IOException {
4646
out.value(((Number) value).doubleValue());
4747
} else if (value instanceof Short || value instanceof Byte) {
4848
out.value(((Number) value).intValue());
49-
} else if (value instanceof BigDecimal) {
50-
// Use jsonValue to write as JSON number, not string
51-
out.jsonValue(((BigDecimal) value).toPlainString());
52-
} else if (value instanceof BigInteger) {
53-
// Use jsonValue to write as JSON number, not string
54-
out.jsonValue(((BigInteger) value).toString());
49+
} else if (value instanceof BigDecimal || value instanceof BigInteger) {
50+
// value(Number) keeps the JSON number unquoted and is JsonTreeWriter-safe,
51+
// unlike jsonValue which throws UnsupportedOperationException under toJsonTree
52+
out.value((Number) value);
5553
} else if (value instanceof Boolean) {
5654
out.value((Boolean) value);
5755
} else if (value instanceof Character) {
@@ -66,9 +64,9 @@ private void writeValue(JsonWriter out, Object value) throws IOException {
6664
} else if (value instanceof Map) {
6765
writeMapObject(out, (Map<String, Object>) value);
6866
} else {
69-
// For unsupported types, serialize using Gson to JSON string
70-
String jsonStr = JsonUtils.toJson(value);
71-
out.jsonValue(jsonStr);
67+
// Serialize arbitrary objects through Gson directly into the writer;
68+
// works for both streaming and JsonTreeWriter (toJsonTree) paths
69+
JsonUtils.gson.toJson(value, value.getClass(), out);
7270
}
7371
}
7472

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) Alibaba, Inc. and its affiliates.
2+
package com.alibaba.dashscope;
3+
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import com.alibaba.dashscope.common.MultiModalMessage;
8+
import com.alibaba.dashscope.utils.JsonUtils;
9+
import com.google.gson.JsonArray;
10+
import com.google.gson.JsonObject;
11+
import java.math.BigDecimal;
12+
import java.math.BigInteger;
13+
import java.util.Arrays;
14+
import java.util.Collections;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
import org.junit.jupiter.api.Test;
18+
19+
public class TestMultiModalMessageAdapter {
20+
21+
private static class CustomContent {
22+
public String url = "http://example.com/a.png";
23+
}
24+
25+
@Test
26+
public void testBigDecimalAndBigIntegerViaToJsonTree() {
27+
Map<String, Object> videoContent = new HashMap<>();
28+
videoContent.put("video", Arrays.asList("base64img1", "base64img2"));
29+
videoContent.put("fps", new BigDecimal("0.1"));
30+
videoContent.put("nframes", new BigInteger("12345678901234567890"));
31+
32+
MultiModalMessage msg =
33+
MultiModalMessage.builder()
34+
.role("user")
35+
.content(
36+
Arrays.asList(
37+
Collections.singletonMap("text", (Object) "describe this video"), videoContent))
38+
.build();
39+
40+
// MultiModalConversationParam.getInput() serializes through Gson.toJsonTree,
41+
// whose JsonTreeWriter does not support jsonValue()
42+
JsonArray array = JsonUtils.toJsonArray(Arrays.asList(msg));
43+
JsonObject video =
44+
array.get(0).getAsJsonObject().getAsJsonArray("content").get(1).getAsJsonObject();
45+
assertTrue(video.get("fps").isJsonPrimitive());
46+
assertTrue(video.get("fps").getAsJsonPrimitive().isNumber());
47+
assertEquals(new BigDecimal("0.1"), video.get("fps").getAsBigDecimal());
48+
assertEquals(new BigInteger("12345678901234567890"), video.get("nframes").getAsBigInteger());
49+
}
50+
51+
@Test
52+
public void testUnsupportedTypeFallbackViaToJsonTree() {
53+
Map<String, Object> content = new HashMap<>();
54+
content.put("custom", new CustomContent());
55+
56+
MultiModalMessage msg =
57+
MultiModalMessage.builder().role("user").content(Arrays.asList(content)).build();
58+
59+
JsonArray array = JsonUtils.toJsonArray(Arrays.asList(msg));
60+
JsonObject custom =
61+
array
62+
.get(0)
63+
.getAsJsonObject()
64+
.getAsJsonArray("content")
65+
.get(0)
66+
.getAsJsonObject()
67+
.getAsJsonObject("custom");
68+
assertEquals("http://example.com/a.png", custom.get("url").getAsString());
69+
}
70+
71+
@Test
72+
public void testStreamingWriterPathStillWorks() {
73+
Map<String, Object> content = new HashMap<>();
74+
content.put("fps", new BigDecimal("0.1"));
75+
content.put("custom", new CustomContent());
76+
77+
MultiModalMessage msg =
78+
MultiModalMessage.builder().role("user").content(Arrays.asList(content)).build();
79+
80+
String json = JsonUtils.toJson(msg);
81+
JsonObject parsed = JsonUtils.parse(json);
82+
JsonObject first = parsed.getAsJsonArray("content").get(0).getAsJsonObject();
83+
assertEquals(new BigDecimal("0.1"), first.get("fps").getAsBigDecimal());
84+
assertEquals(
85+
"http://example.com/a.png", first.getAsJsonObject("custom").get("url").getAsString());
86+
}
87+
}

0 commit comments

Comments
 (0)