Skip to content

Commit

Permalink
Add UniqueItems Attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
rfrerebe-stx committed May 5, 2020
1 parent 3408a72 commit f176a29
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Doc/Samples/Generation/GenerateWithJSchemaAttributes.aml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="GenerateWithJsonNetAttributes" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>This sample generates a new <codeEntityReference>T:Newtonsoft.Json.Schema.JSchema</codeEntityReference>
from a .NET type with JSchema serialization attributes.</para>
</introduction>
<section>
<title>Sample</title>
<content>
<code lang="cs" source="..\Src\Newtonsoft.Json.Schema.Tests\Documentation\Samples\Generation\GenerateWithJSchemaAttributes.cs" region="Types" title="Types" />
<code lang="cs" source="..\Src\Newtonsoft.Json.Schema.Tests\Documentation\Samples\Generation\GenerateWithJSchemaAttributes.cs" region="Usage" title="Usage" />
</content>
</section>
</developerConceptualDocument>
</topic>
1 change: 1 addition & 0 deletions Doc/doc.content
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Topic id="GenerateWithDescriptions" visible="True" isSelected="true" title="Generate with Descriptions" />
<Topic id="GenerateWithProvider" visible="True" title="Using JSchemaGenerationProvider" />
<Topic id="GenerateWithJsonNetAttributes" visible="True" title="Using Json.NET attributes" />
<Topic id="GenerateWithJSchemaAttributes" visible="True" title="Using JSchema attributes" />
<Topic id="GenerateWithDataAnnotations" visible="True" title="Using Data Annotation attributes" />
<Topic id="GenerateWithSchemaIdGenerationHandling" visible="True" title="Auto schema ID generation" />
<Topic id="CreateCustomProvider" visible="True" title="Create a custom JSchemaGenerationProvider" />
Expand Down
1 change: 1 addition & 0 deletions Doc/doc.shfbproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
<None Include="Samples\Load\JsonUrlSchemaResolverRelative.aml" />
<None Include="Samples\Load\JsonUrlSchemaResolverHttp.aml" />
<None Include="Samples\Generation\GenerateWithJsonNetAttributes.aml" />
<None Include="Samples\Generation\GenerateWithJSchemaAttributes.aml" />
<None Include="Samples\Generation\GenerateWithProvider.aml" />
<None Include="Samples\Generation\GenerateWithSchemaIdGenerationHandling.aml" />
<None Include="Samples\Generation\GenerateWithDataAnnotations.aml" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#region License
// Copyright (c) Newtonsoft. All Rights Reserved.
// License: https://raw.github.com/JamesNK/Newtonsoft.Json.Schema/master/LICENSE.md
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Schema.Generation;
using Newtonsoft.Json.Serialization;
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
using Assert = Newtonsoft.Json.Schema.Tests.XUnitAssert;
#else
using NUnit.Framework;
#endif

namespace Newtonsoft.Json.Schema.Tests.Documentation.Samples.Generation
{
[TestFixture]
public class GenerateWithJSchemaAttributes : TestFixtureBase
{
#region Types
public class Computer
{
// always require a string value
[UniqueItems]
public IEnumerable<string> DiskIds { get; set; }

public HashSet<string> ScreenIds { get; set; }

}
#endregion

[Test]
public void Example()
{
#region Usage
JSchemaGenerator generator = new JSchemaGenerator();

JSchema schema = generator.Generate(typeof(Computer));
//{
// "type": "object",
// "properties": {
// "DiskIds": {
// "type": [
// "array",
// "null"
// ],
// "items": {
// "type": [
// "string",
// "null"
// ]
// },
// "uniqueItems": true
// },
// "ScreenIds": {
// "type": [
// "array",
// "null"
// ],
// "items": {
// "type": [
// "string",
// "null"
// ]
// },
// "uniqueItems": true
// }
// },
// "required": [
// "DiskIds",
// "ScreenIds"
// ],
// "dependencies": {}
//}
#endregion

Assert.IsTrue(schema.Properties["DiskIds"].UniqueItems);
Assert.IsTrue(schema.Properties["ScreenIds"].UniqueItems);
}
}
}
46 changes: 46 additions & 0 deletions Src/Newtonsoft.Json.Schema.Tests/JSchemaGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,52 @@ public void GenerationProviderAttributeDerivedPlusConverterAttributeDerived()
Assert.IsNotNull(schema.Properties["Provider"]);
}

internal class UniqueItemsClassWithAttribute
{
[UniqueItems]
public IEnumerable<string> UniqueItemsProperty { get; set; }
}

internal class UniqueItemsClassWithHashSet
{
public HashSet<string> UniqueItemsProperty { get; set; }
}

internal class UniqueItemsClassWithoutAttribute
{
public IEnumerable<string> UniqueItemsProperty { get; set; }
}

[Test]
public void GenerateUniqueItemsWithAttribute()
{
JSchemaGenerator generator = new JSchemaGenerator();
JSchema schema = generator.Generate(typeof(UniqueItemsClassWithAttribute));

Assert.IsNotNull(schema.Properties["UniqueItemsProperty"]);
Assert.IsTrue(schema.Properties["UniqueItemsProperty"].UniqueItems);
}

[Test]
public void GenerateUniqueItemsWithHashSet()
{
JSchemaGenerator generator = new JSchemaGenerator();
JSchema schema = generator.Generate(typeof(UniqueItemsClassWithHashSet));

Assert.IsNotNull(schema.Properties["UniqueItemsProperty"]);
Assert.IsTrue(schema.Properties["UniqueItemsProperty"].UniqueItems);
}

[Test]
public void GenerateUniqueItemsWithoutAttribute()
{
JSchemaGenerator generator = new JSchemaGenerator();
JSchema schema = generator.Generate(typeof(UniqueItemsClassWithoutAttribute));

Assert.IsNotNull(schema.Properties["UniqueItemsProperty"]);
Assert.IsFalse(schema.Properties["UniqueItemsProperty"].UniqueItems);
}

internal class MyRootJsonClass
{
public Dictionary<string, BlockBase> Blocks { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ private void PopulateSchema(JSchema schema, JsonContract contract, JsonProperty
schema.Type = AddNullType(JSchemaType.Array, valueRequired);
schema.MinimumItems = AttributeHelpers.GetMinLength(memberProperty);
schema.MaximumItems = AttributeHelpers.GetMaxLength(memberProperty);
schema.UniqueItems = ReflectionUtils.IsISetType(nonNullableUnderlyingType) || AttributeHelpers.GetUniqueItems(memberProperty) ;

JsonArrayAttribute arrayAttribute = ReflectionUtils.GetAttribute<JsonArrayAttribute>(nonNullableUnderlyingType);

Expand Down
13 changes: 13 additions & 0 deletions Src/Newtonsoft.Json.Schema/Generation/UniqueItemsAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Newtonsoft.Json.Schema.Generation
{

/// <summary>
/// Instructs the <see cref="JSchemaGenerator"/> to add unique items is true to the member.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Parameter, AllowMultiple = false)]
public class UniqueItemsAttribute : Attribute
{
}
}
12 changes: 12 additions & 0 deletions Src/Newtonsoft.Json.Schema/Infrastructure/AttributeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal static class AttributeHelpers
private const string EmailAddressAttributeName = "System.ComponentModel.DataAnnotations.EmailAddressAttribute";
private const string StringLengthAttributeName = "System.ComponentModel.DataAnnotations.StringLengthAttribute";
private const string EnumDataTypeAttributeName = "System.ComponentModel.DataAnnotations.EnumDataTypeAttribute";
private const string UniqueItemsAttributeName = "Newtonsoft.Json.Schema.Generation.UniqueItemsAttribute";

private static bool GetDisplay(Type type, JsonProperty memberProperty, out string name, out string description)
{
Expand Down Expand Up @@ -288,6 +289,17 @@ public static string GetFormat(JsonProperty property)
return null;
}

public static bool GetUniqueItems(JsonProperty property)
{
if (property == null)
{
return false;
}

var uniqueItems = GetAttributeByName(property, UniqueItemsAttributeName, out _);
return uniqueItems != null && ReflectionUtils.IsCollectionItemType(property.PropertyType);
}

private static Attribute GetAttributeByName(JsonProperty property, string name, out Type matchingType)
{
return GetAttributeByName(property.AttributeProvider, name, out matchingType);
Expand Down
62 changes: 62 additions & 0 deletions Src/Newtonsoft.Json.Schema/Infrastructure/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,68 @@ public static bool IsNullableType(Type t)
return (t.IsGenericType() && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}

/// <summary>
/// Gets if the type is a collection.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>True if the type is a collection</returns>
public static bool IsCollectionItemType(Type type)
{
ValidationUtils.ArgumentNotNull(type, nameof(type));

if (type.IsArray)
{
return true;
}

if (ImplementsGenericDefinition(type, typeof(IEnumerable<>), out Type genericListType))
{
if (genericListType.IsGenericTypeDefinition())
{
return false;
}

return true;
}

if (typeof(IEnumerable).IsAssignableFrom(type))
{
return true;
}

return false;
}

/// <summary>
/// Gets if the type is a ISet.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>True if the type is a collection</returns>
public static bool IsISetType(Type type)
{
#if NET35
return false;
#else
ValidationUtils.ArgumentNotNull(type, nameof(type));
foreach (Type i in type.GetInterfaces())
{
if (i.IsGenericType())
{
Type interfaceDefinition = i.GetGenericTypeDefinition();

if (typeof(ISet<>) == interfaceDefinition)
{
return true;
}
}
}

return false;
#endif
}



/// <summary>
/// Gets the type of the typed collection's items.
/// </summary>
Expand Down

0 comments on commit f176a29

Please sign in to comment.