【问题标题】:Adding options property while generating json schema using JSchemaGenerator在使用 JSchemaGenerator 生成 json 模式时添加选项属性
【发布时间】:2021-07-09 14:09:11
【问题描述】:

我正在使用 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"
    }
  }
} 

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

问题是JSchema 类没有Hidden 属性(就像之前的JsonSchema 类一样)也没有Options 属性。

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

任何想法如何实现这一目标?

【问题讨论】:

  • 使用过时的JsonSchema,我无法创建您在问题中显示的架构。我得到的是"hidden": true,,而不是"options": { "hidden": true },,请参阅dotnetfiddle.net/OyB9b3。您能否请edit 分享您的问题minimal reproducible example,展示您如何使用旧的JsonSchema 生成示例架构?
  • 如果我尝试使用JsonSchema 解析您的问题中显示的架构,"options": { "hidden": true }, 属性将被静默剥离,请参阅dotnetfiddle.net/vnHNxb

标签: c# json json.net jsonschema


【解决方案1】:

我不了解 Newtonsoft,但 JsonSchema.Net.Generation 可以通过内置的 [JsonIgnore] 属性轻松完成此操作。此架构库构建在 System.Text.Json 之上。

我显然需要记录一下,这是特别支持的,但这是库的 docs 的其余部分。我确实有一个test (line 168) 确认它有效。

【讨论】:

  • 感谢您的回复,但是我不想使用[JsonIgnore],因为我需要在某些地方序列化这些字段。我已经更新了我原来的问题。
  • 有趣的用例。如果您愿意,请在我的 repo 上打开一个问题,以便我可以跟踪它,我会尝试添加它。我们也可以在那里进行更多讨论。
  • 我已经为它开了一个issue。如果你加入讨论,我会很高兴。
  • 谢谢,但是我无法更改我们用于生成模式的包。公司政策?‍♀️。这个用例确实很有趣,因为作为模式基础的类必须是完全可序列化的,因为我们需要系统的所有属性才能工作。当我们想要为不需要了解配置中所有内容的用户显示配置时,情况会发生变化。这可能应该有两个不同的类,但目前情况就是这样 :) 无论如何,感谢您的时间和帮助
  • 无论如何这是一个很好的建议,我已将它添加到我的库中。如果您想放弃 Newtonsoft 并升级到 System.Text.Json,这就是您要走的路。
【解决方案2】:

关键字"options": { "hidden": true } 甚至"hidden": true 似乎不在当前JSON Schema specificationvalidation keywords 中——或者据我所知的任何早期版本。唯一看起来相关的关键字是readOnlywriteOnly。来自docs

草案 7 中的新功能 布尔关键字 readOnlywriteOnly 通常用于 API 上下文。 readOnly 表示不应修改值。它可用于指示更改值的PUT 请求将导致400 Bad Request 响应。 writeOnly 表示可以设置一个值,但会保持隐藏。 In 可用于指示您可以使用 PUT 请求设置值,但在使用 GET 请求检索该记录时不会包含该值。

{
  "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 Schema 标准的某种自定义或第 3 方扩展。 Json.NET 架构通过 JSchema.ExtensionData 属性支持此类自定义验证关键字。要在自动模式生成期间在此扩展数据中设置隐藏选项,请定义以下 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
    }
  }
}

演示小提琴here.

【讨论】:

    【解决方案3】:

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

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

    由于context.Generator.ContractResolver.ResolveContract(context.ObjectType); 正在返回JsonPrimitiveContract,因此无法转换为JsonObjectContrac,因此引发异常。我不想花太多时间解决这个问题,因此我继续编写代码并尝试执行 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 在大多数情况下为空。我注意到这个自定义提供程序不仅被调用了一次,而且对作为架构基类的一部分的每个属性都调用了一次,并且对基类本身调用了一次(基本上这个提供程序被调用了数百次)。所以我最终只是在我的班级中创建了模式,然后应用了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));。非常感谢!

    【讨论】:

    • 由于context.Generator.ContractResolver.ResolveContract(context.ObjectType); 正在返回JsonPrimitiveContract,因此不可能转换为JsonObjectContrac -- 这不应该发生,因为CanGenerateSchema 被覆盖以仅在以下情况下返回true合同实际上是JsonObjectContract 类型。复制我的代码时是否忽略了覆盖?
    • 由于schema.Properties 在大多数情况下为空,因此出现了另一个问题。 - 只要CanGenerateSchema 被覆盖,就不会发生这种情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-18
    • 2015-11-13
    • 2023-03-17
    • 2022-07-18
    • 2021-09-24
    • 2012-05-03
    • 1970-01-01
    相关资源
    最近更新 更多