diff --git a/dotnet/src/InternalUtilities/src/Schema/KernelJsonSchemaBuilder.cs b/dotnet/src/InternalUtilities/src/Schema/KernelJsonSchemaBuilder.cs index 82f82895b96b..0ddd57d604fa 100644 --- a/dotnet/src/InternalUtilities/src/Schema/KernelJsonSchemaBuilder.cs +++ b/dotnet/src/InternalUtilities/src/Schema/KernelJsonSchemaBuilder.cs @@ -26,6 +26,10 @@ internal static class KernelJsonSchemaBuilder private static JsonSerializerOptions? s_options; internal static readonly AIJsonSchemaCreateOptions s_schemaOptions = new(); + [RequiresUnreferencedCode("Uses JsonStringEnumConverter and DefaultJsonTypeInfoResolver classes, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses JsonStringEnumConverter and DefaultJsonTypeInfoResolver classes, making it incompatible with AOT scenarios.")] + internal static JsonSerializerOptions GetDefaultJsonSerializerOptions() => GetDefaultOptions(); + private static readonly JsonElement s_trueSchemaAsObject = JsonElement.Parse("{}"); private static readonly JsonElement s_falseSchemaAsObject = JsonElement.Parse("""{"not":true}"""); diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs index 9385ae02bcaf..b6859f0658b4 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs @@ -789,18 +789,20 @@ private static bool TryToDeserializeValue(object value, Type targetType, JsonSer { try { + JsonSerializerOptions effectiveOptions = jsonSerializerOptions ?? KernelJsonSchemaBuilder.GetDefaultJsonSerializerOptions(); + deserializedValue = value switch { - JsonDocument document => document.Deserialize(targetType, jsonSerializerOptions), - JsonNode node => node.Deserialize(targetType, jsonSerializerOptions), - JsonElement element => element.Deserialize(targetType, jsonSerializerOptions), + JsonDocument document => document.Deserialize(targetType, effectiveOptions), + JsonNode node => node.Deserialize(targetType, effectiveOptions), + JsonElement element => element.Deserialize(targetType, effectiveOptions), // The JSON can be represented by other data types from various libraries. For example, JObject, JToken, and JValue from the Newtonsoft.Json library. // Since we don't take dependencies on these libraries and don't have access to the types here, // the only way to deserialize those types is to convert them to a string first by calling the 'ToString' method. // Attempting to use the 'JsonSerializer.Serialize' method, instead of calling the 'ToString' directly on those types, can lead to unpredictable outcomes. // For instance, the JObject for { "id": 28 } JSON is serialized into the string "{ "Id": [] }", and the deserialization fails with the // following exception - "The JSON value could not be converted to System.Int32. Path: $.Id | LineNumber: 0 | BytePositionInLine: 7." - _ => JsonSerializer.Deserialize(value.ToString()!, targetType, jsonSerializerOptions) + _ => JsonSerializer.Deserialize(value.ToString()!, targetType, effectiveOptions) }; return true; diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests1.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests1.cs index 2c154029617a..ec8850e5f66c 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests1.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests1.cs @@ -1413,13 +1413,31 @@ public async Task ItCanDeserializeJsonStringAsync() var func = KernelFunctionFactory.CreateFromMethod((CustomTypeForJsonTests param) => { actualArgValue = param; }); // Act - var res = await func.InvokeAsync(this._kernel, new() { ["param"] = jsonString }); + _ = await func.InvokeAsync(this._kernel, new() { ["param"] = jsonString }); // Assert Assert.NotNull(actualArgValue); Assert.Equal(28, actualArgValue.Id); } + [Fact] + public async Task ItCanDeserializeStringEnumsWithDefaultJsonOptionsAsync() + { + // Arrange + var jsonString = @"[{""unit"":""hours""}]"; + Reminder[]? actualArgValue = null; + + var func = KernelFunctionFactory.CreateFromMethod((Reminder[] param) => { actualArgValue = param; }); + + // Act + _ = await func.InvokeAsync(this._kernel, new() { ["param"] = jsonString }); + + // Assert + Assert.NotNull(actualArgValue); + Assert.Single(actualArgValue); + Assert.Equal(ReminderUnit.Hours, actualArgValue[0].Unit); + } + [Fact] public async Task ItCanDeserializeThirdPartyJsonPrimitivesAsync() { @@ -1586,6 +1604,19 @@ private sealed class CustomTypeForJsonTests public int Id { get; set; } } + private sealed class Reminder + { + [JsonPropertyName("unit")] + public ReminderUnit Unit { get; set; } + } + + private enum ReminderUnit + { + Minutes, + Hours, + Days + } + private sealed class ThirdPartyJsonPrimitive(string jsonToReturn) { public override string ToString() => jsonToReturn;