Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/**
* The details of a class type that is used by a client.
*/
public class ClassType implements IType, ConvertToJsonTypeTrait {
public class ClassType implements IType, ConvertToJsonTypeTrait, ConvertFromJsonTypeTrait {
private static ClassType withClientCoreReplacement(String azureClass, String clientCoreClass) {
return withClientCoreAndVNextReplacement(azureClass, clientCoreClass, clientCoreClass);
}
Expand Down Expand Up @@ -282,6 +282,7 @@ private static Builder withVNextReplacementBuilder(String azureClass, String azu
public static final ClassType POLLER_FLUX = new ClassType("com.azure.core.util.polling", "PollerFlux");

// Complex mapped types
// JSON type is STRING, wire type is Base64Url/Base64Uri, client type is byte[]
public static final ClassType BASE_64_URL
= withClientCoreReplacementBuilder("com.azure.core.util.Base64Url", "io.clientcore.core.utils.Base64Uri", false)
.serializationValueGetterModifier(valueGetter -> "Objects.toString(" + valueGetter + ", null)")
Expand Down Expand Up @@ -310,6 +311,7 @@ private static Builder withVNextReplacementBuilder(String azureClass, String azu
.xmlAttributeDeserializationTemplate("%s.getNullableAttribute(%s, %s, BinaryData::fromObject)")
.build();

// JSON type is STRING, wire type is DateTimeRfc1123, client type is OffsetDateTime
public static final ClassType DATE_TIME_RFC_1123
= withClientCoreReplacementBuilder("com.azure.core.util.DateTimeRfc1123",
"io.clientcore.core.utils.DateTimeRfc1123", false)
Expand Down Expand Up @@ -497,8 +499,11 @@ private static Builder withVNextReplacementBuilder(String azureClass, String azu
.xmlAttributeDeserializationTemplate("%s.getNullableAttribute(%s, %s, OffsetDateTime::parse)")
.build();

// JSON type is NUMERIC, client type is OffsetDateTime
public static final ClassType UNIX_TIME_LONG = new ClassType.Builder(false).prototypeAsLong().build();
// JSON type is NUMERIC, client type is Duration
public static final ClassType DURATION_LONG = new ClassType.Builder(false).prototypeAsLong().build();
// JSON type is NUMERIC, client type is Duration
public static final ClassType DURATION_DOUBLE = new ClassType.Builder(false).prototypeAsDouble().build();

public static final ClassType URL = new Builder(false)
Expand Down Expand Up @@ -786,9 +791,31 @@ public boolean isUsedInXml() {

@Override
public String convertToJsonType(String variableName) {
String expression = convertFromClientType(variableName);
return serializationValueGetterModifier != null
? serializationValueGetterModifier.apply(variableName)
: variableName;
? serializationValueGetterModifier.apply(expression)
: expression;
}

@Override
public String convertFromJsonType(String variableName) {
// TODO (weidxu): it may be better to refactor it to type initialization, similar as
// defaultValueExpressionConverter
if (this == ClassType.INTEGER_AS_STRING) {
return variableName + " == null ? null : Integer.parseInt(" + variableName + ")";
} else if (this == ClassType.LONG_AS_STRING) {
return variableName + " == null ? null : Long.parseInt(" + variableName + ")";
} else if (this == ClassType.DATE_TIME) {
return variableName + " == null ? null : OffsetDateTime.parse(" + variableName + ")";
} else if (this == ClassType.DATE_TIME_RFC_1123) {
return variableName + " == null ? null : new DateTimeRfc1123(" + variableName + ")";
} else if (this == ClassType.DURATION) {
return variableName + " == null ? null : Duration.parse(" + variableName + ")";
} else if (this == ClassType.URL) {
return variableName + " == null ? null : new URL(" + variableName + ")";
} else {
return convertToClientType(variableName);
}
Comment on lines +802 to +818
Copy link
Member

@XiaofeiCao XiaofeiCao Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder can we put this into convertToClientType implementation?

And what's the difference between wireType and jsonType?

Copy link
Contributor Author

@weidongxu-microsoft weidongxu-microsoft Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wire type can be e.g. DateTimeRfc1123. JSON type would be JSON string/numeric/boolean etc. (object or array not counted)

Copy link
Contributor Author

@weidongxu-microsoft weidongxu-microsoft Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "wire type" in codegen may not be what you think "on wire".

It is more an "internal type" (compared to client type as "customer facing type").

For models, since codegen now own the serialization/de- (no Jackson needed), this "internal type" may not be useful at all. E.g. we should be able to directly convert a JSON string to OffsetDateTime, according to RFC1123/7231 protocol (instead of the usual RFC3339).

However, for proxy method, the core / clientcore still takes this "internal type" for request parameter / body / response body.

}

public static class Builder {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.typespec.http.client.generator.core.model.clientmodel;

/**
* Trait for types that can convert a JSON wire value back into the client type.
*/
public interface ConvertFromJsonTypeTrait {

/**
* Gets the expression that converts the JSON wire value into this type.
*
* @param variableName The variable to convert.
* @return The expression that converts the variable to this type.
*/
String convertFromJsonType(String variableName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
public interface ConvertToJsonTypeTrait {

/**
* Gets the expression that convert the variable of this type to the wire type.
* Gets the expression that convert the variable of this type to the JSON wire type.
*
* @param variableName The variable to convert.
* @return The expression that convert the variable to the wire type.
* @return The expression that convert the variable to the JSON wire type.
*/
String convertToJsonType(String variableName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* The details of an enumerated type that is used by a service.
*/
public class EnumType implements IType, ConvertToJsonTypeTrait {
public class EnumType implements IType, ConvertToJsonTypeTrait, ConvertFromJsonTypeTrait {
/**
* The name of the new Enum.
*/
Expand Down Expand Up @@ -233,6 +233,11 @@ public String convertToJsonType(String variableName) {
return variableName + " == null ? null : " + variableName + "." + getToMethodName() + "()";
}

@Override
public String convertFromJsonType(String variableName) {
return variableName + " == null ? null : " + getName() + "." + getFromMethodName() + "(" + variableName + ")";
}

public static class Builder {
private String name;
private String description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/**
* A basic type used by a client.
*/
public class PrimitiveType implements IType, ConvertToJsonTypeTrait {
public class PrimitiveType implements IType, ConvertToJsonTypeTrait, ConvertFromJsonTypeTrait {
public static final PrimitiveType VOID = new Builder().name("void").nullableType(ClassType.VOID).build();

public static final PrimitiveType BOOLEAN = new Builder().name("boolean")
Expand Down Expand Up @@ -50,6 +50,7 @@ public class PrimitiveType implements IType, ConvertToJsonTypeTrait {

public static final PrimitiveType LONG = new Builder().prototypeAsLong().build();

// JSON type is STRING
public static final PrimitiveType INT_AS_STRING = new Builder().name("int")
.nullableType(ClassType.INTEGER_AS_STRING)
.defaultValueExpressionConverter(
Expand All @@ -63,6 +64,7 @@ public class PrimitiveType implements IType, ConvertToJsonTypeTrait {
.xmlAttributeDeserializationTemplate("%s.getNullableAttribute(%s, %s, Integer::valueOf)")
.build();

// JSON type is STRING
public static final PrimitiveType LONG_AS_STRING = new Builder().prototypeAsLong()
.nullableType(ClassType.LONG_AS_STRING)
.defaultValueExpressionConverter(defaultValueExpression -> "Long.parseLong(\"" + defaultValueExpression + "\")")
Expand Down Expand Up @@ -99,12 +101,15 @@ public class PrimitiveType implements IType, ConvertToJsonTypeTrait {
.xmlElementDeserializationMethod("getStringElement().charAt(0)")
.build();

// JSON type is NUMERIC, client type is OffsetDateTime
public static final PrimitiveType UNIX_TIME_LONG
= new Builder().prototypeAsLong().nullableType(ClassType.UNIX_TIME_LONG).build();

// JSON type is NUMERIC, client type is Duration
public static final PrimitiveType DURATION_LONG
= new Builder().prototypeAsLong().nullableType(ClassType.DURATION_LONG).build();

// JSON type is NUMERIC, client type is Duration
public static final PrimitiveType DURATION_DOUBLE
= new Builder().prototypeAsDouble().nullableType(ClassType.DURATION_DOUBLE).build();

Expand Down Expand Up @@ -305,10 +310,22 @@ public String toString() {

@Override
public String convertToJsonType(String variableName) {
String expression = convertFromClientType(variableName);
if (wrapSerializationWithObjectsToString) {
return "Objects.toString(" + variableName + ", null)";
return "Objects.toString(" + expression + ", null)";
} else {
return variableName;
return expression;
}
}

@Override
public String convertFromJsonType(String variableName) {
if (this == PrimitiveType.INT_AS_STRING) {
return variableName + " == null ? null : Integer.parseInt(" + variableName + ")";
} else if (this == PrimitiveType.LONG_AS_STRING) {
return variableName + " == null ? null : Long.parseInt(" + variableName + ")";
} else {
return convertToClientType(variableName);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelProperty;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelPropertyAccess;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelPropertyReference;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ConvertFromJsonTypeTrait;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ConvertToJsonTypeTrait;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.IType;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.IterableType;
Expand Down Expand Up @@ -1623,13 +1624,7 @@ private void generateJsonDeserializationLogic(JavaBlock deserializationBlock, Cl
deserializationBlock.line(property.getName() + " = reader.readUntyped();");
}
} else if (wireType instanceof IterableType) {
final String propertyStringVariableName = property.getName() + "EncodedAsString";
IType wireElementType = ((IterableType) wireType).getElementType();
if (property.getArrayEncoding() != null) {
// need to prepare the expression for propertyStringVariableName,
// to be used in "if (property.getArrayEncoding() == null)" block
deserializationBlock.line("String " + propertyStringVariableName + " = reader.getString();");
}

if (!propertiesManager.hasConstructorArguments()) {
deserializationBlock.text(property.getClientType() + " ");
Expand All @@ -1640,21 +1635,38 @@ private void generateJsonDeserializationLogic(JavaBlock deserializationBlock, Cl
deserializeJsonContainerProperty(deserializationBlock, "readArray", wireType, wireElementType,
((IterableType) clientType).getElementType(), 0);
} else {
final String propertyStringVariableName = property.getName() + "EncodedAsString";
// LinkedList is used to be consistent with internal code of core, e.g. "readArray" API
if (wireElementType == ClassType.STRING) {
// wireType is String
deserializationBlock.line(
"%1$s == null ? null : %1$s.isEmpty() ? new LinkedList<>() : new LinkedList<>(Arrays.asList(%1$s.split(\"%2$s\", -1)));",
propertyStringVariableName, property.getArrayEncoding().getEscapedDelimiter());
deserializationBlock.line("reader.getNullable(nonNullReader -> {");
deserializationBlock.indent(() -> {
deserializationBlock.line("String %1$s = nonNullReader.getString();",
propertyStringVariableName);
deserializationBlock.line(
"return %1$s.isEmpty() ? new LinkedList<>() : new LinkedList<>(Arrays.asList(%1$s.split(\"%2$s\", -1)));",
propertyStringVariableName, property.getArrayEncoding().getEscapedDelimiter());
});
deserializationBlock.line("});");
} else {
// wireType need to be converted from String
String conversionExpress = wireElementType.defaultValueExpression("valueAsString")
.replace("\"valueAsString\"", "valueAsString");
deserializationBlock.line(
"%1$s == null ? null : %1$s.isEmpty() ? new LinkedList<>() : new LinkedList<>(Arrays.stream(%1$s.split(\"%2$s\", -1)).map(valueAsString -> %3$s).collect(Collectors.toList()));",
propertyStringVariableName, property.getArrayEncoding().getEscapedDelimiter(),
conversionExpress);

if (wireElementType instanceof ConvertFromJsonTypeTrait) {
String conversionExpress
= ((ConvertFromJsonTypeTrait) wireElementType).convertFromJsonType("valueAsString");
deserializationBlock.line("reader.getNullable(nonNullReader -> {");
deserializationBlock.indent(() -> {
deserializationBlock.line("String %1$s = nonNullReader.getString();",
propertyStringVariableName);
deserializationBlock.line(
"%1$s.isEmpty() ? new LinkedList<>() : new LinkedList<>(Arrays.stream(%1$s.split(\"%2$s\", -1)).map(valueAsString -> %3$s).collect(Collectors.toList()));",
propertyStringVariableName, property.getArrayEncoding().getEscapedDelimiter(),
conversionExpress);
});
deserializationBlock.line("});");
} else {
throw new RuntimeException("Unable to convert type " + wireElementType
+ " from String for ArrayEncoding serialization.");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.typespec.http.client.generator.core.model.clientmodel;

import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class ConvertFromJsonTypeTraitTests {

@Test
public void testPrimitiveTypeConversion() {
Assertions.assertEquals("var", PrimitiveType.INT.convertFromJsonType("var"));
Assertions.assertEquals("var == null ? null : Integer.parseInt(var)",
PrimitiveType.INT_AS_STRING.convertFromJsonType("var"));
Assertions.assertEquals("Duration.ofNanos((long) (var * 1000_000_000L))",
PrimitiveType.DURATION_DOUBLE.convertFromJsonType("var"));
Assertions.assertEquals("OffsetDateTime.ofInstant(Instant.ofEpochSecond(var), ZoneOffset.UTC)",
PrimitiveType.UNIX_TIME_LONG.convertFromJsonType("var"));
}

@Test
public void testClassTypeConversion() {
Assertions.assertEquals("var", ClassType.LONG.convertFromJsonType("var"));
Assertions.assertEquals("var == null ? null : new DateTimeRfc1123(var)",
ClassType.DATE_TIME_RFC_1123.convertFromJsonType("var"));
Assertions.assertEquals("var == null ? null : OffsetDateTime.parse(var)",
ClassType.DATE_TIME.convertFromJsonType("var"));
Assertions.assertEquals("var == null ? null : Duration.parse(var)",
ClassType.DURATION.convertFromJsonType("var"));
Assertions.assertEquals("Duration.ofSeconds(var)", ClassType.DURATION_LONG.convertFromJsonType("var"));
}

@Test
public void testEnumConversion() {
ClientEnumValue enumValue = new ClientEnumValue("VALUE_ONE", "1", "ValueOne");

EnumType.Builder enumTypeBuilder = new EnumType.Builder().name("SampleEnum")
.elementType(ClassType.STRING)
.values(List.of(enumValue))
.expandable(true);

Assertions.assertEquals("var == null ? null : SampleEnum.fromString(var)",
enumTypeBuilder.build().convertFromJsonType("var"));

enumTypeBuilder.expandable(false);
Assertions.assertEquals("var == null ? null : SampleEnum.fromString(var)",
enumTypeBuilder.build().convertFromJsonType("var"));

enumTypeBuilder.elementType(ClassType.INTEGER);
Assertions.assertEquals("var == null ? null : SampleEnum.fromInteger(var)",
enumTypeBuilder.build().convertFromJsonType("var"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.typespec.http.client.generator.core.model.clientmodel;

import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class ConvertToJsonTypeTraitTests {

@Test
public void testPrimitiveTypeConversion() {
Assertions.assertEquals("var", PrimitiveType.INT.convertToJsonType("var"));
Assertions.assertEquals("Objects.toString(var, null)", PrimitiveType.INT_AS_STRING.convertToJsonType("var"));
Assertions.assertEquals("(double) var.toNanos() / 1000_000_000L",
PrimitiveType.DURATION_DOUBLE.convertToJsonType("var"));
}

@Test
public void testClassTypeConversion() {
Assertions.assertEquals("var", ClassType.LONG.convertToJsonType("var"));
Assertions.assertEquals("Objects.toString(new DateTimeRfc1123(var), null)",
ClassType.DATE_TIME_RFC_1123.convertToJsonType("var"));
Assertions.assertEquals("var == null ? null : DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(var)",
ClassType.DATE_TIME.convertToJsonType("var"));
Assertions.assertEquals("CoreUtils.durationToStringWithDays(var)", ClassType.DURATION.convertToJsonType("var"));
}

@Test
public void testEnumConversion() {
ClientEnumValue enumValue = new ClientEnumValue("VALUE_ONE", "ValueOne", "ValueOne");

EnumType.Builder enumTypeBuilder = new EnumType.Builder().name("SampleEnum")
.elementType(ClassType.STRING)
.values(List.of(enumValue))
.expandable(true);

Assertions.assertEquals("var == null ? null : var.toString()",
enumTypeBuilder.build().convertToJsonType("var"));

enumTypeBuilder.expandable(false);
Assertions.assertEquals("var == null ? null : var.toString()",
enumTypeBuilder.build().convertToJsonType("var"));

enumTypeBuilder.elementType(ClassType.INTEGER);
Assertions.assertEquals("var == null ? null : var.toInteger()",
enumTypeBuilder.build().convertToJsonType("var"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ public static CommaDelimitedArrayProperty fromJson(JsonReader jsonReader) throws
reader.nextToken();

if ("value".equals(fieldName)) {
String valueEncodedAsString = reader.getString();
value = valueEncodedAsString == null
? null
: valueEncodedAsString.isEmpty()
value = reader.getNullable(nonNullReader -> {
String valueEncodedAsString = nonNullReader.getString();
return valueEncodedAsString.isEmpty()
? new LinkedList<>()
: new LinkedList<>(Arrays.asList(valueEncodedAsString.split(",", -1)));
});
} else {
reader.skipChildren();
}
Expand Down
Loading
Loading