Backport JsonSchemaExporter bugfix. (#5671)

* Backport JsonSchemaExporter bugfix.

* Address feedback.
This commit is contained in:
Eirik Tsarpalis 2024-11-19 18:41:33 +00:00 коммит произвёл Stephen Toub
Родитель 32fd4f2d59
Коммит 63d537804b
4 изменённых файлов: 68 добавлений и 11 удалений

Просмотреть файл

@ -17,8 +17,8 @@ internal static partial class JsonSchemaExporter
// https://github.com/dotnet/runtime/blob/50d6cad649aad2bfa4069268eddd16fd51ec5cf3/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs
private sealed class JsonSchema
{
public static JsonSchema False { get; } = new(false);
public static JsonSchema True { get; } = new(true);
public static JsonSchema CreateFalseSchema() => new(false);
public static JsonSchema CreateTrueSchema() => new(true);
public JsonSchema()
{
@ -467,7 +467,7 @@ internal static partial class JsonSchemaExporter
switch (schema._trueOrFalse)
{
case false:
schema = new JsonSchema { Not = JsonSchema.True };
schema = new JsonSchema { Not = JsonSchema.CreateTrueSchema() };
break;
case true:
schema = new JsonSchema();

Просмотреть файл

@ -119,7 +119,7 @@ internal static partial class JsonSchemaExporter
if (!ReflectionHelpers.IsBuiltInConverter(effectiveConverter))
{
// Return a `true` schema for types with user-defined converters.
return CompleteSchema(ref state, JsonSchema.True);
return CompleteSchema(ref state, JsonSchema.CreateTrueSchema());
}
if (parentPolymorphicTypeInfo is null && typeInfo.PolymorphismOptions is { DerivedTypes.Count: > 0 } polyOptions)
@ -245,7 +245,7 @@ internal static partial class JsonSchemaExporter
if (effectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
{
// Disallow unspecified properties.
additionalProperties = JsonSchema.False;
additionalProperties = JsonSchema.CreateFalseSchema();
}
if (typeDiscriminator is { } typeDiscriminatorPair)
@ -435,7 +435,7 @@ internal static partial class JsonSchemaExporter
}
else
{
schema = JsonSchema.True;
schema = JsonSchema.CreateTrueSchema();
}
return CompleteSchema(ref state, schema);
@ -578,7 +578,7 @@ internal static partial class JsonSchemaExporter
private static readonly Dictionary<Type, Func<JsonNumberHandling, JsonSchema>> _simpleTypeSchemaFactories = new()
{
[typeof(object)] = _ => JsonSchema.True,
[typeof(object)] = _ => JsonSchema.CreateTrueSchema(),
[typeof(bool)] = _ => new JsonSchema { Type = JsonSchemaType.Boolean },
[typeof(byte)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
[typeof(ushort)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
@ -625,10 +625,10 @@ internal static partial class JsonSchemaExporter
Pattern = @"^\d+(\.\d+){1,3}$",
},
[typeof(JsonDocument)] = _ => JsonSchema.True,
[typeof(JsonElement)] = _ => JsonSchema.True,
[typeof(JsonNode)] = _ => JsonSchema.True,
[typeof(JsonValue)] = _ => JsonSchema.True,
[typeof(JsonDocument)] = _ => JsonSchema.CreateTrueSchema(),
[typeof(JsonElement)] = _ => JsonSchema.CreateTrueSchema(),
[typeof(JsonNode)] = _ => JsonSchema.CreateTrueSchema(),
[typeof(JsonValue)] = _ => JsonSchema.CreateTrueSchema(),
[typeof(JsonObject)] = _ => new JsonSchema { Type = JsonSchemaType.Object },
[typeof(JsonArray)] = _ => new JsonSchema { Type = JsonSchemaType.Array },
};

Просмотреть файл

@ -13,6 +13,7 @@ using System.Text.Json.Serialization.Metadata;
using System.Xml.Linq;
#endif
using Xunit;
using static Microsoft.Extensions.AI.JsonSchemaExporter.TestTypes;
#pragma warning disable SA1402 // File may only contain a single type
@ -86,6 +87,38 @@ public abstract class JsonSchemaExporterTests
}
#endif
#if !NET9_0 // Disable until https://github.com/dotnet/runtime/pull/109954 gets backported
[Fact]
public void TransformSchemaNode_PropertiesWithCustomConverters()
{
// Regression test for https://github.com/dotnet/runtime/issues/109868
List<(Type? parentType, string? propertyName, Type type)> visitedNodes = new();
JsonSchemaExporterOptions exporterOptions = new()
{
TransformSchemaNode = (ctx, schema) =>
{
#if NET9_0_OR_GREATER
visitedNodes.Add((ctx.PropertyInfo?.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type));
#else
visitedNodes.Add((ctx.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type));
#endif
return schema;
}
};
List<(Type? parentType, string? propertyName, Type type)> expectedNodes =
[
(typeof(ClassWithPropertiesUsingCustomConverters), "Prop1", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter1)),
(typeof(ClassWithPropertiesUsingCustomConverters), "Prop2", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter2)),
(null, null, typeof(ClassWithPropertiesUsingCustomConverters)),
];
Options.GetJsonSchemaAsNode(typeof(ClassWithPropertiesUsingCustomConverters), exporterOptions);
Assert.Equal(expectedNodes, visitedNodes);
}
#endif
[Fact]
public void TreatNullObliviousAsNonNullable_True_DoesNotImpactObjectType()
{

Просмотреть файл

@ -1164,6 +1164,29 @@ public static partial class TestTypes
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
}
public class ClassWithPropertiesUsingCustomConverters
{
[JsonPropertyOrder(0)]
public ClassWithCustomConverter1? Prop1 { get; set; }
[JsonPropertyOrder(1)]
public ClassWithCustomConverter2? Prop2 { get; set; }
[JsonConverter(typeof(CustomConverter<ClassWithCustomConverter1>))]
public class ClassWithCustomConverter1;
[JsonConverter(typeof(CustomConverter<ClassWithCustomConverter2>))]
public class ClassWithCustomConverter2;
public sealed class CustomConverter<T> : JsonConverter<T>
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> default;
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
=> writer.WriteNullValue();
}
}
[JsonSerializable(typeof(object))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(byte))]
@ -1248,6 +1271,7 @@ public static partial class TestTypes
[JsonSerializable(typeof(PocoCombiningPolymorphicTypeAndDerivedTypes))]
[JsonSerializable(typeof(ClassWithComponentModelAttributes))]
[JsonSerializable(typeof(ClassWithOptionalObjectParameter))]
[JsonSerializable(typeof(ClassWithPropertiesUsingCustomConverters))]
// Collection types
[JsonSerializable(typeof(int[]))]
[JsonSerializable(typeof(List<bool>))]