diff --git a/.chronus/changes/http-server-csharp-converters-on-ef-2026-8-5.md b/.chronus/changes/http-server-csharp-converters-on-ef-2026-8-5.md new file mode 100644 index 00000000000..bb40d882404 --- /dev/null +++ b/.chronus/changes/http-server-csharp-converters-on-ef-2026-8-5.md @@ -0,0 +1,9 @@ +--- +changeKind: internal +packages: + - "@typespec/http-server-csharp" +--- + +Generate the JSON converter helpers with the emitter framework's `JsonConverter` component + +`TimeSpanDurationConverter`, `Base64UrlJsonConverter` and the Unix epoch converters were raw string templates. The generated classes are unchanged apart from an explicit `using System;` that was previously implicit. diff --git a/packages/http-server-csharp/src/components/serialization/base64-url-json-converter.tsx b/packages/http-server-csharp/src/components/serialization/base64-url-json-converter.tsx index c90d6dd2dd6..ef1391f86c3 100644 --- a/packages/http-server-csharp/src/components/serialization/base64-url-json-converter.tsx +++ b/packages/http-server-csharp/src/components/serialization/base64-url-json-converter.tsx @@ -1,5 +1,6 @@ import { code, type Children } from "@alloy-js/core"; -import { Namespace } from "@alloy-js/csharp"; +import { DocSummary, Namespace } from "@alloy-js/csharp"; +import { JsonConverter } from "@typespec/emitter-framework/csharp"; import { CSharpFile } from "../csharp-file.jsx"; /** @@ -8,17 +9,32 @@ import { CSharpFile } from "../csharp-file.jsx"; */ export function Base64UrlJsonConverter(): Children { return ( - + - {code` - /// - /// System.Text.Json converter for the properties using Base64Url encoding - /// - public class Base64UrlJsonConverter : JsonConverter - { + + System.Text.Json converter for the properties using Base64Url encoding + + } + readReturns="byte[]?" + decodeAndReturn={(reader, typeToConvert) => code` + if (${typeToConvert} != typeof(byte[])) + throw new ArgumentException($"Cannot apply converter {this.GetType().FullName} to type {${typeToConvert}.FullName}"); + var value = ${reader}.GetString(); + if (string.IsNullOrWhiteSpace(value)) + return null; + return Convert.FromBase64String(Pad(value.Replace('-', '+').Replace('_', '/'))); + `} + encodeAndWrite={(writer, value) => code` + ${writer}.WriteStringValue(Convert.ToBase64String(${value}).TrimEnd('=').Replace('+', '-').Replace('/', '_')); + `} + > + {code` /// /// Adds padding to the input /// @@ -26,30 +42,15 @@ export function Base64UrlJsonConverter(): Children { /// the padded string private static string Pad(string input) { - var count = 3 - ((input.Length + 3) % 4); - if (count == 0) - { - return input; - } - return $"{input}{new string('=', count)}"; - } - - public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (typeToConvert != typeof(byte[])) - throw new ArgumentException($"Cannot apply converter {this.GetType().FullName} to type {typeToConvert.FullName}"); - var value = reader.GetString(); - if (string.IsNullOrWhiteSpace(value)) - return null; - return Convert.FromBase64String(Pad(value.Replace('-', '+').Replace('_', '/'))); + var count = 3 - ((input.Length + 3) % 4); + if (count == 0) + { + return input; + } + return $"{input}{new string('=', count)}"; } - - public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) - { - writer.WriteStringValue(Convert.ToBase64String(value).TrimEnd('=').Replace('+', '-').Replace('/', '_')); - } - } - `} + `} + ); diff --git a/packages/http-server-csharp/src/components/serialization/time-span-duration-converter.tsx b/packages/http-server-csharp/src/components/serialization/time-span-duration-converter.tsx index b38dd602aab..6a607de3a20 100644 --- a/packages/http-server-csharp/src/components/serialization/time-span-duration-converter.tsx +++ b/packages/http-server-csharp/src/components/serialization/time-span-duration-converter.tsx @@ -1,5 +1,6 @@ import { code, type Children } from "@alloy-js/core"; -import { Namespace } from "@alloy-js/csharp"; +import { DocSummary, Namespace } from "@alloy-js/csharp"; +import { JsonConverter } from "@typespec/emitter-framework/csharp"; import { CSharpFile } from "../csharp-file.jsx"; /** @@ -8,34 +9,27 @@ import { CSharpFile } from "../csharp-file.jsx"; */ export function TimeSpanDurationConverter(): Children { return ( - + - {code` - /// - /// Converts between Json duration and .Net TimeSpan - /// - public class TimeSpanDurationConverter : JsonConverter - { - public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (typeToConvert != typeof(TimeSpan)) - throw new ArgumentException($"Cannot apply converter {this.GetType().FullName} to type {typeToConvert.FullName}"); + Converts between Json duration and .Net TimeSpan} + decodeAndReturn={(reader, typeToConvert) => code` + if (${typeToConvert} != typeof(TimeSpan)) + throw new ArgumentException($"Cannot apply converter {this.GetType().FullName} to type {${typeToConvert}.FullName}"); - var value = reader.GetString(); - if (string.IsNullOrWhiteSpace(value)) - return TimeSpan.MinValue; - return XmlConvert.ToTimeSpan(value); - } - - public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) - { - writer.WriteStringValue(XmlConvert.ToString(value)); - } - } - `} + var value = ${reader}.GetString(); + if (string.IsNullOrWhiteSpace(value)) + return TimeSpan.MinValue; + return XmlConvert.ToTimeSpan(value); + `} + encodeAndWrite={(writer, value) => code` + ${writer}.WriteStringValue(XmlConvert.ToString(${value})); + `} + /> ); diff --git a/packages/http-server-csharp/src/components/serialization/unix-epoch-date-time-converter.tsx b/packages/http-server-csharp/src/components/serialization/unix-epoch-date-time-converter.tsx index 25e394684cf..df6bc85dc2b 100644 --- a/packages/http-server-csharp/src/components/serialization/unix-epoch-date-time-converter.tsx +++ b/packages/http-server-csharp/src/components/serialization/unix-epoch-date-time-converter.tsx @@ -1,5 +1,6 @@ import { code, type Children } from "@alloy-js/core"; -import { Namespace } from "@alloy-js/csharp"; +import { DocSummary, Namespace } from "@alloy-js/csharp"; +import { JsonConverter } from "@typespec/emitter-framework/csharp"; import { CSharpFile } from "../csharp-file.jsx"; /** @@ -9,60 +10,48 @@ import { CSharpFile } from "../csharp-file.jsx"; export function UnixEpochDateTimeConverter(): Children { return ( <> - + - {code` - /// - /// Converts between an integer timestamp and a .Net DateTime - /// - public sealed class UnixEpochDateTimeConverter : JsonConverter - { - static readonly DateTime s_epoch = new DateTime(1970, 1, 1, 0, 0, 0); - - public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var formatted = reader.GetInt64()!; - return s_epoch.AddMilliseconds(formatted); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds); - writer.WriteNumberValue(unixTime); - } - } - `} + Converts between an integer timestamp and a .Net DateTime} + decodeAndReturn={(reader) => code` + var formatted = ${reader}.GetInt64()!; + return s_epoch.AddMilliseconds(formatted); + `} + encodeAndWrite={(writer, value) => code` + long unixTime = Convert.ToInt64((${value} - s_epoch).TotalMilliseconds); + ${writer}.WriteNumberValue(unixTime); + `} + > + {code`static readonly DateTime s_epoch = new DateTime(1970, 1, 1, 0, 0, 0);`} + - + - {code` - /// - /// Converts between a Unix TimeStamp and a .Net DateTimeOffset - /// - public sealed class UnixEpochDateTimeOffsetConverter : JsonConverter - { - static readonly DateTimeOffset s_epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); - - public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var formatted = reader.GetInt64()!; - return s_epoch.AddMilliseconds(formatted); - } - - public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) - { - long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds); - writer.WriteNumberValue(unixTime); - } + Converts between a Unix TimeStamp and a .Net DateTimeOffset } - `} + decodeAndReturn={(reader) => code` + var formatted = ${reader}.GetInt64()!; + return s_epoch.AddMilliseconds(formatted); + `} + encodeAndWrite={(writer, value) => code` + long unixTime = Convert.ToInt64((${value} - s_epoch).TotalMilliseconds); + ${writer}.WriteNumberValue(unixTime); + `} + > + {code`static readonly DateTimeOffset s_epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);`} + diff --git a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/Base64UrlJsonConverter.cs b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/Base64UrlJsonConverter.cs index 31b7a3320aa..b391a1bfc9f 100644 --- a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/Base64UrlJsonConverter.cs +++ b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/Base64UrlJsonConverter.cs @@ -2,6 +2,7 @@ // #nullable enable +using System; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/TimeSpanDurationConverter.cs b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/TimeSpanDurationConverter.cs index 78ea0937205..0202505f313 100644 --- a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/TimeSpanDurationConverter.cs +++ b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/TimeSpanDurationConverter.cs @@ -2,6 +2,7 @@ // #nullable enable +using System; using System.Text.Json; using System.Text.Json.Serialization; using System.Xml; diff --git a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeConverter.cs b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeConverter.cs index 1706e2aa02c..f04a52c2b09 100644 --- a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeConverter.cs +++ b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeConverter.cs @@ -2,6 +2,7 @@ // #nullable enable +using System; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeOffsetConverter.cs b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeOffsetConverter.cs index c9bddfa3343..fee5f55fb7c 100644 --- a/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeOffsetConverter.cs +++ b/packages/http-server-csharp/test/snapshots/sample-service/generated/lib/UnixEpochDateTimeOffsetConverter.cs @@ -2,6 +2,7 @@ // #nullable enable +using System; using System.Text.Json; using System.Text.Json.Serialization; @@ -14,7 +15,11 @@ public sealed class UnixEpochDateTimeOffsetConverter : JsonConverter