我正在使用Newtonsoft的Json.NET架构生成器,我想生成一个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]
,因为我需要在某些地方序列化这些属性,但我只希望在创建模式时隐藏它们。
有什么想法可以实现吗?
关键字"options": { "hidden": true }
甚至"hidden": true
似乎不在当前JSONSchema规范或任何早期版本的验证关键字中。唯一看起来相关的关键字是readOnly
和writeOnly
。来自文档:
草案7新增布尔关键字
readOnly
和writeOnly
通常用于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模式标准的某种自定义或第三方扩展。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
}
}
}
在这里演示小提琴。
我不知道Newtonsoft,但JsonSchema.Net.Generation可以通过内置的[JsonIgnore]
属性轻松做到这一点。这个模式库是建立在System.Text.Json
之上的。
我显然需要证明这是专门支持的,但以下是库的其余文档。我确实有一个测试(第168行(确认它有效。
这是对@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));
。非常感谢!