diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Defaults.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Defaults.cs
index de2c2a695b6..9ae0fac76c4 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Defaults.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Defaults.cs
@@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
+using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
@@ -13,7 +15,26 @@ namespace Microsoft.Extensions.AI;
public static partial class AIJsonUtilities
{
- /// Gets the singleton used as the default in JSON serialization operations.
+ ///
+ /// Gets the singleton used as the default in JSON serialization operations.
+ ///
+ ///
+ /// For Native AOT or applications disabling this instance includes source generated contracts
+ /// for all common exchange types contained in the Microsoft.Extensions.AI.Abstractions library.
+ ///
+ ///
+ /// It additionally turns on the following settings:
+ ///
+ /// - Enables the property.
+ /// - Enables string based enum serialization as implemented by .
+ /// - Enables as the default ignore condition for properties.
+ /// -
+ /// Enables when escaping JSON strings.
+ /// Consuming applications must ensure that JSON outputs are adequately escaped before embedding in other document formats, such as HTML and XML.
+ ///
+ ///
+ ///
+ ///
public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();
/// Creates the default to use for serialization-related operations.
@@ -24,25 +45,31 @@ private static JsonSerializerOptions CreateDefaultOptions()
// If reflection-based serialization is enabled by default, use it, as it's the most permissive in terms of what it can serialize,
// and we want to be flexible in terms of what can be put into the various collections in the object model.
// Otherwise, use the source-generated options to enable trimming and Native AOT.
+ JsonSerializerOptions options;
if (JsonSerializer.IsReflectionEnabledByDefault)
{
// Keep in sync with the JsonSourceGenerationOptions attribute on JsonContext below.
- JsonSerializerOptions options = new(JsonSerializerDefaults.Web)
+ options = new(JsonSerializerDefaults.Web)
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
Converters = { new JsonStringEnumConverter() },
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true,
};
-
- options.MakeReadOnly();
- return options;
}
else
{
- return JsonContext.Default.Options;
+ options = new(JsonContext.Default.Options)
+ {
+ // Compile-time encoder setting not yet available
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ };
}
+
+ options.MakeReadOnly();
+ return options;
}
// Keep in sync with CreateDefaultOptions above.
@@ -82,5 +109,6 @@ private static JsonSerializerOptions CreateDefaultOptions()
[JsonSerializable(typeof(Embedding))]
[JsonSerializable(typeof(Embedding))]
[JsonSerializable(typeof(AIContent))]
+ [EditorBrowsable(EditorBrowsableState.Never)] // Never use JsonContext directly, use DefaultOptions instead.
private sealed partial class JsonContext : JsonSerializerContext;
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs
index b67fc1ddd77..1af2e5a8c92 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs
@@ -212,7 +212,7 @@ private static JsonElement GetJsonSchemaCore(JsonSerializerOptions options, Sche
return schemaObj is null
? _trueJsonSchema
- : JsonSerializer.SerializeToElement(schemaObj, JsonContext.Default.JsonNode);
+ : JsonSerializer.SerializeToElement(schemaObj, options.GetTypeInfo(typeof(JsonNode)));
}
if (key.Type == typeof(void))
@@ -227,7 +227,7 @@ private static JsonElement GetJsonSchemaCore(JsonSerializerOptions options, Sche
};
JsonNode node = options.GetJsonSchemaAsNode(key.Type, exporterOptions);
- return JsonSerializer.SerializeToElement(node, JsonContext.Default.JsonNode);
+ return JsonSerializer.SerializeToElement(node, DefaultOptions.GetTypeInfo(typeof(JsonNode)));
JsonNode TransformSchemaNode(JsonSchemaExporterContext schemaExporterContext, JsonNode schema)
{
diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs
index e79d2c9034e..6e782273f8b 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs
@@ -4,6 +4,7 @@
using System;
using System.ComponentModel;
using System.Linq;
+using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
@@ -33,6 +34,20 @@ public static void DefaultOptions_HasExpectedConfiguration()
// Additional settings
Assert.Equal(JsonIgnoreCondition.WhenWritingNull, options.DefaultIgnoreCondition);
Assert.True(options.WriteIndented);
+ Assert.Same(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder);
+ }
+
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("""{"forecast":"sunny", "temperature":"75"}""", """{\"forecast\":\"sunny\", \"temperature\":\"75\"}""")]
+ [InlineData("""{"message":"Πάντα ῥεῖ."}""", """{\"message\":\"Πάντα ῥεῖ.\"}""")]
+ [InlineData("""{"message":"七転び八起き"}""", """{\"message\":\"七転び八起き\"}""")]
+ [InlineData("""☺️🤖🌍𝄞""", """☺️\uD83E\uDD16\uD83C\uDF0D\uD834\uDD1E""")]
+ public static void DefaultOptions_UsesExpectedEscaping(string input, string expectedJsonString)
+ {
+ var options = AIJsonUtilities.DefaultOptions;
+ string json = JsonSerializer.Serialize(input, options);
+ Assert.Equal($@"""{expectedJsonString}""", json);
}
[Theory]