Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2339,8 +2339,10 @@ Type t when ValueTypeIsInt(t) =>
Type t when t == typeof(TimeSpan) => format switch
{
SerializationFormat.Duration_Seconds => TimeSpanSnippets.FromSeconds(element.GetInt32()),
SerializationFormat.Duration_Seconds_Int64 => TimeSpanSnippets.FromSeconds(element.GetInt64()),
SerializationFormat.Duration_Seconds_Float or SerializationFormat.Duration_Seconds_Double => TimeSpanSnippets.FromSeconds(element.GetDouble()),
SerializationFormat.Duration_Milliseconds => TimeSpanSnippets.FromMilliseconds(element.GetInt32()),
SerializationFormat.Duration_Milliseconds_Int64 => TimeSpanSnippets.FromMilliseconds(element.GetInt64()),
SerializationFormat.Duration_Milliseconds_Float or SerializationFormat.Duration_Milliseconds_Double => TimeSpanSnippets.FromMilliseconds(element.GetDouble()),
_ => element.GetTimeSpan(format.ToFormatSpecifier())
},
Expand Down Expand Up @@ -2397,9 +2399,11 @@ private static MethodBodyStatement SerializeDateTimeRelatedTypes(Type valueType,
var format = serializationFormat.ToFormatSpecifier();
return serializationFormat switch
{
SerializationFormat.Duration_Seconds => utf8JsonWriter.WriteNumberValue(ConvertSnippets.InvokeToInt32(value.As<TimeSpan>().TotalSeconds())),
SerializationFormat.Duration_Seconds => utf8JsonWriter.WriteNumberValue(ConvertSnippets.InvokeToInt32(MathSnippets.InvokeRound(value.As<TimeSpan>().TotalSeconds()))),
SerializationFormat.Duration_Seconds_Int64 => utf8JsonWriter.WriteNumberValue(ConvertSnippets.InvokeToInt64(MathSnippets.InvokeRound(value.As<TimeSpan>().TotalSeconds()))),
SerializationFormat.Duration_Seconds_Float or SerializationFormat.Duration_Seconds_Double => utf8JsonWriter.WriteNumberValue(value.As<TimeSpan>().TotalSeconds()),
SerializationFormat.Duration_Milliseconds => utf8JsonWriter.WriteNumberValue(ConvertSnippets.InvokeToInt32(value.As<TimeSpan>().TotalMilliseconds())),
SerializationFormat.Duration_Milliseconds => utf8JsonWriter.WriteNumberValue(ConvertSnippets.InvokeToInt32(MathSnippets.InvokeRound(value.As<TimeSpan>().TotalMilliseconds()))),
SerializationFormat.Duration_Milliseconds_Int64 => utf8JsonWriter.WriteNumberValue(ConvertSnippets.InvokeToInt64(MathSnippets.InvokeRound(value.As<TimeSpan>().TotalMilliseconds()))),
SerializationFormat.Duration_Milliseconds_Float or SerializationFormat.Duration_Milliseconds_Double => utf8JsonWriter.WriteNumberValue(value.As<TimeSpan>().TotalMilliseconds()),
SerializationFormat.DateTime_Unix => utf8JsonWriter.WriteNumberValue(value, format),
_ => format is not null ? utf8JsonWriter.WriteStringValue(value, format) : utf8JsonWriter.WriteStringValue(value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public SerializationFormatDefinition() : base(null!)
public const string Duration_Milliseconds = "Duration_Milliseconds";
public const string Duration_Milliseconds_Float = "Duration_Milliseconds_Float";
public const string Duration_Milliseconds_Double = "Duration_Milliseconds_Double";
public const string Duration_Seconds_Int64 = "Duration_Seconds_Int64";
public const string Duration_Milliseconds_Int64 = "Duration_Milliseconds_Int64";
public const string Time_ISO8601 = "Time_ISO8601";
public const string Bytes_Base64Url = "Bytes_Base64Url";
public const string Bytes_Base64 = "Bytes_Base64";
Expand Down Expand Up @@ -68,6 +70,8 @@ protected override IReadOnlyList<EnumTypeMember> BuildEnumValues()
(Time_ISO8601, 15, "The ISO8601 time format."),
(Bytes_Base64Url, 16, "The Base64Url bytes format."),
(Bytes_Base64, 17 , "The Base64 bytes format."),
(Duration_Seconds_Int64, 18, "The seconds duration format with int64 precision."),
(Duration_Milliseconds_Int64, 19, "The milliseconds duration format with int64 precision."),
};

var members = new EnumTypeMember[enumValues.Count];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,19 @@ private ValueExpression BuildSwitchExpression(ValueExpression value, ValueExpres
// Special handling for TimeSpan with duration seconds/milliseconds encoding
SwitchCaseExpression.When(new DeclarationExpression(typeof(TimeSpan), "timeSpan", out var timeSpanSeconds),
formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Seconds")),
ConvertSnippets.InvokeToInt32(timeSpanSeconds.As<TimeSpan>().TotalSeconds()).InvokeToString(invariantCulture)),
ConvertSnippets.InvokeToInt32(MathSnippets.InvokeRound(timeSpanSeconds.As<TimeSpan>().TotalSeconds())).InvokeToString(invariantCulture)),
SwitchCaseExpression.When(new DeclarationExpression(typeof(TimeSpan), "timeSpan", out var timeSpanSecondsLong),
formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Seconds_Int64")),
ConvertSnippets.InvokeToInt64(MathSnippets.InvokeRound(timeSpanSecondsLong.As<TimeSpan>().TotalSeconds())).InvokeToString(invariantCulture)),
SwitchCaseExpression.When(new DeclarationExpression(typeof(TimeSpan), "timeSpan", out var timeSpanSecondsFloat),
formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Seconds_Float")).Or(formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Seconds_Double"))),
timeSpanSecondsFloat.As<TimeSpan>().TotalSeconds().InvokeToString(invariantCulture)),
SwitchCaseExpression.When(new DeclarationExpression(typeof(TimeSpan), "timeSpan", out var timeSpanMilliseconds),
formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Milliseconds")),
ConvertSnippets.InvokeToInt32(timeSpanMilliseconds.As<TimeSpan>().TotalMilliseconds()).InvokeToString(invariantCulture)),
ConvertSnippets.InvokeToInt32(MathSnippets.InvokeRound(timeSpanMilliseconds.As<TimeSpan>().TotalMilliseconds())).InvokeToString(invariantCulture)),
SwitchCaseExpression.When(new DeclarationExpression(typeof(TimeSpan), "timeSpan", out var timeSpanMillisecondsLong),
formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Milliseconds_Int64")),
ConvertSnippets.InvokeToInt64(MathSnippets.InvokeRound(timeSpanMillisecondsLong.As<TimeSpan>().TotalMilliseconds())).InvokeToString(invariantCulture)),
SwitchCaseExpression.When(new DeclarationExpression(typeof(TimeSpan), "timeSpan", out var timeSpanMillisecondsFloat),
formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Milliseconds_Float")).Or(formatParameter.Equal(new MemberExpression(serializationFormatType, "Duration_Milliseconds_Double"))),
timeSpanMillisecondsFloat.As<TimeSpan>().TotalMilliseconds().InvokeToString(invariantCulture)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,32 @@ public void TestDeserializationUsesUriKindRelativeOrAbsolute()
Assert.IsTrue(methodBody.Contains("global::System.UriKind.RelativeOrAbsolute"),
$"Uri property should specify UriKind.RelativeOrAbsolute. Actual:\n{methodBody}");
}

// Validates that duration properties encoded as integer milliseconds are deserialized
// from an integer JSON value (GetInt32 for Int32 wire type, GetInt64 for larger integer kinds).
[TestCase(nameof(InputPrimitiveType.Int32))]
[TestCase(nameof(InputPrimitiveType.Int64))]
[TestCase(nameof(InputPrimitiveTypeKind.Integer))]
public void TestDeserializationOfDurationMillisecondsIntegerWireType(string wireKindName)
{
var wireType = wireKindName switch
{
nameof(InputPrimitiveType.Int32) => InputPrimitiveType.Int32,
nameof(InputPrimitiveType.Int64) => InputPrimitiveType.Int64,
nameof(InputPrimitiveTypeKind.Integer) => new InputPrimitiveType(InputPrimitiveTypeKind.Integer, "integer", "TypeSpec.integer"),
_ => throw new ArgumentException(wireKindName),
};
var durationType = new InputDurationType(DurationKnownEncoding.Milliseconds, "duration", "TypeSpec.duration", wireType, null);
var inputModel = InputFactory.Model(
"TestModel",
properties: [InputFactory.Property("audio_end_ms", durationType, wireName: "audio_end_ms", isRequired: true)]);

var mrwProvider = new ModelProvider(inputModel).SerializationProviders.FirstOrDefault();
Assert.IsNotNull(mrwProvider);

var writer = new TypeProviderWriter(mrwProvider!);
var file = writer.Write();
Assert.AreEqual(Helpers.GetExpectedFromFile(parameters: wireKindName), file.Content);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,53 @@ public void NonBodyPropertyKindsInModel()
var file = writer.Write();
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
}

// Validates that duration properties encoded as integer milliseconds/seconds are always
// serialized as integers, regardless of the wire-type integer kind (e.g. integer, int64, etc.).
[TestCase(nameof(InputPrimitiveType.Int32))]
[TestCase(nameof(InputPrimitiveType.Int64))]
[TestCase(nameof(InputPrimitiveTypeKind.Integer))]
public void DurationMillisecondsIntegerWireTypeWritesAsInt(string wireKindName)
{
var wireType = wireKindName switch
{
nameof(InputPrimitiveType.Int32) => InputPrimitiveType.Int32,
nameof(InputPrimitiveType.Int64) => InputPrimitiveType.Int64,
nameof(InputPrimitiveTypeKind.Integer) => new InputPrimitiveType(InputPrimitiveTypeKind.Integer, "integer", "TypeSpec.integer"),
_ => throw new System.ArgumentException(wireKindName),
};
var durationType = new InputDurationType(DurationKnownEncoding.Milliseconds, "duration", "TypeSpec.duration", wireType, null);
var inputModel = InputFactory.Model(
"TestModel",
properties: [InputFactory.Property("audio_end_ms", durationType, wireName: "audio_end_ms", isRequired: true)]);

var mrwProvider = new ModelProvider(inputModel).SerializationProviders.First();
var writer = new TypeProviderWriter(mrwProvider);
var file = writer.Write();
Assert.AreEqual(Helpers.GetExpectedFromFile(parameters: wireKindName), file.Content);
}

// Validates that duration properties encoded as float/double milliseconds preserve the
// floating-point value (no integer rounding).
[TestCase(nameof(InputPrimitiveType.Float32))]
[TestCase(nameof(InputPrimitiveType.Float64))]
public void DurationMillisecondsFloatWireTypeWritesAsDouble(string wireKindName)
{
var wireType = wireKindName switch
{
nameof(InputPrimitiveType.Float32) => InputPrimitiveType.Float32,
nameof(InputPrimitiveType.Float64) => InputPrimitiveType.Float64,
_ => throw new System.ArgumentException(wireKindName),
};
var durationType = new InputDurationType(DurationKnownEncoding.Milliseconds, "duration", "TypeSpec.duration", wireType, null);
var inputModel = InputFactory.Model(
"TestModel",
properties: [InputFactory.Property("audio_end_ms", durationType, wireName: "audio_end_ms", isRequired: true)]);

var mrwProvider = new ModelProvider(inputModel).SerializationProviders.First();
var writer = new TypeProviderWriter(mrwProvider);
var file = writer.Write();
Assert.AreEqual(Helpers.GetExpectedFromFile(parameters: wireKindName), file.Content);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -897,9 +897,11 @@ public string TestIntDeserializeExpression(Type type, SerializationFormat format
}

[TestCase(SerializationFormat.Duration_Seconds, ExpectedResult = "global::System.TimeSpan.FromSeconds(foo.GetInt32())")]
[TestCase(SerializationFormat.Duration_Seconds_Int64, ExpectedResult = "global::System.TimeSpan.FromSeconds(foo.GetInt64())")]
[TestCase(SerializationFormat.Duration_Seconds_Float, ExpectedResult = "global::System.TimeSpan.FromSeconds(foo.GetDouble())")]
[TestCase(SerializationFormat.Duration_Seconds_Double, ExpectedResult = "global::System.TimeSpan.FromSeconds(foo.GetDouble())")]
[TestCase(SerializationFormat.Duration_Milliseconds, ExpectedResult = "global::System.TimeSpan.FromMilliseconds(foo.GetInt32())")]
[TestCase(SerializationFormat.Duration_Milliseconds_Int64, ExpectedResult = "global::System.TimeSpan.FromMilliseconds(foo.GetInt64())")]
[TestCase(SerializationFormat.Duration_Milliseconds_Float, ExpectedResult = "global::System.TimeSpan.FromMilliseconds(foo.GetDouble())")]
[TestCase(SerializationFormat.Duration_Milliseconds_Double, ExpectedResult = "global::System.TimeSpan.FromMilliseconds(foo.GetDouble())")]
[TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "foo.GetTimeSpan(\"P\")")]
Expand All @@ -915,10 +917,12 @@ public string TestTimeSpanDeserializeExpression(SerializationFormat format)
return expr.ToDisplayString();
}

[TestCase(SerializationFormat.Duration_Seconds, ExpectedResult = "writer.WriteNumberValue(global::System.Convert.ToInt32(value.TotalSeconds));\n")]
[TestCase(SerializationFormat.Duration_Seconds, ExpectedResult = "writer.WriteNumberValue(global::System.Convert.ToInt32(global::System.Math.Round(value.TotalSeconds)));\n")]
[TestCase(SerializationFormat.Duration_Seconds_Int64, ExpectedResult = "writer.WriteNumberValue(global::System.Convert.ToInt64(global::System.Math.Round(value.TotalSeconds)));\n")]
[TestCase(SerializationFormat.Duration_Seconds_Float, ExpectedResult = "writer.WriteNumberValue(value.TotalSeconds);\n")]
[TestCase(SerializationFormat.Duration_Seconds_Double, ExpectedResult = "writer.WriteNumberValue(value.TotalSeconds);\n")]
[TestCase(SerializationFormat.Duration_Milliseconds, ExpectedResult = "writer.WriteNumberValue(global::System.Convert.ToInt32(value.TotalMilliseconds));\n")]
[TestCase(SerializationFormat.Duration_Milliseconds, ExpectedResult = "writer.WriteNumberValue(global::System.Convert.ToInt32(global::System.Math.Round(value.TotalMilliseconds)));\n")]
[TestCase(SerializationFormat.Duration_Milliseconds_Int64, ExpectedResult = "writer.WriteNumberValue(global::System.Convert.ToInt64(global::System.Math.Round(value.TotalMilliseconds)));\n")]
[TestCase(SerializationFormat.Duration_Milliseconds_Float, ExpectedResult = "writer.WriteNumberValue(value.TotalMilliseconds);\n")]
[TestCase(SerializationFormat.Duration_Milliseconds_Double, ExpectedResult = "writer.WriteNumberValue(value.TotalMilliseconds);\n")]
[TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "writer.WriteStringValue(value, \"P\");\n")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Text.Json;
using Sample;

namespace Sample.Models
{
public partial class TestModel : global::System.ClientModel.Primitives.IJsonModel<global::Sample.Models.TestModel>
{
internal static global::Sample.Models.TestModel DeserializeTestModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null))
{
return null;
}
global::System.TimeSpan audioEndMs = default;
global::System.Collections.Generic.IDictionary<string, global::System.BinaryData> additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary<string, global::System.BinaryData>();
foreach (var prop in element.EnumerateObject())
{
if (prop.NameEquals("audio_end_ms"u8))
{
audioEndMs = global::System.TimeSpan.FromMilliseconds(prop.Value.GetInt32());
continue;
}
if ((options.Format != "W"))
{
additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText()));
}
}
return new global::Sample.Models.TestModel(audioEndMs, additionalBinaryDataProperties);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Text.Json;
using Sample;

namespace Sample.Models
{
public partial class TestModel : global::System.ClientModel.Primitives.IJsonModel<global::Sample.Models.TestModel>
{
internal static global::Sample.Models.TestModel DeserializeTestModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null))
{
return null;
}
global::System.TimeSpan audioEndMs = default;
global::System.Collections.Generic.IDictionary<string, global::System.BinaryData> additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary<string, global::System.BinaryData>();
foreach (var prop in element.EnumerateObject())
{
if (prop.NameEquals("audio_end_ms"u8))
{
audioEndMs = global::System.TimeSpan.FromMilliseconds(prop.Value.GetInt64());
continue;
}
if ((options.Format != "W"))
{
additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText()));
}
}
return new global::Sample.Models.TestModel(audioEndMs, additionalBinaryDataProperties);
}
}
}
Loading
Loading