You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 12:35:07