diff --git a/Doc/Samples/Generation/GenerateWithJSchemaAttributes.aml b/Doc/Samples/Generation/GenerateWithJSchemaAttributes.aml
new file mode 100644
index 00000000..b0291022
--- /dev/null
+++ b/Doc/Samples/Generation/GenerateWithJSchemaAttributes.aml
@@ -0,0 +1,16 @@
+
+
+
+
+ This sample generates a new T:Newtonsoft.Json.Schema.JSchema
+ from a .NET type with JSchema serialization attributes.
+
+
+
+
\ No newline at end of file
diff --git a/Doc/doc.content b/Doc/doc.content
index 684b5444..8dfeb628 100644
--- a/Doc/doc.content
+++ b/Doc/doc.content
@@ -35,6 +35,7 @@
+
diff --git a/Doc/doc.shfbproj b/Doc/doc.shfbproj
index 67b8340d..27267cf9 100644
--- a/Doc/doc.shfbproj
+++ b/Doc/doc.shfbproj
@@ -110,6 +110,7 @@
+
diff --git a/Src/Newtonsoft.Json.Schema.Tests/Documentation/Samples/Generation/GenerateWithJSchemaAttributes.cs b/Src/Newtonsoft.Json.Schema.Tests/Documentation/Samples/Generation/GenerateWithJSchemaAttributes.cs
new file mode 100644
index 00000000..033c7262
--- /dev/null
+++ b/Src/Newtonsoft.Json.Schema.Tests/Documentation/Samples/Generation/GenerateWithJSchemaAttributes.cs
@@ -0,0 +1,71 @@
+#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 DiskIds { get; set; }
+
+ public HashSet 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",
+ // "items": {
+ // "type": "string",
+ // },
+ // "uniqueItems": true
+ // },
+ // "ScreenIds": {
+ // "type": "array",
+ // "items": {
+ // "type": "string",
+ // },
+ // "uniqueItems": true
+ // }
+ // }
+ //}
+ #endregion
+
+ Assert.IsTrue(schema.Properties["DiskIds"].UniqueItems);
+#if !NET35
+ Assert.IsTrue(schema.Properties["ScreenIds"].UniqueItems);
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/Src/Newtonsoft.Json.Schema.Tests/JSchemaGeneratorTests.cs b/Src/Newtonsoft.Json.Schema.Tests/JSchemaGeneratorTests.cs
index 7e2e2c90..eb09f6a6 100644
--- a/Src/Newtonsoft.Json.Schema.Tests/JSchemaGeneratorTests.cs
+++ b/Src/Newtonsoft.Json.Schema.Tests/JSchemaGeneratorTests.cs
@@ -1798,6 +1798,70 @@ public void GenerationProviderAttributeDerivedPlusConverterAttributeDerived()
Assert.IsNotNull(schema.Properties["Provider"]);
}
+ internal class UniqueItemsClassWithAttribute
+ {
+ [UniqueItems]
+ public IEnumerable UniqueItemsProperty { get; set; }
+ }
+
+ internal class UniqueItemsClassWithHashSet
+ {
+ public HashSet UniqueItemsProperty { get; set; }
+ }
+
+ internal class UniqueItemsClassWithoutAttribute
+ {
+ public IEnumerable UniqueItemsProperty { get; set; }
+ }
+
+ internal class UniqueItemsClassWithAttributeButWithoutCollection
+ {
+ [UniqueItems]
+ public 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);
+ }
+
+#if !NET35
+ [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);
+ }
+#endif
+
+ [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);
+ }
+
+ [Test]
+ public void GenerateUniqueItemsWithtAttributeButWithoutCollection()
+ {
+ JSchemaGenerator generator = new JSchemaGenerator();
+ JSchema schema = generator.Generate(typeof(UniqueItemsClassWithAttributeButWithoutCollection));
+
+ Assert.IsNotNull(schema.Properties["UniqueItemsProperty"]);
+ Assert.IsFalse(schema.Properties["UniqueItemsProperty"].UniqueItems);
+ }
+
internal class MyRootJsonClass
{
public Dictionary Blocks { get; set; }
diff --git a/Src/Newtonsoft.Json.Schema/Generation/JSchemaGeneratorInternal.cs b/Src/Newtonsoft.Json.Schema/Generation/JSchemaGeneratorInternal.cs
index 56dfca01..fcf7bee5 100644
--- a/Src/Newtonsoft.Json.Schema/Generation/JSchemaGeneratorInternal.cs
+++ b/Src/Newtonsoft.Json.Schema/Generation/JSchemaGeneratorInternal.cs
@@ -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(nonNullableUnderlyingType);
diff --git a/Src/Newtonsoft.Json.Schema/Generation/UniqueItemsAttribute.cs b/Src/Newtonsoft.Json.Schema/Generation/UniqueItemsAttribute.cs
new file mode 100644
index 00000000..15babd04
--- /dev/null
+++ b/Src/Newtonsoft.Json.Schema/Generation/UniqueItemsAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Newtonsoft.Json.Schema.Generation
+{
+
+ ///
+ /// Instructs the to add unique items is true to the member.
+ ///
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Parameter, AllowMultiple = false)]
+ public class UniqueItemsAttribute : Attribute
+ {
+ }
+}
diff --git a/Src/Newtonsoft.Json.Schema/Infrastructure/AttributeHelpers.cs b/Src/Newtonsoft.Json.Schema/Infrastructure/AttributeHelpers.cs
index 8b409459..153599ef 100644
--- a/Src/Newtonsoft.Json.Schema/Infrastructure/AttributeHelpers.cs
+++ b/Src/Newtonsoft.Json.Schema/Infrastructure/AttributeHelpers.cs
@@ -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)
{
@@ -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);
diff --git a/Src/Newtonsoft.Json.Schema/Infrastructure/ReflectionUtils.cs b/Src/Newtonsoft.Json.Schema/Infrastructure/ReflectionUtils.cs
index 8a4a4ab6..41d890f8 100644
--- a/Src/Newtonsoft.Json.Schema/Infrastructure/ReflectionUtils.cs
+++ b/Src/Newtonsoft.Json.Schema/Infrastructure/ReflectionUtils.cs
@@ -168,6 +168,66 @@ public static bool IsNullableType(Type t)
return (t.IsGenericType() && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
+ ///
+ /// Gets if the type is a collection.
+ ///
+ /// The type.
+ /// True if the type is a collection
+ 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;
+ }
+
+ ///
+ /// Checks if the type is an ISet.
+ ///
+ /// The type.
+ /// True if the type is an ISet
+ 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
+ }
+
///
/// Gets the type of the typed collection's items.
///