基于Category属性筛选生成JSON Schema的实现(Newtonsoft JSON)
基于Newtonsoft JSON生成指定Category属性成员的JSON Schema
我们使用Newtonsoft JSON(C#,.NET Framework 4.5),希望仅为类中标记了指定Category属性的成员生成JSON Schema。例如下面的School类,我们只需要生成Category为"Student"的成员对应的Schema:
class School { [Editable(false)] [DisplayName("Student")] [Category("Student")] public string StudentName { get; set; } [Editable(true)] [Range(0, 1000, ErrorMessage = "Value for {0} must be between {1} and {2}.")] [Category("Student")] public int Id { get; set; } = 10; [Editable(false)] [DisplayName("ParentName")] [Category("Student")] public string ParentName { get; set; } [JsonProperty("ParentPhone", Required = Required.Default)] [PhoneMask("999-999-9999", ErrorMessage = "{0} value does not match the mask {1}.")] [Category("Student")] public string Phone; [Editable(true)] [Category("Staff")] public string TeacherName { get; set; } [Editable(true)] [Range(0, 10000, ErrorMessage = "Value for {0} must be between {1} and {2}.")] [DefaultValue(5000)] [Category("Staff")] public int Salary { get; set; } [ReadOnly(true)] [Category("Student")] public bool AvailingTransport { get; set; } = true; }
我们尝试了以下代码,但未能生效:
abstract class SelectedAttributesSerializationProvider : JSchemaGenerationProvider { public override JSchema GetSchema(JSchemaTypeGenerationContext context) { var type = context.ObjectType; JSchema schema = null; var generator = new JSchemaGenerator(); PropertyInfo[] myMembers = type.GetProperties(); var categoryAttributes = myMembers.Where(c => c.CustomAttributes.Any(p => p.AttributeType.Name == "CategoryAttribute")); foreach(PropertyInfo categoryAttribute in categoryAttributes) { foreach(CustomAttributeData customAttributeData in categoryAttribute.GetCustomAttributesData()) { if(customAttributeData.AttributeType.Name == "CategoryAttribute") { generator.GenerationProviders.Add(this); } } } schema = generator.Generate(type); return ModifySchema(schema, context); } public abstract JSchema ModifySchema(JSchema schema, JSchemaTypeGenerationContext context); }
解决方案
正确的做法是通过自定义契约解析器(IContractResolver)筛选出符合Category要求的成员,再让Schema生成器基于筛选后的契约生成Schema。
步骤1:实现自定义契约解析器
这个解析器会过滤掉不包含目标Category属性的成员:
public class CategoryFilteredContractResolver : DefaultContractResolver { private readonly string _targetCategory; public CategoryFilteredContractResolver(string targetCategory) { _targetCategory = targetCategory; } protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { var allProperties = base.CreateProperties(type, memberSerialization); // 仅保留带有指定Category属性的成员 return allProperties.Where(property => { // 获取成员的元数据(属性或字段) var memberInfo = type.GetMember(property.UnderlyingName).FirstOrDefault(); if (memberInfo == null) return false; // 检查是否存在目标Category属性 var categoryAttribute = memberInfo.GetCustomAttribute<CategoryAttribute>(); return categoryAttribute != null && categoryAttribute.Category == _targetCategory; }).ToList(); } }
步骤2:使用解析器生成Schema
将自定义解析器绑定到JSchemaGenerator,然后生成目标类的Schema:
// 初始化Schema生成器 var schemaGenerator = new JSchemaGenerator(); // 设置自定义契约解析器,指定目标Category为"Student" schemaGenerator.ContractResolver = new CategoryFilteredContractResolver("Student"); // 生成School类的Schema JSchema studentSchema = schemaGenerator.Generate(typeof(School)); // 输出Schema内容 Console.WriteLine(studentSchema.ToString());
原代码失效原因
之前的实现存在两个核心问题:
- 在
GetSchema方法内新建JSchemaGenerator并添加自身到生成器的GenerationProviders,会导致循环调用,逻辑混乱。 - 没有真正过滤掉不符合要求的成员,只是遍历了属性的特性,没有对Schema生成过程做实际的筛选控制。
内容的提问来源于stack exchange,提问作者Developer
相关产品推荐
相关产品推荐

