diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/JsonSerialization/AzureStorageJsonUtilities.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/JsonSerialization/AzureStorageJsonUtilities.cs new file mode 100644 index 00000000000..23b2ae9c88c --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/JsonSerialization/AzureStorageJsonUtilities.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using static Microsoft.Extensions.AI.Evaluation.Reporting.Storage.AzureStorageResponseCache; + +namespace Microsoft.Extensions.AI.Evaluation.Reporting.JsonSerialization; + +internal static partial class AzureStorageJsonUtilities +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Default matches the generated source naming convention.")] + internal static class Default + { + private static JsonSerializerOptions? _options; + internal static JsonSerializerOptions Options => _options ??= CreateJsonSerializerOptions(writeIndented: true); + internal static JsonTypeInfo CacheEntryTypeInfo => Options.GetTypeInfo(); + internal static JsonTypeInfo ScenarioRunResultTypeInfo => Options.GetTypeInfo(); + } + + internal static class Compact + { + private static JsonSerializerOptions? _options; + internal static JsonSerializerOptions Options => _options ??= CreateJsonSerializerOptions(writeIndented: false); + internal static JsonTypeInfo ScenarioRunResultTypeInfo => Options.GetTypeInfo(); + } + + private static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options) => (JsonTypeInfo)options.GetTypeInfo(typeof(T)); + + private static JsonSerializerOptions CreateJsonSerializerOptions(bool writeIndented) + { + var options = new JsonSerializerOptions(JsonContext.Default.Options) + { + WriteIndented = writeIndented, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + options.TypeInfoResolverChain.Add(AIJsonUtilities.DefaultOptions.TypeInfoResolver!); + options.MakeReadOnly(); + return options; + } + + [JsonSerializable(typeof(ScenarioRunResult))] + [JsonSerializable(typeof(CacheEntry))] + [JsonSourceGenerationOptions( + Converters = [ + typeof(AzureStorageCamelCaseEnumConverter), + typeof(AzureStorageCamelCaseEnumConverter), + typeof(AzureStorageTimeSpanConverter) + ], + WriteIndented = true, + IgnoreReadOnlyProperties = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] + private sealed partial class JsonContext : JsonSerializerContext; + +} diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/JsonSerialization/AzureStorageSerializerContext.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/JsonSerialization/AzureStorageSerializerContext.cs deleted file mode 100644 index 9e6dfc72224..00000000000 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/JsonSerialization/AzureStorageSerializerContext.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json; -using System.Text.Json.Serialization; -using static Microsoft.Extensions.AI.Evaluation.Reporting.Storage.AzureStorageResponseCache; - -namespace Microsoft.Extensions.AI.Evaluation.Reporting.JsonSerialization; - -[JsonSerializable(typeof(ScenarioRunResult))] -[JsonSerializable(typeof(CacheEntry))] -[JsonSourceGenerationOptions( - Converters = [ - typeof(AzureStorageCamelCaseEnumConverter), - typeof(AzureStorageCamelCaseEnumConverter), - typeof(AzureStorageTimeSpanConverter)], - WriteIndented = true, - IgnoreReadOnlyProperties = false, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] -internal sealed partial class AzureStorageSerializerContext : JsonSerializerContext -{ - private static AzureStorageSerializerContext? _compact; - - internal static AzureStorageSerializerContext Compact => - _compact ??= - new(new JsonSerializerOptions(Default.Options) - { - WriteIndented = false, - }); -} diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResponseCache.CacheEntry.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResponseCache.CacheEntry.cs index 8c6153334f8..c165148c91d 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResponseCache.CacheEntry.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResponseCache.CacheEntry.cs @@ -45,7 +45,7 @@ public static CacheEntry Read( CacheEntry cacheEntry = JsonSerializer.Deserialize( content.Value.Content.ToMemory().Span, - AzureStorageSerializerContext.Default.CacheEntry) + AzureStorageJsonUtilities.Default.CacheEntryTypeInfo) ?? throw new JsonException( string.Format(CultureInfo.CurrentCulture, DeserializationFailedMessage, fileClient.Name)); @@ -62,7 +62,7 @@ public static async Task ReadAsync( CacheEntry cacheEntry = await JsonSerializer.DeserializeAsync( content.Value.Content.ToStream(), - AzureStorageSerializerContext.Default.CacheEntry, + AzureStorageJsonUtilities.Default.CacheEntryTypeInfo, cancellationToken).ConfigureAwait(false) ?? throw new JsonException( string.Format(CultureInfo.CurrentCulture, DeserializationFailedMessage, fileClient.Name)); @@ -76,7 +76,7 @@ public void Write( { MemoryStream stream = new(); - JsonSerializer.Serialize(stream, this, AzureStorageSerializerContext.Default.CacheEntry); + JsonSerializer.Serialize(stream, this, AzureStorageJsonUtilities.Default.CacheEntryTypeInfo); _ = stream.Seek(0, SeekOrigin.Begin); _ = fileClient.Upload(stream, overwrite: true, cancellationToken); @@ -91,7 +91,7 @@ public async Task WriteAsync( await JsonSerializer.SerializeAsync( stream, this, - AzureStorageSerializerContext.Default.CacheEntry, + AzureStorageJsonUtilities.Default.CacheEntryTypeInfo, cancellationToken).ConfigureAwait(false); _ = stream.Seek(0, SeekOrigin.Begin); diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResultStore.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResultStore.cs index fe1a3d91a9c..25213713d97 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResultStore.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Storage/AzureStorageResultStore.cs @@ -119,7 +119,7 @@ public async IAsyncEnumerable ReadResultsAsync( ScenarioRunResult? result = await JsonSerializer.DeserializeAsync( content.Value.Content.ToStream(), - AzureStorageSerializerContext.Default.ScenarioRunResult, + AzureStorageJsonUtilities.Default.ScenarioRunResultTypeInfo, cancellationToken).ConfigureAwait(false) ?? throw new JsonException( string.Format(CultureInfo.CurrentCulture, DeserializationFailedMessage, fileClient.Name)); @@ -171,7 +171,7 @@ public async ValueTask WriteResultsAsync( await JsonSerializer.SerializeAsync( stream, result, - AzureStorageSerializerContext.Default.ScenarioRunResult, + AzureStorageJsonUtilities.Default.ScenarioRunResultTypeInfo, cancellationToken).ConfigureAwait(false); _ = stream.Seek(0, SeekOrigin.Begin); diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Html/HtmlReportWriter.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Html/HtmlReportWriter.cs index 28c6c163b02..f3f3e004168 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Html/HtmlReportWriter.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Html/HtmlReportWriter.cs @@ -56,7 +56,7 @@ public async ValueTask WriteReportAsync( await JsonSerializer.SerializeAsync( stream, dataset, - SerializerContext.Compact.Dataset, + JsonUtilities.Compact.DatasetTypeInfo, cancellationToken).ConfigureAwait(false); #if NET diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Json/JsonReportWriter.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Json/JsonReportWriter.cs index da7921df938..63d58d0dc66 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Json/JsonReportWriter.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Formats/Json/JsonReportWriter.cs @@ -45,7 +45,7 @@ public async ValueTask WriteReportAsync( await JsonSerializer.SerializeAsync( stream, dataset, - SerializerContext.Default.Dataset, + JsonUtilities.Default.DatasetTypeInfo, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/JsonSerialization/JsonUtilities.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/JsonSerialization/JsonUtilities.cs new file mode 100644 index 00000000000..c1870709be3 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/JsonSerialization/JsonUtilities.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Microsoft.Extensions.AI.Evaluation.Reporting.Formats; +using static Microsoft.Extensions.AI.Evaluation.Reporting.Storage.DiskBasedResponseCache; + +namespace Microsoft.Extensions.AI.Evaluation.Reporting.JsonSerialization; + +internal static partial class JsonUtilities +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Default matches the generated source naming convention.")] + internal static class Default + { + private static JsonSerializerOptions? _options; + internal static JsonSerializerOptions Options => _options ??= CreateJsonSerializerOptions(writeIndented: true); + internal static JsonTypeInfo DatasetTypeInfo => Options.GetTypeInfo(); + internal static JsonTypeInfo CacheEntryTypeInfo => Options.GetTypeInfo(); + internal static JsonTypeInfo CacheOptionsTypeInfo => Options.GetTypeInfo(); + internal static JsonTypeInfo ScenarioRunResultTypeInfo => Options.GetTypeInfo(); + } + + internal static class Compact + { + private static JsonSerializerOptions? _options; + internal static JsonSerializerOptions Options => _options ??= CreateJsonSerializerOptions(writeIndented: false); + internal static JsonTypeInfo DatasetTypeInfo => Options.GetTypeInfo(); + internal static JsonTypeInfo CacheEntryTypeInfo => Options.GetTypeInfo(); + internal static JsonTypeInfo CacheOptionsTypeInfo => Options.GetTypeInfo(); + internal static JsonTypeInfo ScenarioRunResultTypeInfo => Options.GetTypeInfo(); + } + + private static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options) => (JsonTypeInfo)options.GetTypeInfo(typeof(T)); + + private static JsonSerializerOptions CreateJsonSerializerOptions(bool writeIndented) + { + var options = new JsonSerializerOptions(JsonContext.Default.Options) + { + WriteIndented = writeIndented, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + options.TypeInfoResolverChain.Add(AIJsonUtilities.DefaultOptions.TypeInfoResolver!); + options.MakeReadOnly(); + return options; + } + + [JsonSerializable(typeof(EvaluationResult))] + [JsonSerializable(typeof(Dataset))] + [JsonSerializable(typeof(CacheEntry))] + [JsonSerializable(typeof(CacheOptions))] + [JsonSourceGenerationOptions( + Converters = [ + typeof(CamelCaseEnumConverter), + typeof(CamelCaseEnumConverter), + typeof(CamelCaseEnumConverter), + typeof(TimeSpanConverter) + ], + WriteIndented = true, + IgnoreReadOnlyProperties = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] + private sealed partial class JsonContext : JsonSerializerContext; + +} diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/JsonSerialization/SerializerContext.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/JsonSerialization/SerializerContext.cs deleted file mode 100644 index 315180c4892..00000000000 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/JsonSerialization/SerializerContext.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json; -using System.Text.Json.Serialization; -using Microsoft.Extensions.AI.Evaluation.Reporting.Formats; -using static Microsoft.Extensions.AI.Evaluation.Reporting.Storage.DiskBasedResponseCache; - -namespace Microsoft.Extensions.AI.Evaluation.Reporting.JsonSerialization; - -[JsonSerializable(typeof(EvaluationResult))] -[JsonSerializable(typeof(Dataset))] -[JsonSerializable(typeof(CacheEntry))] -[JsonSerializable(typeof(CacheOptions))] -[JsonSourceGenerationOptions( - Converters = [ - typeof(CamelCaseEnumConverter), - typeof(CamelCaseEnumConverter), - typeof(CamelCaseEnumConverter), - typeof(TimeSpanConverter)], - WriteIndented = true, - IgnoreReadOnlyProperties = false, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] -internal sealed partial class SerializerContext : JsonSerializerContext -{ - private static SerializerContext? _compact; - - internal static SerializerContext Compact => - _compact ??= - new(new JsonSerializerOptions(Default.Options) - { - WriteIndented = false, - }); -} diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheEntry.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheEntry.cs index 34c0b99ac98..193e2b8832f 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheEntry.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheEntry.cs @@ -40,7 +40,7 @@ public static CacheEntry Read(string cacheEntryFilePath) CacheEntry cacheEntry = JsonSerializer.Deserialize( cacheEntryFile, - SerializerContext.Default.CacheEntry) ?? + JsonUtilities.Default.CacheEntryTypeInfo) ?? throw new JsonException( string.Format(CultureInfo.CurrentCulture, DeserializationFailedMessage, cacheEntryFilePath)); @@ -56,7 +56,7 @@ public static async Task ReadAsync( CacheEntry cacheEntry = await JsonSerializer.DeserializeAsync( cacheEntryFile, - SerializerContext.Default.CacheEntry, + JsonUtilities.Default.CacheEntryTypeInfo, cancellationToken).ConfigureAwait(false) ?? throw new JsonException( string.Format(CultureInfo.CurrentCulture, DeserializationFailedMessage, cacheEntryFilePath)); @@ -67,7 +67,7 @@ await JsonSerializer.DeserializeAsync( public void Write(string cacheEntryFilePath) { using FileStream cacheEntryFile = File.Create(cacheEntryFilePath); - JsonSerializer.Serialize(cacheEntryFile, this, SerializerContext.Default.CacheEntry); + JsonSerializer.Serialize(cacheEntryFile, this, JsonUtilities.Default.CacheEntryTypeInfo); } public async Task WriteAsync( @@ -78,7 +78,7 @@ public async Task WriteAsync( await JsonSerializer.SerializeAsync( cacheEntryFile, this, - SerializerContext.Default.CacheEntry, + JsonUtilities.Default.CacheEntryTypeInfo, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheOptions.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheOptions.cs index 574f0a21a0e..68d8d66823e 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheOptions.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResponseCache.CacheOptions.cs @@ -45,7 +45,7 @@ public static CacheOptions Read(string cacheOptionsFilePath) CacheOptions cacheOptions = JsonSerializer.Deserialize( cacheOptionsFile, - SerializerContext.Default.CacheOptions) ?? + JsonUtilities.Default.CacheOptionsTypeInfo) ?? throw new JsonException( string.Format(CultureInfo.CurrentCulture, DeserializationFailedMessage, cacheOptionsFilePath)); @@ -61,7 +61,7 @@ public static async Task ReadAsync( CacheOptions cacheOptions = await JsonSerializer.DeserializeAsync( cacheOptionsFile, - SerializerContext.Default.CacheOptions, + JsonUtilities.Default.CacheOptionsTypeInfo, cancellationToken).ConfigureAwait(false) ?? throw new JsonException( string.Format(CultureInfo.CurrentCulture, DeserializationFailedMessage, cacheOptionsFilePath)); @@ -72,7 +72,7 @@ await JsonSerializer.DeserializeAsync( public void Write(string cacheOptionsFilePath) { using FileStream cacheOptionsFile = File.Create(cacheOptionsFilePath); - JsonSerializer.Serialize(cacheOptionsFile, this, SerializerContext.Default.CacheOptions); + JsonSerializer.Serialize(cacheOptionsFile, this, JsonUtilities.Default.CacheOptionsTypeInfo); } public async Task WriteAsync( @@ -83,7 +83,7 @@ public async Task WriteAsync( await JsonSerializer.SerializeAsync( cacheOptionsFile, this, - SerializerContext.Default.CacheOptions, + JsonUtilities.Default.CacheOptionsTypeInfo, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResultStore.cs b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResultStore.cs index 422bcab2fb2..9917e0403ff 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResultStore.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Storage/DiskBasedResultStore.cs @@ -68,7 +68,7 @@ public async IAsyncEnumerable ReadResultsAsync( ScenarioRunResult? result = await JsonSerializer.DeserializeAsync( stream, - SerializerContext.Default.ScenarioRunResult, + JsonUtilities.Default.ScenarioRunResultTypeInfo, cancellationToken).ConfigureAwait(false); yield return result is null @@ -101,7 +101,7 @@ public async ValueTask WriteResultsAsync( await JsonSerializer.SerializeAsync( stream, result, - SerializerContext.Default.ScenarioRunResult, + JsonUtilities.Default.ScenarioRunResultTypeInfo, cancellationToken).ConfigureAwait(false); } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheEntryTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheEntryTests.cs index 6f88f273fe8..38738a16f46 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheEntryTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheEntryTests.cs @@ -23,8 +23,8 @@ public void SerializeCacheEntry() creation: DateTime.UtcNow, expiration: DateTime.UtcNow.Add(TimeSpan.FromMinutes(5))); - string json = JsonSerializer.Serialize(entry, SerializerContext.Default.CacheEntry); - CacheEntry? deserialized = JsonSerializer.Deserialize(json, SerializerContext.Default.CacheEntry); + string json = JsonSerializer.Serialize(entry, JsonUtilities.Default.CacheEntryTypeInfo); + CacheEntry? deserialized = JsonSerializer.Deserialize(json, JsonUtilities.Default.CacheEntryTypeInfo); Assert.NotNull(deserialized); Assert.Equal(entry.ScenarioName, deserialized!.ScenarioName); @@ -43,8 +43,8 @@ public void SerializeCacheEntryCompact() creation: DateTime.UtcNow, expiration: DateTime.UtcNow.Add(TimeSpan.FromMinutes(5))); - string json = JsonSerializer.Serialize(entry, SerializerContext.Compact.CacheEntry); - CacheEntry? deserialized = JsonSerializer.Deserialize(json, SerializerContext.Default.CacheEntry); + string json = JsonSerializer.Serialize(entry, JsonUtilities.Compact.CacheEntryTypeInfo); + CacheEntry? deserialized = JsonSerializer.Deserialize(json, JsonUtilities.Default.CacheEntryTypeInfo); Assert.NotNull(deserialized); Assert.Equal(entry.ScenarioName, deserialized!.ScenarioName); diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheOptionsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheOptionsTests.cs index 740bdc04dcd..c7e90b92503 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheOptionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/CacheOptionsTests.cs @@ -19,8 +19,8 @@ public void SerializeCacheOptions() { var options = new CacheOptions(CacheMode.Disabled, TimeSpan.FromDays(300)); - string json = JsonSerializer.Serialize(options, SerializerContext.Default.CacheOptions); - CacheOptions? deserialized = JsonSerializer.Deserialize(json, SerializerContext.Default.CacheOptions); + string json = JsonSerializer.Serialize(options, JsonUtilities.Default.CacheOptionsTypeInfo); + CacheOptions? deserialized = JsonSerializer.Deserialize(json, JsonUtilities.Default.CacheOptionsTypeInfo); Assert.NotNull(deserialized); Assert.Equal(options.Mode, deserialized!.Mode); @@ -33,8 +33,8 @@ public void SerializeCacheOptionsCompact() { var options = new CacheOptions(CacheMode.Disabled, TimeSpan.FromDays(300)); - string json = JsonSerializer.Serialize(options, SerializerContext.Compact.CacheOptions); - CacheOptions? deserialized = JsonSerializer.Deserialize(json, SerializerContext.Default.CacheOptions); + string json = JsonSerializer.Serialize(options, JsonUtilities.Compact.CacheOptionsTypeInfo); + CacheOptions? deserialized = JsonSerializer.Deserialize(json, JsonUtilities.Default.CacheOptionsTypeInfo); Assert.NotNull(deserialized); Assert.Equal(options.Mode, deserialized!.Mode); diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ScenarioRunResultTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ScenarioRunResultTests.cs index 9418a5db359..354f82624a2 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ScenarioRunResultTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ScenarioRunResultTests.cs @@ -39,8 +39,8 @@ public void SerializeScenarioRunResult() modelResponse: new ChatResponse(new ChatMessage(ChatRole.Assistant, "response")), evaluationResult: new EvaluationResult(booleanMetric, numericMetric, stringMetric, metricWithNoValue)); - string json = JsonSerializer.Serialize(entry, SerializerContext.Default.ScenarioRunResult); - ScenarioRunResult? deserialized = JsonSerializer.Deserialize(json, SerializerContext.Default.ScenarioRunResult); + string json = JsonSerializer.Serialize(entry, JsonUtilities.Default.ScenarioRunResultTypeInfo); + ScenarioRunResult? deserialized = JsonSerializer.Deserialize(json, JsonUtilities.Default.ScenarioRunResultTypeInfo); Assert.NotNull(deserialized); Assert.Equal(entry.ScenarioName, deserialized!.ScenarioName); @@ -80,8 +80,8 @@ public void SerializeDatasetCompact() var dataset = new Dataset([entry], createdAt: DateTime.UtcNow, generatorVersion: "1.2.3.4"); - string json = JsonSerializer.Serialize(dataset, SerializerContext.Compact.Dataset); - Dataset? deserialized = JsonSerializer.Deserialize(json, SerializerContext.Default.Dataset); + string json = JsonSerializer.Serialize(dataset, JsonUtilities.Compact.DatasetTypeInfo); + Dataset? deserialized = JsonSerializer.Deserialize(json, JsonUtilities.Default.DatasetTypeInfo); Assert.NotNull(deserialized); Assert.Equal(entry.ScenarioName, deserialized!.ScenarioRunResults[0].ScenarioName); @@ -107,8 +107,8 @@ public void VerifyCompactSerialization() creation: DateTime.UtcNow, expiration: DateTime.UtcNow.Add(TimeSpan.FromMinutes(5))); - string defaultJson = JsonSerializer.Serialize(entry, SerializerContext.Default.CacheEntry); - string compactJson = JsonSerializer.Serialize(entry, SerializerContext.Compact.CacheEntry); + string defaultJson = JsonSerializer.Serialize(entry, JsonUtilities.Default.CacheEntryTypeInfo); + string compactJson = JsonSerializer.Serialize(entry, JsonUtilities.Compact.CacheEntryTypeInfo); Assert.NotEqual(defaultJson, compactJson); Assert.True(defaultJson.Length > compactJson.Length); diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/SerializationChainingTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/SerializationChainingTests.cs new file mode 100644 index 00000000000..86a319c059d --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/SerializationChainingTests.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.RegularExpressions; +using Microsoft.Extensions.AI.Evaluation.Reporting.JsonSerialization; +using Xunit; + +namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; + +/// +/// These tests are designed to verify that the M.E.AI.Evaluation.Reporting library can serialize and deserialize +/// types that it doesn't know about, but that M.E.AI does know about. For example JsonElement. +/// +public class SerializationChainingTests +{ + private ScenarioRunResult _scenarioRunResult = + new(scenarioName: "ScenarioName", + iterationName: "IterationName", + executionName: "ExecutionName", + creationTime: DateTime.UtcNow, + messages: [], + modelResponse: new ChatResponse + { + Messages = new List + { + new ChatMessage + { + Role = ChatRole.User, + Contents = new List + { + new TextContent("A user message"), + }, + }, + }, + AdditionalProperties = new AdditionalPropertiesDictionary + { + { "model", "gpt-7" }, + { "data", JsonDocument.Parse("{\"some\":\"data\"}").RootElement }, + }, + }, evaluationResult: new EvaluationResult() + ); + + private static void VerifyScenarioRunResult(ScenarioRunResult? resp) + { + Assert.NotNull(resp); + Assert.Single(resp.ModelResponse.Messages); + Assert.Equal(ChatRole.User, resp.ModelResponse.Messages[0].Role); + Assert.Equal("A user message", resp.ModelResponse.Messages[0].Text); + Assert.NotNull(resp.ModelResponse.AdditionalProperties); + Assert.Equal("gpt-7", resp.ModelResponse.AdditionalProperties?["model"]?.ToString()); + + string jsonFromElement = resp.ModelResponse.AdditionalProperties?["data"]?.ToString()!; + jsonFromElement = Regex.Replace(jsonFromElement, @"\s+", ""); + Assert.Equal("{\"some\":\"data\"}", jsonFromElement); + } + + [Fact] + public void SerializeScenarioResult_JsonUtilities_Default() + { + string text = JsonSerializer.Serialize(_scenarioRunResult, JsonUtilities.Default.ScenarioRunResultTypeInfo); + ScenarioRunResult? response = JsonSerializer.Deserialize(text, JsonUtilities.Default.ScenarioRunResultTypeInfo); + + VerifyScenarioRunResult(response); + } + + [Fact] + public void SerializeRoundTripChatResponse_JsonUtilities_Compact() + { + string text = JsonSerializer.Serialize(_scenarioRunResult, JsonUtilities.Compact.ScenarioRunResultTypeInfo); + ScenarioRunResult? response = JsonSerializer.Deserialize(text, JsonUtilities.Compact.ScenarioRunResultTypeInfo); + + VerifyScenarioRunResult(response); + } + + [Fact] + public void SerializeRoundTripChatResponse_AzureStorageJsonUtilities_Default() + { + string text = JsonSerializer.Serialize(_scenarioRunResult, AzureStorageJsonUtilities.Default.ScenarioRunResultTypeInfo); + ScenarioRunResult? response = JsonSerializer.Deserialize(text, AzureStorageJsonUtilities.Default.ScenarioRunResultTypeInfo); + + VerifyScenarioRunResult(response); + } + + [Fact] + public void SerializeRoundTripChatResponse_AzureStorageJsonUtilities_Compact() + { + string text = JsonSerializer.Serialize(_scenarioRunResult, AzureStorageJsonUtilities.Compact.ScenarioRunResultTypeInfo); + ScenarioRunResult? response = JsonSerializer.Deserialize(text, AzureStorageJsonUtilities.Compact.ScenarioRunResultTypeInfo); + + VerifyScenarioRunResult(response); + } +}