Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/9.0-staging] Fix transformer handling of boolean schemas in JsonSchemaExporter. #109975

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ internal sealed class JsonSchema
internal const string MinLengthPropertyName = "minLength";
internal const string MaxLengthPropertyName = "maxLength";

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() { }
private JsonSchema(bool trueOrFalse) { _trueOrFalse = trueOrFalse; }
Expand Down Expand Up @@ -279,7 +279,7 @@ public static void EnsureMutable(ref JsonSchema schema)
switch (schema._trueOrFalse)
{
case false:
schema = new JsonSchema { Not = True };
schema = new JsonSchema { Not = CreateTrueSchema() };
break;
case true:
schema = new JsonSchema();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ private static JsonSchema MapJsonSchemaCore(
JsonUnmappedMemberHandling effectiveUnmappedMemberHandling = typeInfo.UnmappedMemberHandling ?? typeInfo.Options.UnmappedMemberHandling;
if (effectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
{
additionalProperties = JsonSchema.False;
additionalProperties = JsonSchema.CreateFalseSchema();
}

if (typeDiscriminator is { } typeDiscriminatorPair)
Expand Down Expand Up @@ -350,7 +350,7 @@ private static JsonSchema MapJsonSchemaCore(
default:
Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.None);
// Return a `true` schema for types with user-defined converters.
return CompleteSchema(ref state, JsonSchema.True);
return CompleteSchema(ref state, JsonSchema.CreateTrueSchema());
}

JsonSchema CompleteSchema(ref GenerationState state, JsonSchema schema)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,6 @@ public override void Write(Utf8JsonWriter writer, JsonNode? value, JsonSerialize
return node;
}

internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True;
internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ public override void Write(Utf8JsonWriter writer, JsonValue? value, JsonSerializ
return JsonValue.CreateFromElement(ref element, options.GetNodeOptions());
}

internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True;
internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,6 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
return true;
}

internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True;
internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ public override void Write(Utf8JsonWriter writer, JsonDocument? value, JsonSeria
value.WriteTo(writer);
}

internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True;
internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSeriali
value.WriteTo(writer);
}

internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True;
internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions
throw new NotSupportedException(ErrorMessage);

internal override JsonSchema? GetSchema(JsonNumberHandling _) =>
new JsonSchema { Comment = "Unsupported .NET type", Not = JsonSchema.True };
new JsonSchema { Comment = "Unsupported .NET type", Not = JsonSchema.CreateTrueSchema() };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,18 @@ of the type which points to the first occurrence. */
}
""");

yield return new TestData<ClassWithPropertiesUsingCustomConverters>(
Value: new() { Prop1 = new() , Prop2 = new() },
ExpectedJsonSchema: """
{
"type": ["object","null"],
"properties": {
"Prop1": true,
"Prop2": true,
}
}
""");

// Collection types
yield return new TestData<int[]>([1, 2, 3], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"integer"}}""");
yield return new TestData<List<bool>>([false, true, false], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"boolean"}}""");
Expand Down Expand Up @@ -1586,6 +1598,29 @@ public interface ITestData
IEnumerable<ITestData> GetTestDataForAllValues();
}

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();
}
}

private static TAttribute? GetCustomAttribute<TAttribute>(ICustomAttributeProvider? provider, bool inherit = false) where TAttribute : Attribute
=> provider?.GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,32 @@ public void CanGenerateXElementSchema()
Assert.True(schema.ToJsonString().Length < 100_000);
}

[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) =>
{
visitedNodes.Add((ctx.PropertyInfo?.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type));
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)),
];

Serializer.DefaultOptions.GetJsonSchemaAsNode(typeof(ClassWithPropertiesUsingCustomConverters), exporterOptions);

Assert.Equal(expectedNodes, visitedNodes);
}

[Fact]
public void TypeWithDisallowUnmappedMembers_AdditionalPropertiesFailValidation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public sealed partial class JsonSchemaExporterTests_SourceGen()
[JsonSerializable(typeof(ClassWithComponentModelAttributes))]
[JsonSerializable(typeof(ClassWithJsonPointerEscapablePropertyNames))]
[JsonSerializable(typeof(ClassWithOptionalObjectParameter))]
[JsonSerializable(typeof(ClassWithPropertiesUsingCustomConverters))]
// Collection types
[JsonSerializable(typeof(int[]))]
[JsonSerializable(typeof(List<bool>))]
Expand Down
Loading