using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.ComponentModel;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
public class CategoryOnlyContractResolver : DefaultContractResolver
readonly bool alwaysIncludeConstructorParameters = false;
readonly string category;
public CategoryOnlyContractResolver(string category, bool alwaysIncludeConstructorParameters = false) : base()
this.category = category;
this.alwaysIncludeConstructorParameters = alwaysIncludeConstructorParameters;
protected override JsonObjectContract CreateObjectContract(Type objectType)
var contract = base.CreateObjectContract(objectType);
for (int i = contract.Properties.Count - 1; i >= 0; i--)
var p = contract.Properties[i];
if (!p.AttributeProvider.GetAttributes(typeof(System.ComponentModel.CategoryAttribute), true).Cast<CategoryAttribute>().Any(c => c.Category == category)
|| (alwaysIncludeConstructorParameters && contract.CreatorParameters.GetClosestMatchProperty(p.PropertyName) != null))
contract.Properties.RemoveAt(i);
public School() { this.Id = 10; }
public string StudentName { get; set; }
[Range(0, 1000, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int Id { get; set; }
[DisplayName("ParentName")]
public string ParentName { get; set; }
[JsonProperty("ParentPhone", Required = Required.Default)]
[PhoneMask("999-999-9999",
ErrorMessage = "{0} value does not match the mask {1}.")]
public string TeacherName { get; set; }
[Range(0, 10000, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int Salary { get; set; }
[ReadOnly(true)] [Category("Student")] public bool AvailingTransport { get; set; } = true;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class PhoneMaskAttribute : ValidationAttribute
public PhoneMaskAttribute(string mask)
public override bool IsValid(object value)
var phoneNumber = (String)value;
result = MatchesMask(this.Mask, phoneNumber);
internal bool MatchesMask(string mask, string phoneNumber)
if (mask.Length != phoneNumber.Trim().Length)
for (int i = 0; i < mask.Length; i++)
if (mask[i] == 'd' && char.IsDigit(phoneNumber[i]) == false)
if (mask[i] == '-' && phoneNumber[i] != '-')
public override string FormatErrorMessage(string name)
return String.Format(CultureInfo.CurrentCulture,
ErrorMessageString, name, this.Mask);
public static void Test()
var studentGenerator = new JSchemaGenerator();
studentGenerator.ContractResolver = new CategoryOnlyContractResolver("Student");
var studentSchema = studentGenerator.Generate(typeof(School));
Console.WriteLine("Student Only schema for {0}:", typeof(School));
Console.WriteLine(studentSchema);
var defaultGenerator = new JSchemaGenerator();
var defaultSchema = defaultGenerator.Generate(typeof(School));
Console.WriteLine("\nDefault schema for {0}:", typeof(School));
Console.WriteLine(defaultSchema);
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Json.NET Schema version: " + typeof(Newtonsoft.Json.Schema.JSchema).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");