使用 JSchemaGenerator 在生成 json 架构时添加选项 属性

Adding options property while generating json schema using JSchemaGenerator

我正在使用 Newtonsoft 的 Json.NET Schema 模式生成器,我想生成一个 JSON 模式并隐藏几个字段。我知道使用 options 属性 是可能的。这是使用此 属性.

的示例模式
{
  "title": "Person",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "options": { "hidden": true },
      "description": "First and Last name",
      "minLength": 4,
      "default": "Jeremy Dorn"
    }
  }
} 

我有一个 class 作为模式的基础,我决定对我想在模式生成期间隐藏的属性使用自定义属性。然后使用自定义 GenerationProvider 我想检查该字段是否具有该属性,如果有则添加 "options": { "hidden": true }, 位。

问题是 JSchema class 没有 Hidden 属性(就像之前的 JsonSchema class)也不是 Options 属性.

注意:我不想使用 [JsonIgnore],因为我需要在某些地方序列化这些属性,但我只想在创建模式时隐藏它们。

有什么实现方法吗?

我不知道 Newtonsoft,但是 JsonSchema.Net.Generation 可以使用内置的 [JsonIgnore] 属性轻松地做到这一点。这个模式库建立在 System.Text.Json.

之上

我显然需要证明这是特别受支持的,但这里是 docs for the library. I do have a test (line 168) 确认它有效的其余部分。

关键字 "options": { "hidden": true } 甚至 "hidden": true 似乎不在 validation keywords for the current JSON Schema specification -- or any earlier version as far as I can tell. The only keywords that seem related are readOnly and writeOnly. From the docs:

New in draft 7 The boolean keywords readOnly and writeOnly are typically used in an API context. readOnly indicates that a value should not be modified. It could be used to indicate that a PUT request that changes a value would result in a 400 Bad Request response. writeOnly indicates that a value may be set, but will remain hidden. In could be used to indicate you can set a value with a PUT request, but it would not be included when retrieving that record with a GET request.

{
  "title": "Match anything",
  "description": "This is a schema that matches anything.",
  "default": "Default value",
  "examples": [
    "Anything",
    4035
  ],
  "readOnly": true,
  "writeOnly": false 
}

因此,"options": { "hidden": true } 似乎是对 JSON 架构标准的某种自定义或第三方扩展。 Json.NET 架构通过 JSchema.ExtensionData property. To set your hidden option in this extension data during automatic schema generation, define the following JSchemaGenerationProvider:

支持此类自定义验证关键字
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class HiddenAttribute : System.Attribute
{
}

public class HiddenOptionProvider : CustomizedProviderBase
{
    public override JSchema GetSchema(JSchemaTypeGenerationContext context)
    {
        var schema = base.GetSchema(context);
        // Get the JsonObjectContract for this type.
        var contract = (JsonObjectContract)context.Generator.ContractResolver.ResolveContract(context.ObjectType);
        foreach (var propertySchema in schema.Properties)
        {
            // Find the corresponding JsonProperty from the contract resolver.
            var jProperty = contract.Properties[propertySchema.Key];
            // Check to see if the member has HiddenAttribute set.
            if (jProperty.AttributeProvider.GetAttributes(typeof(HiddenAttribute), true).Any())
                // If so add "options": { "hidden": true }
                propertySchema.Value.ExtensionData["options"] = new JObject(new JProperty("hidden", true));
        }
        
        return schema;
    }

    public override bool CanGenerateSchema(JSchemaTypeGenerationContext context) =>
        base.CanGenerateSchema(context) && context.Generator.ContractResolver.ResolveContract(context.ObjectType) is JsonObjectContract;
}

public abstract class CustomizedProviderBase : JSchemaGenerationProvider
{
    // Base class that allows generation of a default schema which may then be subsequently customized. 
    // Note this class contains state information and so is not thread safe.
    readonly Stack<Type> currentTypes = new ();

    public override JSchema GetSchema(JSchemaTypeGenerationContext context)
    {
        if (CanGenerateSchema(context))
        {
            var currentType = context.ObjectType;
            try
            {
                currentTypes.Push(currentType);
                return context.Generator.Generate(currentType);
            }
            finally
            {
                currentTypes.Pop();
            }
        }
        else
            throw new NotImplementedException();
    }
    
    public override bool CanGenerateSchema(JSchemaTypeGenerationContext context) => 
        !currentTypes.TryPeek(out var t) || t != context.ObjectType;
}

然后按如下方式定义您的 Person 类型:

[DisplayName("Person")]
public class Person
{
    [JsonProperty("name", Required = Required.DisallowNull)]
    [DefaultValue("Jeremy Dorn"), MinLength(4), System.ComponentModel.DescriptionAttribute("First and Last name")]
    [Hidden] // Your custom attribute
    public string Name { get; set; } = "Jeremy Dorn";
}

并生成如下模式:

var generator = new JSchemaGenerator();
generator.GenerationProviders.Add(new HiddenOptionProvider());
var schema = generator.Generate(typeof(Person));            

您将根据需要获得以下架构:

{
  "title": "Person",
  "type": "object",
  "properties": {
    "name": {
      "description": "First and Last name",
      "options": {
        "hidden": true
      },
      "type": "string",
      "default": "Jeremy Dorn",
      "minLength": 4
    }
  }
}

演示 fiddle here.

这是对@dbc 的相当长的回复,它帮助我完成了这项工作。由于我为其创建架构的 classes 非常大,并且其中包含不同类型的负载,因此我无法使用此解决方案。我在这里注意到的几件事。我正在使用 "Newtonsoft.Json.Schema" Version="3.0.14" 并在自定义提供程序中使用行

var contract = (JsonObjectContract)context.Generator.ContractResolver.ResolveContract(context.ObjectType);

抛出异常,因为 JsonObjectContrac 的转换是不可能的,因为 context.Generator.ContractResolver.ResolveContract(context.ObjectType); 正在返回 JsonPrimitiveContract。我不想花太多时间解决这个问题,因此我继续编写代码并尝试执行类似 dbc 在此代码中执行的操作:

foreach (var propertySchema in schema.Properties)
        {
            // Find the corresponding JsonProperty from the contract resolver.
            var jProperty = contract.Properties[propertySchema.Key];
            // Check to see if the member has HiddenAttribute set.
            if (jProperty.AttributeProvider.GetAttributes(typeof(HiddenAttribute), true).Any())
                // If so add "options": { "hidden": true }
                propertySchema.Value.ExtensionData["options"] = new JObject(new JProperty("hidden", true));
        }

另一个问题出现了,因为在大多数情况下 schema.Properties 是空的。我注意到这个自定义提供程序不仅被调用一次,而且每个 属性 都被调用,这是我的基础 class 的一部分,用于架构,一次为基础 class 本身(基本上这个提供者被调用了数百次)。所以我最终只是在 class 中创建了模式,然后应用了 ExtensionData。因此,我的提供者除了执行一些其他逻辑外,它还有 CheckIsHidden(JSchemaTypeGenerationContext context, JSchema schema) 方法来完成这项工作:

        public static void CheckIsHidden(JSchemaTypeGenerationContext context, JSchema schema)
        {
            var hiddenAttribute = context.MemberProperty?.AttributeProvider?.GetAttributes(true)
                ?.FirstOrDefault(a => a.GetType().Name == nameof(JsonConfigIgnoreAttribute));
            if (hiddenAttribute != null)
            {
                schema.ExtensionData["options"] = new JObject(new JProperty("hidden", true));
            }
        }

评论确实帮助我实现了这一点,因为我主要是在寻找这一行 schema.ExtensionData["options"] = new JObject(new JProperty("hidden", true));。非常感谢!