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

Custom extension x-enumMetaData to expose title and description for an enum value. #1383

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 43 additions & 2 deletions src/NJsonSchema.Tests/Generation/EnumTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
Expand Down Expand Up @@ -107,9 +109,48 @@ public async Task When_SerializerSettings_has_CamelCase_StringEnumConverter_then
Assert.Equal("Value2", schema.EnumerationNames.Last());
}

[Flags]
public enum EnumWithFlags
[Fact]
public async Task When_ComponentModel_Attributes_are_not_used_meta_data_has_only_the_names()
{
// Arrange

// Act
var schema = JsonSchema.FromType<MyEnum>();
var json = schema.ToJson();

// Assert
Assert.Equal("Value1", schema.EnumerationMetaData.First().Title);
Assert.Equal("Value2", schema.EnumerationMetaData.Last().Title);

Assert.Null(schema.EnumerationMetaData.First().Description);
Assert.Null(schema.EnumerationMetaData.Last().Description);
}

public enum MyEnumWithAttributes {
[Display(Name = "My name 1", Description = "My description 1")] Value1,
[Description("My description 2")]
Value2
}

[Fact]
public async Task When_ComponentModel_Attributes_are_used_on_enum_values_its_used_in_meta_data()
{
// Arrange

// Act
var schema = JsonSchema.FromType<MyEnumWithAttributes>();
var json = schema.ToJson();

// Assert
Assert.Equal("My name 1", schema.EnumerationMetaData.First().Title);
Assert.Equal("Value2", schema.EnumerationMetaData.Last().Title);

Assert.Equal("My description 1", schema.EnumerationMetaData.First().Description);
Assert.Equal("My description 2", schema.EnumerationMetaData.Last().Description);
}

[Flags]
public enum EnumWithFlags {
Foo = 1,
Bar = 2,
Baz = 4,
Expand Down
23 changes: 23 additions & 0 deletions src/NJsonSchema/Generation/EnumerationMetaData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Newtonsoft.Json;

namespace NJsonSchema.Generation {
/// <summary>
/// Additional meta data for enumerations.
/// </summary>
/// <remarks>
/// An enum value doesn't have a title or description associated with it in the JSON schema specification.
/// </remarks>
public class EnumerationMetaData {
/// <summary>
/// Surrogate for the "title" keyword as described in <see href="https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.9.1">the specification</see>.
/// </summary>
[JsonProperty("title", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 0)]
public string Title { get; set; }

/// <summary>
/// Surrogate for the "description" keyword as described in <see href="https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.9.1">the specification</see>.
/// </summary>
[JsonProperty("description", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 0)]
public string Description { get; set; }
}
}
18 changes: 10 additions & 8 deletions src/NJsonSchema/Generation/JsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,15 +351,10 @@ public virtual void ApplyDataAnnotations(JsonSchema schema, JsonTypeDescription
{
var contextualType = typeDescription.ContextualType;

dynamic displayAttribute = contextualType.ContextAttributes.FirstAssignableToTypeNameOrDefault("System.ComponentModel.DataAnnotations.DisplayAttribute");
if (displayAttribute != null)
var name = contextualType.GetDisplayName();
if (name != null)
{
// GetName returns null if the Name property on the attribute is not specified.
var name = displayAttribute.GetName();
if (name != null)
{
schema.Title = name;
}
schema.Title = name;
}

dynamic defaultValueAttribute = contextualType.ContextAttributes.FirstAssignableToTypeNameOrDefault("System.ComponentModel.DefaultValueAttribute");
Expand Down Expand Up @@ -720,6 +715,13 @@ protected virtual void GenerateEnum(JsonSchema schema, JsonTypeDescription typeD
}
}

var contextualFieldType = contextualType.GetField(enumName);
schema.EnumerationMetaData.Add(new EnumerationMetaData
{
Title = contextualFieldType?.GetDisplayName() ?? enumName,
Description = contextualFieldType?.GetDescription()
});

schema.EnumerationNames.Add(enumName);
}

Expand Down
9 changes: 9 additions & 0 deletions src/NJsonSchema/Infrastructure/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ private static string GetNameWithoutCache(ContextualMemberInfo member)
return member.Name;
}

/// <summary>Gets the name of the given member (based on the DisplayAttribute).</summary>
/// <returns>The name or null if no name is available.</returns>
public static string GetDisplayName(this ContextualType contextualType)
{
dynamic displayAttribute = contextualType.ContextAttributes.FirstAssignableToTypeNameOrDefault("System.ComponentModel.DataAnnotations.DisplayAttribute");
// GetName returns null if the Name property on the attribute is not specified.
return displayAttribute?.GetName();
}

/// <summary>Gets the description of the given member (based on the DescriptionAttribute, DisplayAttribute or XML Documentation).</summary>
/// <param name="type">The member info</param>
/// <param name="attributeType">The attribute type to check.</param>
Expand Down
13 changes: 13 additions & 0 deletions src/NJsonSchema/JsonSchema.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NJsonSchema.Collections;
using NJsonSchema.Generation;
using NJsonSchema.Infrastructure;

namespace NJsonSchema
Expand Down Expand Up @@ -131,6 +132,10 @@ internal object DiscriminatorRaw
/// <summary>Gets or sets the enumeration names (optional, draft v5). </summary>
[JsonIgnore]
public Collection<string> EnumerationNames { get; set; }

/// <summary>Gets or sets the enumeration meta data (custom extension, sets 'x-enumMetaData').</summary>
[JsonIgnore]
public Collection<EnumerationMetaData> EnumerationMetaData { get; set; }

/// <summary>Gets or sets a value indicating whether the maximum value is excluded. </summary>
[JsonProperty("exclusiveMaximum", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
Expand Down Expand Up @@ -395,6 +400,14 @@ internal Collection<string> EnumerationNamesRaw
set { EnumerationNames = value != null ? new ObservableCollection<string>(value) : new ObservableCollection<string>(); }
}

/// <summary>Gets or sets the enumeration meta data (custom extension, sets 'x-enumMetaData').</summary>
[JsonProperty("x-enumMetaData", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
internal Collection<EnumerationMetaData> EnumerationMetaDataRaw
{
get { return EnumerationMetaData != null && EnumerationMetaData.Count > 0 ? EnumerationMetaData : null; }
set { EnumerationMetaData = value != null ? new ObservableCollection<EnumerationMetaData>(value) : new ObservableCollection<EnumerationMetaData>(); }
}

[JsonProperty("enum", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
internal ICollection<object> EnumerationRaw
{
Expand Down
5 changes: 5 additions & 0 deletions src/NJsonSchema/JsonSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,11 @@ private void Initialize()
{
EnumerationNames = new Collection<string>();
}

if (EnumerationMetaData == null)
{
EnumerationMetaData = new Collection<EnumerationMetaData>();
}
}
}
}