Skip to content

Commit

Permalink
Add support for STJ-native inheritance schema generation, #1595
Browse files Browse the repository at this point in the history
  • Loading branch information
Rico Suter committed Sep 26, 2023
1 parent e12442b commit 16bc1c0
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 11 deletions.
6 changes: 0 additions & 6 deletions global.json

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NJsonSchema.Converters;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -35,6 +36,55 @@ public async Task When_using_JsonInheritanceAttribute_and_SystemTextJson_then_sc
Assert.NotNull(data);
Assert.Contains(@"""a"": """, data);
Assert.Contains(@"""o"": """, data);
Assert.Contains(@"""discriminator"": {
""propertyName"": ""k"",
""mapping"": {
""a"": ""#/definitions/Apple"",
""o"": ""#/definitions/Orange""
}
},", data);
}

#if !NET462

public class Apple2 : Fruit2
{
public string Foo { get; set; }
}

public class Orange2 : Fruit2
{
public string Bar { get; set; }
}

[JsonDerivedType(typeof(Apple2), "a")]
[JsonDerivedType(typeof(Orange2), "o")]
[JsonPolymorphic(TypeDiscriminatorPropertyName = "k")]
public class Fruit2
{
public string Baz { get; set; }
}

[Fact]
public async Task When_using_native_attributes_in_SystemTextJson_then_schema_is_correct()
{
//// Act
var schema = JsonSchema.FromType<Fruit2>();
var data = schema.ToJson();

//// Assert
Assert.NotNull(data);
Assert.Contains(@"""a"": """, data);
Assert.Contains(@"""o"": """, data);
Assert.Contains(@"""discriminator"": {
""propertyName"": ""k"",
""mapping"": {
""a"": ""#/definitions/Apple2"",
""o"": ""#/definitions/Orange2""
}
},", data);
}

#endif
}
}
2 changes: 1 addition & 1 deletion src/NJsonSchema.Tests/NJsonSchema.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<TargetFrameworks>net7.0;net462</TargetFrameworks>
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
Expand Down
44 changes: 44 additions & 0 deletions src/NJsonSchema/Generation/JsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,18 @@ private void GenerateKnownTypes(Type type, JsonSchemaResolver schemaResolver)
AddKnownType(knownType, schemaResolver);
}
}

foreach (var jsonConverterAttribute in attributes
.GetAssignableToTypeName("System.Text.Json.Serialization.JsonDerivedTypeAttribute", TypeNameStyle.FullName))
{
var knownType = ObjectExtensions.TryGetPropertyValue<Type>(
jsonConverterAttribute, "DerivedType", null);

if (knownType != null)
{
AddKnownType(knownType, schemaResolver);
}
}
}

private void AddKnownType(Type type, JsonSchemaResolver schemaResolver)
Expand Down Expand Up @@ -1067,6 +1079,7 @@ private object TryGetInheritanceDiscriminatorConverter(Type type)
{
var typeAttributes = type.GetTypeInfo().GetCustomAttributes(false).OfType<Attribute>();

// support for NJsonSchema provided inheritance converters
dynamic jsonConverterAttribute = typeAttributes.FirstAssignableToTypeNameOrDefault(nameof(JsonConverterAttribute), TypeNameStyle.Name);
if (jsonConverterAttribute != null)
{
Expand All @@ -1084,9 +1097,40 @@ private object TryGetInheritanceDiscriminatorConverter(Type type)
}
}

// support for native System.Text.Json inheritance
dynamic[] jsonDerivedTypeAttributes = typeAttributes
.Where(a => a.GetType().IsAssignableToTypeName("System.Text.Json.Serialization.JsonDerivedTypeAttribute", TypeNameStyle.FullName))
.ToArray();

if (jsonDerivedTypeAttributes.Any())
{
dynamic jsonPolymorphicAttribute = typeAttributes
.FirstAssignableToTypeNameOrDefault("System.Text.Json.Serialization.JsonPolymorphicAttribute", TypeNameStyle.FullName);
return new SystemTextJsonInheritanceWrapper(jsonPolymorphicAttribute?.TypeDiscriminatorPropertyName ?? "$type", jsonDerivedTypeAttributes);
}

return null;
}

private class SystemTextJsonInheritanceWrapper
{
private readonly dynamic[] _jsonDerivedTypeAttributes;

public SystemTextJsonInheritanceWrapper(string discriminatorName, dynamic[] jsonDerivedTypeAttributes)
{
DiscriminatorName = discriminatorName;
_jsonDerivedTypeAttributes = jsonDerivedTypeAttributes;
}

public string DiscriminatorName { get; }

public string GetDiscriminatorValue(Type type)
{
return _jsonDerivedTypeAttributes.FirstOrDefault(a => a.DerivedType == type)?.TypeDiscriminator
?? throw new InvalidOperationException($"Discriminator value for {type.FullName} not found.");
}
}

private string TryGetInheritanceDiscriminatorName(object jsonInheritanceConverter)
{
return ObjectExtensions.TryGetPropertyValue(
Expand Down
5 changes: 1 addition & 4 deletions src/NJsonSchema/OpenApiDiscriminator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Reflection;
using NJsonSchema.Infrastructure;
using NJsonSchema.References;
using Newtonsoft.Json.Linq;

Expand All @@ -37,14 +36,12 @@ public class OpenApiDiscriminator
/// <param name="schema">The schema.</param>
public void AddMapping(Type type, JsonSchema schema)
{
dynamic converter = JsonInheritanceConverter;

var getDiscriminatorValueMethod = JsonInheritanceConverter?.GetType()
.GetRuntimeMethod("GetDiscriminatorValue", new Type[] { typeof(Type) });

if (getDiscriminatorValueMethod != null)
{
var discriminatorValue = converter.GetDiscriminatorValue(type);
var discriminatorValue = (string)getDiscriminatorValueMethod.Invoke(JsonInheritanceConverter, new[] { type } );
Mapping[discriminatorValue] = new JsonSchema { Reference = schema.ActualSchema };
}
else
Expand Down

0 comments on commit 16bc1c0

Please sign in to comment.