Skip to content

Commit

Permalink
Allow renaming discriminator values in JsonInheritanceConverter & sup…
Browse files Browse the repository at this point in the history
…port that in generator, closes #668
  • Loading branch information
RicoSuter committed Mar 27, 2018
1 parent 5bec379 commit 4b2c224
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,23 @@ public async Task When_JsonInheritanceConverter_is_set_then_discriminator_field_
Assert.Equal("discriminator", discriminator);
}

[Fact]
public async Task When_JsonInheritanceConverter_is_set_then_discriminator_mappings_are_generated()
{
//// Arrange
var schema = await JsonSchema4.FromTypeAsync<Container>();
var json = schema.ToJson();

//// Act
var baseSchema = schema.Definitions["SubClass"].ActualSchema;

//// Assert
Assert.Equal(3, baseSchema.DiscriminatorObject.Mapping.Count);
Assert.True(baseSchema.DiscriminatorObject.Mapping.ContainsKey("SubClass1"));
Assert.True(baseSchema.DiscriminatorObject.Mapping.ContainsKey("SubClass2"));
Assert.True(baseSchema.DiscriminatorObject.Mapping.ContainsKey("SubClass3"));
}

[Fact]
public async Task When_schema_contains_discriminator_and_inheritance_hierarchy_then_CSharp_is_correctly_generated()
{
Expand Down
31 changes: 24 additions & 7 deletions src/NJsonSchema/Converters/JsonInheritanceConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public JsonInheritanceConverter(Type baseType, string discriminator)
_baseType = baseType;
}

/// <summary>Gets the discriminator property name.</summary>
public virtual string DiscriminatorName => _discriminator;

/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
Expand All @@ -83,7 +86,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
_isWriting = true;

var jObject = JObject.FromObject(value, serializer);
jObject.AddFirst(new JProperty(_discriminator, value.GetType().Name));
jObject.AddFirst(new JProperty(_discriminator, GetDiscriminatorValue(value.GetType())));
writer.WriteToken(jObject.CreateReader());
}
finally
Expand Down Expand Up @@ -157,7 +160,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
return null;

var discriminator = jObject.GetValue(_discriminator).Value<string>();
var subtype = GetObjectSubtype(jObject, objectType, discriminator);
var subtype = GetDiscriminatorType(jObject, objectType, discriminator);

try
{
Expand All @@ -170,16 +173,29 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
}
}

private Type GetObjectSubtype(JObject jObject, Type objectType, string discriminator)
/// <summary>Gets the discriminator value for the given type.</summary>
/// <param name="type">The object type.</param>
/// <returns>The discriminator value.</returns>
public virtual string GetDiscriminatorValue(Type type)
{
if (objectType.Name == discriminator)
return type.Name;
}

/// <summary>Gets the type for the given discriminator value.</summary>
/// <param name="jObject">The JSON object.</param>
/// <param name="objectType">The object (base) type.</param>
/// <param name="discriminatorValue">The discriminator value.</param>
/// <returns></returns>
protected virtual Type GetDiscriminatorType(JObject jObject, Type objectType, string discriminatorValue)
{
if (objectType.Name == discriminatorValue)
return objectType;

var knownTypeAttributesSubtype = GetSubtypeFromKnownTypeAttributes(objectType, discriminator);
var knownTypeAttributesSubtype = GetSubtypeFromKnownTypeAttributes(objectType, discriminatorValue);
if (knownTypeAttributesSubtype != null)
return knownTypeAttributesSubtype;

var typeName = objectType.Namespace + "." + discriminator;
var typeName = objectType.Namespace + "." + discriminatorValue;
var subtype = objectType.GetTypeInfo().Assembly.GetType(typeName);
if (subtype != null)
return subtype;
Expand All @@ -191,7 +207,7 @@ private Type GetObjectSubtype(JObject jObject, Type objectType, string discrimin
return Type.GetType(typeInfo.Value<string>());
}

throw new InvalidOperationException("Could not find subtype of '" + objectType.Name + "' with discriminator '" + discriminator + "'.");
throw new InvalidOperationException("Could not find subtype of '" + objectType.Name + "' with discriminator '" + discriminatorValue + "'.");
}

private Type GetSubtypeFromKnownTypeAttributes(Type objectType, string discriminator)
Expand Down Expand Up @@ -222,6 +238,7 @@ private Type GetSubtypeFromKnownTypeAttributes(Type objectType, string discrimin
}
type = type.GetTypeInfo().BaseType;
} while (type != null);

return null;
}
}
Expand Down
49 changes: 35 additions & 14 deletions src/NJsonSchema/Generation/JsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,6 @@ private async Task AddKnownTypeAsync(Type type, JsonSchemaResolver schemaResolve

private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver)
{
GenerateInheritanceDiscriminator(type, schema);

var baseType = type.GetTypeInfo().BaseType;
if (baseType != null && baseType != typeof(object) && baseType != typeof(ValueType))
{
Expand Down Expand Up @@ -660,46 +658,69 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS
await GeneratePropertiesAndInheritanceAsync(i, schema, schemaResolver).ConfigureAwait(false);
}
}

GenerateInheritanceDiscriminator(type, schema);
}

private void GenerateInheritanceDiscriminator(Type type, JsonSchema4 schema)
{
if (!Settings.FlattenInheritanceHierarchy)
{
var discriminator = TryGetInheritanceDiscriminator(type);
if (!string.IsNullOrEmpty(discriminator))
var discriminatorConverter = TryGetInheritanceDiscriminatorConverter(type);
if (discriminatorConverter != null)
{
if (schema.Properties.ContainsKey(discriminator))
throw new InvalidOperationException("The JSON property '" + discriminator + "' is defined multiple times on type '" + type.FullName + "'.");
var discriminatorName = TryGetInheritanceDiscriminatorName(discriminatorConverter);
if (schema.Properties.ContainsKey(discriminatorName))
throw new InvalidOperationException("The JSON property '" + discriminatorName + "' is defined multiple times on type '" + type.FullName + "'.");

schema.Discriminator = discriminator;
schema.Properties[discriminator] = new JsonProperty
var discriminator = new OpenApiDiscriminator
{
JsonInheritanceConverter = discriminatorConverter,
PropertyName = discriminatorName
};

schema.DiscriminatorObject = discriminator;
schema.Properties[discriminatorName] = new JsonProperty
{
Type = JsonObjectType.String,
IsRequired = true
};
}
else
{
var baseDiscriminator = schema.BaseDiscriminator;
baseDiscriminator?.AddMapping(type, schema);
}
}
}

private string TryGetInheritanceDiscriminator(Type type)
private object TryGetInheritanceDiscriminatorConverter(Type type)
{
var typeAttributes = type.GetTypeInfo().GetCustomAttributes(false).OfType<Attribute>();

dynamic jsonConverterAttribute = typeAttributes.TryGetIfAssignableTo("JsonConverterAttribute", TypeNameStyle.Name);
dynamic jsonConverterAttribute = typeAttributes.TryGetIfAssignableTo(nameof(JsonConverterAttribute), TypeNameStyle.Name);
if (jsonConverterAttribute != null)
{
var converterType = (Type)jsonConverterAttribute.ConverterType;
if (converterType.Name == "JsonInheritanceConverter")
if (converterType.IsAssignableTo(nameof(JsonInheritanceConverter), TypeNameStyle.Name))
{
if (jsonConverterAttribute.ConverterParameters != null && jsonConverterAttribute.ConverterParameters.Length > 0)
return jsonConverterAttribute.ConverterParameters[0];
return JsonInheritanceConverter.DefaultDiscriminatorName;
return jsonConverterAttribute.ConverterParameters != null && jsonConverterAttribute.ConverterParameters.Length > 0 ?
Activator.CreateInstance(jsonConverterAttribute.ConverterType, jsonConverterAttribute.ConverterParameters) :
Activator.CreateInstance(jsonConverterAttribute.ConverterType);
}
}

return null;
}

private string TryGetInheritanceDiscriminatorName(dynamic jsonInheritanceConverter)
{
if (ReflectionExtensions.HasProperty(jsonInheritanceConverter, nameof(JsonInheritanceConverter.DiscriminatorName)))
return jsonInheritanceConverter.DiscriminatorName;

return JsonInheritanceConverter.DefaultDiscriminatorName;
}

private void LoadEnumerations(Type type, JsonSchema4 schema, JsonTypeDescription typeDescription)
{
schema.Type = typeDescription.Type;
Expand Down
30 changes: 30 additions & 0 deletions src/NJsonSchema/OpenApiDiscriminator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
// <author>Rico Suter, mail@rsuter.com</author>
//-----------------------------------------------------------------------

using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Reflection;
using NJsonSchema.Infrastructure;

namespace NJsonSchema
{
Expand All @@ -21,5 +24,32 @@ public class OpenApiDiscriminator
/// <summary>Gets or sets the discriminator mappings.</summary>
[JsonProperty("mapping", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public IDictionary<string, JsonSchema4> Mapping { get; } = new Dictionary<string, JsonSchema4>();

/// <summary>The currently used <see cref="JsonInheritanceConverter"/>.</summary>
[JsonIgnore]
public object JsonInheritanceConverter { get; set; }

/// <summary>Adds a discriminator mapping for the given type and schema based on the used <see cref="JsonInheritanceConverter"/>.</summary>
/// <param name="type">The type.</param>
/// <param name="schema">The schema.</param>
public void AddMapping(Type type, JsonSchema4 schema)
{
dynamic converter = JsonInheritanceConverter;

var getDiscriminatorValueMethod = JsonInheritanceConverter.GetType()
#if LEGACY
.GetMethod(nameof(Converters.JsonInheritanceConverter.GetDiscriminatorValue), new Type[] { typeof(Type) });
#else
.GetRuntimeMethod(nameof(Converters.JsonInheritanceConverter.GetDiscriminatorValue), new Type[] { typeof(Type) });
#endif

if (getDiscriminatorValueMethod != null)
{
var discriminatorValue = converter.GetDiscriminatorValue(type);
Mapping[discriminatorValue] = new JsonSchema4 { Reference = schema.ActualSchema };
}
else
Mapping[type.Name] = new JsonSchema4 { Reference = schema.ActualSchema };
}
}
}

0 comments on commit 4b2c224

Please sign in to comment.