Skip to content

Commit 9cfd5ff

Browse files
Backport JsonSchemaExporter bugfix. (#5671)
* Backport JsonSchemaExporter bugfix. * Address feedback.
1 parent 9b61daa commit 9cfd5ff

File tree

4 files changed

+68
-11
lines changed

4 files changed

+68
-11
lines changed

src/Shared/JsonSchemaExporter/JsonSchemaExporter.JsonSchema.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ internal static partial class JsonSchemaExporter
1717
// https://github.com/dotnet/runtime/blob/50d6cad649aad2bfa4069268eddd16fd51ec5cf3/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs
1818
private sealed class JsonSchema
1919
{
20-
public static JsonSchema False { get; } = new(false);
21-
public static JsonSchema True { get; } = new(true);
20+
public static JsonSchema CreateFalseSchema() => new(false);
21+
public static JsonSchema CreateTrueSchema() => new(true);
2222

2323
public JsonSchema()
2424
{
@@ -467,7 +467,7 @@ public static void EnsureMutable(ref JsonSchema schema)
467467
switch (schema._trueOrFalse)
468468
{
469469
case false:
470-
schema = new JsonSchema { Not = JsonSchema.True };
470+
schema = new JsonSchema { Not = JsonSchema.CreateTrueSchema() };
471471
break;
472472
case true:
473473
schema = new JsonSchema();

src/Shared/JsonSchemaExporter/JsonSchemaExporter.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private static JsonSchema MapJsonSchemaCore(
119119
if (!ReflectionHelpers.IsBuiltInConverter(effectiveConverter))
120120
{
121121
// Return a `true` schema for types with user-defined converters.
122-
return CompleteSchema(ref state, JsonSchema.True);
122+
return CompleteSchema(ref state, JsonSchema.CreateTrueSchema());
123123
}
124124

125125
if (parentPolymorphicTypeInfo is null && typeInfo.PolymorphismOptions is { DerivedTypes.Count: > 0 } polyOptions)
@@ -245,7 +245,7 @@ private static JsonSchema MapJsonSchemaCore(
245245
if (effectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
246246
{
247247
// Disallow unspecified properties.
248-
additionalProperties = JsonSchema.False;
248+
additionalProperties = JsonSchema.CreateFalseSchema();
249249
}
250250

251251
if (typeDiscriminator is { } typeDiscriminatorPair)
@@ -435,7 +435,7 @@ private static JsonSchema MapJsonSchemaCore(
435435
}
436436
else
437437
{
438-
schema = JsonSchema.True;
438+
schema = JsonSchema.CreateTrueSchema();
439439
}
440440

441441
return CompleteSchema(ref state, schema);
@@ -578,7 +578,7 @@ private static string FormatJsonPointer(ReadOnlySpan<string> path)
578578

579579
private static readonly Dictionary<Type, Func<JsonNumberHandling, JsonSchema>> _simpleTypeSchemaFactories = new()
580580
{
581-
[typeof(object)] = _ => JsonSchema.True,
581+
[typeof(object)] = _ => JsonSchema.CreateTrueSchema(),
582582
[typeof(bool)] = _ => new JsonSchema { Type = JsonSchemaType.Boolean },
583583
[typeof(byte)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
584584
[typeof(ushort)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
@@ -625,10 +625,10 @@ private static string FormatJsonPointer(ReadOnlySpan<string> path)
625625
Pattern = @"^\d+(\.\d+){1,3}$",
626626
},
627627

628-
[typeof(JsonDocument)] = _ => JsonSchema.True,
629-
[typeof(JsonElement)] = _ => JsonSchema.True,
630-
[typeof(JsonNode)] = _ => JsonSchema.True,
631-
[typeof(JsonValue)] = _ => JsonSchema.True,
628+
[typeof(JsonDocument)] = _ => JsonSchema.CreateTrueSchema(),
629+
[typeof(JsonElement)] = _ => JsonSchema.CreateTrueSchema(),
630+
[typeof(JsonNode)] = _ => JsonSchema.CreateTrueSchema(),
631+
[typeof(JsonValue)] = _ => JsonSchema.CreateTrueSchema(),
632632
[typeof(JsonObject)] = _ => new JsonSchema { Type = JsonSchemaType.Object },
633633
[typeof(JsonArray)] = _ => new JsonSchema { Type = JsonSchemaType.Array },
634634
};

test/Shared/JsonSchemaExporter/JsonSchemaExporterTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Xml.Linq;
1414
#endif
1515
using Xunit;
16+
using static Microsoft.Extensions.AI.JsonSchemaExporter.TestTypes;
1617

1718
#pragma warning disable SA1402 // File may only contain a single type
1819

@@ -86,6 +87,38 @@ public void CanGenerateXElementSchema()
8687
}
8788
#endif
8889

90+
#if !NET9_0 // Disable until https://github.com/dotnet/runtime/pull/109954 gets backported
91+
[Fact]
92+
public void TransformSchemaNode_PropertiesWithCustomConverters()
93+
{
94+
// Regression test for https://github.com/dotnet/runtime/issues/109868
95+
List<(Type? parentType, string? propertyName, Type type)> visitedNodes = new();
96+
JsonSchemaExporterOptions exporterOptions = new()
97+
{
98+
TransformSchemaNode = (ctx, schema) =>
99+
{
100+
#if NET9_0_OR_GREATER
101+
visitedNodes.Add((ctx.PropertyInfo?.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type));
102+
#else
103+
visitedNodes.Add((ctx.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type));
104+
#endif
105+
return schema;
106+
}
107+
};
108+
109+
List<(Type? parentType, string? propertyName, Type type)> expectedNodes =
110+
[
111+
(typeof(ClassWithPropertiesUsingCustomConverters), "Prop1", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter1)),
112+
(typeof(ClassWithPropertiesUsingCustomConverters), "Prop2", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter2)),
113+
(null, null, typeof(ClassWithPropertiesUsingCustomConverters)),
114+
];
115+
116+
Options.GetJsonSchemaAsNode(typeof(ClassWithPropertiesUsingCustomConverters), exporterOptions);
117+
118+
Assert.Equal(expectedNodes, visitedNodes);
119+
}
120+
#endif
121+
89122
[Fact]
90123
public void TreatNullObliviousAsNonNullable_True_DoesNotImpactObjectType()
91124
{

test/Shared/JsonSchemaExporter/TestTypes.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,29 @@ public readonly struct StructDictionary<TKey, TValue>(IEnumerable<KeyValuePair<T
11641164
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
11651165
}
11661166

1167+
public class ClassWithPropertiesUsingCustomConverters
1168+
{
1169+
[JsonPropertyOrder(0)]
1170+
public ClassWithCustomConverter1? Prop1 { get; set; }
1171+
[JsonPropertyOrder(1)]
1172+
public ClassWithCustomConverter2? Prop2 { get; set; }
1173+
1174+
[JsonConverter(typeof(CustomConverter<ClassWithCustomConverter1>))]
1175+
public class ClassWithCustomConverter1;
1176+
1177+
[JsonConverter(typeof(CustomConverter<ClassWithCustomConverter2>))]
1178+
public class ClassWithCustomConverter2;
1179+
1180+
public sealed class CustomConverter<T> : JsonConverter<T>
1181+
{
1182+
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
1183+
=> default;
1184+
1185+
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
1186+
=> writer.WriteNullValue();
1187+
}
1188+
}
1189+
11671190
[JsonSerializable(typeof(object))]
11681191
[JsonSerializable(typeof(bool))]
11691192
[JsonSerializable(typeof(byte))]
@@ -1248,6 +1271,7 @@ public readonly struct StructDictionary<TKey, TValue>(IEnumerable<KeyValuePair<T
12481271
[JsonSerializable(typeof(PocoCombiningPolymorphicTypeAndDerivedTypes))]
12491272
[JsonSerializable(typeof(ClassWithComponentModelAttributes))]
12501273
[JsonSerializable(typeof(ClassWithOptionalObjectParameter))]
1274+
[JsonSerializable(typeof(ClassWithPropertiesUsingCustomConverters))]
12511275
// Collection types
12521276
[JsonSerializable(typeof(int[]))]
12531277
[JsonSerializable(typeof(List<bool>))]

0 commit comments

Comments
 (0)