diff --git a/.chronus/changes/http-client-java_bug-fix-serialization-dict-of-unknown-2026-1-9-15-8-29.md b/.chronus/changes/http-client-java_bug-fix-serialization-dict-of-unknown-2026-1-9-15-8-29.md new file mode 100644 index 00000000000..29fba344fd2 --- /dev/null +++ b/.chronus/changes/http-client-java_bug-fix-serialization-dict-of-unknown-2026-1-9-15-8-29.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-client-java" +--- + +Fix mock data for BinaryData. \ No newline at end of file diff --git a/packages/http-client-java/generator/http-client-generator-clientcore-test/src/main/java/type/union/MixedTypesCases.java b/packages/http-client-java/generator/http-client-generator-clientcore-test/src/main/java/type/union/MixedTypesCases.java index 7f29429d680..ab22bd28590 100644 --- a/packages/http-client-java/generator/http-client-generator-clientcore-test/src/main/java/type/union/MixedTypesCases.java +++ b/packages/http-client-java/generator/http-client-generator-clientcore-test/src/main/java/type/union/MixedTypesCases.java @@ -129,8 +129,13 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { this.intProperty.writeTo(jsonWriter); jsonWriter.writeFieldName("boolean"); this.booleanProperty.writeTo(jsonWriter); - jsonWriter.writeArrayField("array", this.array, - (writer, element) -> writer.writeUntyped(element == null ? null : element.toObject(Object.class))); + jsonWriter.writeArrayField("array", this.array, (writer, element) -> { + if (element == null) { + writer.writeNull(); + } else { + element.writeTo(writer); + } + }); return jsonWriter.writeEndObject(); } diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/StreamSerializationModelTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/StreamSerializationModelTemplate.java index c8656983965..b964ecf4676 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/StreamSerializationModelTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/StreamSerializationModelTemplate.java @@ -879,7 +879,11 @@ private static void serializeJsonContainerProperty(JavaBlock methodBlock, String } methodBlock.indent(() -> { - if (valueSerializationMethod != null) { + if (elementType == ClassType.BINARY_DATA) { + // Special handling for BinaryData + methodBlock.line("{ if (%1$s == null) { %2$s.writeNull(); } else { %1$s.writeTo(%2$s); } }", + elementName, lambdaWriterName); + } else if (valueSerializationMethod != null) { if (isJsonMergePatch && containerType instanceof MapType) { methodBlock.block("", codeBlock -> codeBlock.ifBlock(elementName + "!= null", ifBlock -> { if (elementType instanceof ClassType && ((ClassType) elementType).isSwaggerType()) { @@ -908,8 +912,6 @@ private static void serializeJsonContainerProperty(JavaBlock methodBlock, String serializeJsonContainerProperty(methodBlock, "writeMap", elementType, ((MapType) elementType).getValueType(), serializedName, propertyValueGetter, depth + 1, isJsonMergePatch); - } else if (elementType == ClassType.BINARY_DATA) { - methodBlock.line(elementName + ".writeTo(" + lambdaWriterName + ")"); } else { throw new RuntimeException("Unknown value type " + elementType + " in " + containerType + " serialization. Need to add support for it."); diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/ModelTestCaseUtil.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/ModelTestCaseUtil.java index f38954434f8..cc4cbe0e432 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/ModelTestCaseUtil.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/util/ModelTestCaseUtil.java @@ -129,7 +129,7 @@ public static Object jsonFromType(int depth, IType type) { return "http://example.org/" + URLEncoder.encode(randomString(), StandardCharsets.UTF_8); } else if (type == ClassType.OBJECT || type == ClassType.BINARY_DATA) { // unknown type, use a simple string - return "data" + randomString(); + return ClassType.STRING.defaultValueExpression("data" + randomString()); } else if (type instanceof EnumType) { IType elementType = ((EnumType) type).getElementType(); List values diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/tsptest/armstreamstyleserialization/models/Builtin.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/tsptest/armstreamstyleserialization/models/Builtin.java index 4baa115a2f1..f450c4a17fc 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/tsptest/armstreamstyleserialization/models/Builtin.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/tsptest/armstreamstyleserialization/models/Builtin.java @@ -122,6 +122,21 @@ public final class Builtin implements JsonSerializable { */ private BinaryData unknown; + /* + * The unknownDict property. + */ + private Map unknownDict; + + /* + * The unknownArray property. + */ + private List unknownArray; + + /* + * The unknownDictArray property. + */ + private List> unknownDictArray; + /** * Creates an instance of Builtin class. */ @@ -508,6 +523,66 @@ public Builtin withUnknown(BinaryData unknown) { return this; } + /** + * Get the unknownDict property: The unknownDict property. + * + * @return the unknownDict value. + */ + public Map unknownDict() { + return this.unknownDict; + } + + /** + * Set the unknownDict property: The unknownDict property. + * + * @param unknownDict the unknownDict value to set. + * @return the Builtin object itself. + */ + public Builtin withUnknownDict(Map unknownDict) { + this.unknownDict = unknownDict; + return this; + } + + /** + * Get the unknownArray property: The unknownArray property. + * + * @return the unknownArray value. + */ + public List unknownArray() { + return this.unknownArray; + } + + /** + * Set the unknownArray property: The unknownArray property. + * + * @param unknownArray the unknownArray value to set. + * @return the Builtin object itself. + */ + public Builtin withUnknownArray(List unknownArray) { + this.unknownArray = unknownArray; + return this; + } + + /** + * Get the unknownDictArray property: The unknownDictArray property. + * + * @return the unknownDictArray value. + */ + public List> unknownDictArray() { + return this.unknownDictArray; + } + + /** + * Set the unknownDictArray property: The unknownDictArray property. + * + * @param unknownDictArray the unknownDictArray value to set. + * @return the Builtin object itself. + */ + public Builtin withUnknownDictArray(List> unknownDictArray) { + this.unknownDictArray = unknownDictArray; + return this; + } + /** * Validates the instance. * @@ -565,6 +640,18 @@ public void validate() { throw LOGGER.atError() .log(new IllegalArgumentException("Missing required property unknown in model Builtin")); } + if (unknownDict() == null) { + throw LOGGER.atError() + .log(new IllegalArgumentException("Missing required property unknownDict in model Builtin")); + } + if (unknownArray() == null) { + throw LOGGER.atError() + .log(new IllegalArgumentException("Missing required property unknownArray in model Builtin")); + } + if (unknownDictArray() == null) { + throw LOGGER.atError() + .log(new IllegalArgumentException("Missing required property unknownDictArray in model Builtin")); + } } private static final ClientLogger LOGGER = new ClientLogger(Builtin.class); @@ -597,6 +684,28 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStringField("uuid", this.uuid); jsonWriter.writeFieldName("unknown"); this.unknown.writeTo(jsonWriter); + jsonWriter.writeMapField("unknownDict", this.unknownDict, (writer, element) -> { + if (element == null) { + writer.writeNull(); + } else { + element.writeTo(writer); + } + }); + jsonWriter.writeArrayField("unknownArray", this.unknownArray, (writer, element) -> { + if (element == null) { + writer.writeNull(); + } else { + element.writeTo(writer); + } + }); + jsonWriter.writeArrayField("unknownDictArray", this.unknownDictArray, + (writer, element) -> writer.writeMap(element, (writer1, element1) -> { + if (element1 == null) { + writer1.writeNull(); + } else { + element1.writeTo(writer1); + } + })); return jsonWriter.writeEndObject(); } @@ -663,6 +772,19 @@ public static Builtin fromJson(JsonReader jsonReader) throws IOException { } else if ("unknown".equals(fieldName)) { deserializedBuiltin.unknown = reader.getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped())); + } else if ("unknownDict".equals(fieldName)) { + Map unknownDict = reader.readMap(reader1 -> reader1 + .getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped()))); + deserializedBuiltin.unknownDict = unknownDict; + } else if ("unknownArray".equals(fieldName)) { + List unknownArray = reader.readArray(reader1 -> reader1 + .getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped()))); + deserializedBuiltin.unknownArray = unknownArray; + } else if ("unknownDictArray".equals(fieldName)) { + List> unknownDictArray + = reader.readArray(reader1 -> reader1.readMap(reader2 -> reader2 + .getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped())))); + deserializedBuiltin.unknownDictArray = unknownDictArray; } else { reader.skipChildren(); } diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/java/type/union/models/MixedTypesCases.java b/packages/http-client-java/generator/http-client-generator-test/src/main/java/type/union/models/MixedTypesCases.java index a1432230f8b..8adfc9a891b 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/java/type/union/models/MixedTypesCases.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/java/type/union/models/MixedTypesCases.java @@ -133,8 +133,13 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { this.intProperty.writeTo(jsonWriter); jsonWriter.writeFieldName("boolean"); this.booleanProperty.writeTo(jsonWriter); - jsonWriter.writeArrayField("array", this.array, - (writer, element) -> writer.writeUntyped(element == null ? null : element.toObject(Object.class))); + jsonWriter.writeArrayField("array", this.array, (writer, element) -> { + if (element == null) { + writer.writeNull(); + } else { + element.writeTo(writer); + } + }); return jsonWriter.writeEndObject(); } diff --git a/packages/http-client-java/generator/http-client-generator-test/src/test/java/tsptest/armstreamstyleserialization/StreamStyleSerializationTests.java b/packages/http-client-java/generator/http-client-generator-test/src/test/java/tsptest/armstreamstyleserialization/StreamStyleSerializationTests.java index af56b1cdd2c..8521d76f970 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/test/java/tsptest/armstreamstyleserialization/StreamStyleSerializationTests.java +++ b/packages/http-client-java/generator/http-client-generator-test/src/test/java/tsptest/armstreamstyleserialization/StreamStyleSerializationTests.java @@ -11,6 +11,9 @@ import com.azure.core.util.serializer.SerializerAdapter; import com.azure.core.util.serializer.SerializerEncoding; import com.azure.json.JsonProviders; +import com.azure.json.JsonReader; +import com.azure.json.JsonSerializable; +import com.azure.json.JsonToken; import com.azure.json.JsonWriter; import java.io.IOException; import java.io.StringWriter; @@ -107,6 +110,73 @@ public void testPropertyWithNullValue() { Assertions.assertEquals("input", jsonDict.get("input")); } + public final static class TestBinary implements JsonSerializable { + private Map unknownDict; + + public Map unknownDict() { + return this.unknownDict; + } + + public TestBinary() { + } + + public TestBinary withUnknownDict(Map unknownDict) { + this.unknownDict = unknownDict; + return this; + } + + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeMapField("unknownDict", this.unknownDict, (writer, element) -> { + if (element == null) { + writer.writeNull(); + } else { + element.writeTo(writer); + } + }); + return jsonWriter.writeEndObject(); + } + + public static TestBinary fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + TestBinary deserializedTestBinary = new TestBinary(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("unknownDict".equals(fieldName)) { + Map unknownDict = reader.readMap(reader1 -> reader1 + .getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped()))); + deserializedTestBinary.unknownDict = unknownDict; + } else { + reader.skipChildren(); + } + } + + return deserializedTestBinary; + }); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testBinaryDataInContainer() { + // use a simple class + TestBinary model = new TestBinary().withUnknownDict( + Map.of("string", BinaryData.fromString("\"value\""), "object", BinaryData.fromString("{\"k\", \"v\"}"))); + + com.azure.core.util.BinaryData binaryData = BinaryData.fromObject(model); + String jsonString = binaryData.toString(); + Map jsonMap = BinaryData.fromString(jsonString).toObject(Map.class); + Assertions.assertTrue(jsonMap.containsKey("unknownDict")); + Map unknownDict = (Map) jsonMap.get("unknownDict"); + Assertions.assertEquals("\"value\"", unknownDict.get("string")); + Assertions.assertEquals("{\"k\", \"v\"}", unknownDict.get("object")); + + model = binaryData.toObject(TestBinary.class); + } + @Test public void ensureInstantMaxValue() { // ensure Integer.MAX_VALUE doesn't exceeds Instant.MAX diff --git a/packages/http-client-java/generator/http-client-generator-test/tsp/arm-stream-style-serialization.tsp b/packages/http-client-java/generator/http-client-generator-test/tsp/arm-stream-style-serialization.tsp index fc109c13e40..d26c8cf9f3d 100644 --- a/packages/http-client-java/generator/http-client-generator-test/tsp/arm-stream-style-serialization.tsp +++ b/packages/http-client-java/generator/http-client-generator-test/tsp/arm-stream-style-serialization.tsp @@ -309,6 +309,9 @@ model Builtin { encoding: Encoded; uuid: Azure.Core.uuid; `unknown`: unknown; + unknownDict: Record; + unknownArray: unknown[]; + unknownDictArray: Record[]; } @encode(DurationKnownEncoding.seconds, float32)