使用Newtonsoft.Json.Schema.Generation按Category生成自定义JSON Schema
基于Newtonsoft.Json.Schema实现按Category属性分组生成JSON Schema
我们使用Newtonsoft.Json.Schema.Generation(C# .NET 5.0)时,默认生成的JSON Schema是平级结构,但需求是根据类属性上的Category特性,将属性按类别分组嵌套(比如Student、Staff作为子对象,Common直接放在根层级)。要实现这个自定义逻辑,需要编写自定义的Schema生成提供者来拦截并修改生成过程。
实现步骤
1. 自定义Schema生成提供者
创建继承自JSchemaGenerationProvider的类,手动按Category分组构建嵌套的Schema结构:
using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Schema; using Newtonsoft.Json.Schema.Generation; using System.ComponentModel; public class CategoryGroupedSchemaProvider : JSchemaGenerationProvider { public override JSchema GenerateSchema(Type type, JSchemaGenerator generator, JSchemaResolver resolver) { // 仅处理目标类型(如School),其他类型沿用默认生成逻辑 if (type != typeof(School)) { return generator.GenerateSchema(type, resolver); } // 初始化根Schema var rootSchema = new JSchema { Type = JSchemaType.Object, AdditionalProperties = false }; // 按Category属性对类的属性分组 var propertiesGrouped = type.GetProperties() .Select(prop => new { Property = prop, Category = prop.GetCustomAttributes(typeof(CategoryAttribute), false) .Cast<CategoryAttribute>() .FirstOrDefault()?.Category ?? "Uncategorized" }) .GroupBy(item => item.Category); foreach (var categoryGroup in propertiesGrouped) { if (categoryGroup.Key == "Common") { // Common类别属性直接添加到根Schema foreach (var propItem in categoryGroup) { var propSchema = generator.GenerateSchema(propItem.Property.PropertyType, resolver); // 读取JsonProperty特性指定的属性名,无则用原属性名 var jsonPropAttr = propItem.Property.GetCustomAttributes(typeof(JsonPropertyAttribute), false) .Cast<JsonPropertyAttribute>() .FirstOrDefault(); string propName = jsonPropAttr?.PropertyName ?? propItem.Property.Name; // 判断是否需要加入required数组 bool isRequired = jsonPropAttr?.Required == Required.Always || (jsonPropAttr?.Required != Required.AllowNull && propItem.Property.PropertyType.IsValueType && Nullable.GetUnderlyingType(propItem.Property.PropertyType) == null); if (isRequired) { rootSchema.Required.Add(propName); } rootSchema.Properties.Add(propName, propSchema); } } else { // 非Common类别创建子对象Schema var categorySubSchema = new JSchema { Type = JSchemaType.Object, AdditionalProperties = false }; foreach (var propItem in categoryGroup) { var propSchema = generator.GenerateSchema(propItem.Property.PropertyType, resolver); var jsonPropAttr = propItem.Property.GetCustomAttributes(typeof(JsonPropertyAttribute), false) .Cast<JsonPropertyAttribute>() .FirstOrDefault(); string propName = jsonPropAttr?.PropertyName ?? propItem.Property.Name; // 处理必填逻辑 bool isRequired = jsonPropAttr?.Required == Required.Always || (jsonPropAttr?.Required != Required.AllowNull && propItem.Property.PropertyType.IsValueType && Nullable.GetUnderlyingType(propItem.Property.PropertyType) == null); if (isRequired) { categorySubSchema.Required.Add(propName); } // 处理DefaultValue特性 var defaultValueAttr = propItem.Property.GetCustomAttributes(typeof(DefaultValueAttribute), false) .Cast<DefaultValueAttribute>() .FirstOrDefault(); if (defaultValueAttr != null) { propSchema.Default = Newtonsoft.Json.Linq.JToken.FromObject(defaultValueAttr.Value); } categorySubSchema.Properties.Add(propName, propSchema); } // 将子Schema添加到根,并标记为必填 rootSchema.Properties.Add(categoryGroup.Key, categorySubSchema); rootSchema.Required.Add(categoryGroup.Key); } } return rootSchema; } }
2. 使用自定义生成器生成Schema
注册自定义提供者并生成最终的JSON Schema:
var schemaGenerator = new JSchemaGenerator(); // 添加自定义生成提供者 schemaGenerator.GenerationProviders.Add(new CategoryGroupedSchemaProvider()); // 生成School类的Schema var schoolSchema = schemaGenerator.GenerateSchema(typeof(School)); // 输出格式化后的JSON string formattedSchema = schoolSchema.ToString(Newtonsoft.Json.Formatting.Indented); Console.WriteLine(formattedSchema);
关键逻辑说明
- 分组逻辑:通过反射读取每个属性的
CategoryAttribute,将属性按类别分组处理。 - Common类别处理:直接将属性挂载到根Schema的
properties下,符合示例需求。 - 子对象构建:为
Student、Staff等类别创建独立的对象Schema,作为根Schema的子属性。 - 特性兼容:自动处理
JsonProperty(别名)、Required(必填判断)、DefaultValue(默认值)、Range(范围约束,默认生成器已处理)等特性。 - 必填规则:值类型默认视为必填(除非标记
Required.AllowNull),引用类型根据JsonProperty的Required配置判断。
生成的Schema结构将完全符合你给出的预期格式,对应的JSON数据也能正常匹配该Schema进行校验。
内容的提问来源于stack exchange,提问作者Developer
相关产品推荐
相关产品推荐

