为字典生成 swagger<SomeEnum, string>
Generating swagger for Dictionary<SomeEnum, string>
我正在使用 Swashbuckle.AspNetCore.Swagger 生成 swagger 文档,然后使用 NSwag 生成 C# SDK。
我有几个 classes,我在其中使用 Dictionary 来保存杂项属性。
namespace Sample
{
/// <summary>Possible properties for MyClass1 objects</summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum PropEnum1
{
/// <summary>OpenAPI doesn't see this description</summary>
PropName1,
PropName2,
PropName3,
// and more property names
}
/// <summary>The first class...</summary>
public class MyClass1
{
public string Name { get; set; }
/// <summary>Properties that vary from instance to instance.</summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<PropEnum1, string> Props { get; set; }
}
// There is also a PropEnum2 and MyClass2, but let's stay simple
}
使用 Swashbuckle 6.14 我最终得到了这样的招摇:
"MyClass1": {
"type": "object",
"properties": {
"name": {
"type": "string",
"nullable": true
},
"props": {
"type": "object",
"properties": {
"PropName1": {
"type": "string"
},
"PropName2": {
"type": "string"
},
"PropName3": {
"type": "string"
}
},
"additionalProperties": false,
"description": "Properties that vary from instance to instance.",
"nullable": true
}
},
"additionalProperties": false,
"description": "The first class..."
}
然后 NSwag 生成一个如下所示的 C# 接口:
/// <summary>The first class...</summary>
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class MyClass1
{
[Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>Properties that vary from instance to instance.</summary>
[Newtonsoft.Json.JsonProperty("props", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public Props Props { get; set; }
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class Props
{
[Newtonsoft.Json.JsonProperty("PropName1", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string PropName1 { get; set; }
[Newtonsoft.Json.JsonProperty("PropName2", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string PropName2 { get; set; }
[Newtonsoft.Json.JsonProperty("PropName3", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string PropName3 { get; set; }
}
我发现 Props class 的生成对于每个可能的枚举值都有一个成员有点令人惊讶,但我可以接受它。
问题是 MyClass1 的实例可能不具有所有属性。我有一个 API returns MyClass1 的实例。它在 swagger 页面上工作,但使用 NSwag SDK 给我一个错误“Newtonsoft.Json.JsonSerializationException: Required 属性 'PropName2' expects a non-null value.”
眼前的问题是生成的 Props class 中的 DisallowNull。如果我将它们从 DisallowNull 手动编辑为 Default,SDK 将按预期工作。但是手工编辑生成的代码并不好。
DisallowNull 来自 swagger 中的 PropName 属性,没有将“可空”设置为 true。如果我暂停我的生成过程并将它们手动编辑到 swagger 中,我会在生成的 C# 代码中得到 Required.Default。但是手工编辑生成的swagger也不行。
所以解决它的一种方法是在 swagger 中为属性添加一个“可空”,但我不知道该怎么做。
或者有没有办法让 Swashbuckle 将 Props 字典更像 Dictionary?
从 this post 看来, Dictionary 似乎以这种方式处理,但这可能是旧的。
这是我最终得到的结果。它将类型转换为通用对象(其中属性必须是 valueType。请注意,我已经将枚举序列化为字符串,因此这可能不适合其他人。
/// <summary>
/// For properties that are Dictionary[SomeEnum, valueType] alter the schema
/// so the generated SDK code will be IDictionary[string, valueType].
/// </summary>
public class EnumDictionaryToStringDictionarySchemaFilter : ISchemaFilter
{
/// <summary>
/// Apply the schema changes
/// </summary>
/// <param name="schema">The schema model</param>
/// <param name="context">The schema filter context</param>
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
// Only for fields that are Dictionary<Enum, TValue>
//
if (!context.Type.IsGenericType)
return;
if (!context.Type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Dictionary<,>))
&& !context.Type.GetGenericTypeDefinition().IsAssignableFrom(typeof(IDictionary<,>)))
return;
var keyType = context.Type.GetGenericArguments()[0];
if (!keyType.IsEnum)
return;
var valueType = context.Type.GetGenericArguments()[1];
var valueTypeSchema = context.SchemaGenerator.GenerateSchema(valueType, context.SchemaRepository);
schema.Type = "object";
schema.Properties.Clear();
schema.AdditionalPropertiesAllowed = true;
schema.AdditionalProperties = valueTypeSchema;
}
}
我正在使用 Swashbuckle.AspNetCore.Swagger 生成 swagger 文档,然后使用 NSwag 生成 C# SDK。
我有几个 classes,我在其中使用 Dictionary
namespace Sample
{
/// <summary>Possible properties for MyClass1 objects</summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum PropEnum1
{
/// <summary>OpenAPI doesn't see this description</summary>
PropName1,
PropName2,
PropName3,
// and more property names
}
/// <summary>The first class...</summary>
public class MyClass1
{
public string Name { get; set; }
/// <summary>Properties that vary from instance to instance.</summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<PropEnum1, string> Props { get; set; }
}
// There is also a PropEnum2 and MyClass2, but let's stay simple
}
使用 Swashbuckle 6.14 我最终得到了这样的招摇:
"MyClass1": {
"type": "object",
"properties": {
"name": {
"type": "string",
"nullable": true
},
"props": {
"type": "object",
"properties": {
"PropName1": {
"type": "string"
},
"PropName2": {
"type": "string"
},
"PropName3": {
"type": "string"
}
},
"additionalProperties": false,
"description": "Properties that vary from instance to instance.",
"nullable": true
}
},
"additionalProperties": false,
"description": "The first class..."
}
然后 NSwag 生成一个如下所示的 C# 接口:
/// <summary>The first class...</summary>
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class MyClass1
{
[Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>Properties that vary from instance to instance.</summary>
[Newtonsoft.Json.JsonProperty("props", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public Props Props { get; set; }
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class Props
{
[Newtonsoft.Json.JsonProperty("PropName1", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string PropName1 { get; set; }
[Newtonsoft.Json.JsonProperty("PropName2", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string PropName2 { get; set; }
[Newtonsoft.Json.JsonProperty("PropName3", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string PropName3 { get; set; }
}
我发现 Props class 的生成对于每个可能的枚举值都有一个成员有点令人惊讶,但我可以接受它。
问题是 MyClass1 的实例可能不具有所有属性。我有一个 API returns MyClass1 的实例。它在 swagger 页面上工作,但使用 NSwag SDK 给我一个错误“Newtonsoft.Json.JsonSerializationException: Required 属性 'PropName2' expects a non-null value.”
眼前的问题是生成的 Props class 中的 DisallowNull。如果我将它们从 DisallowNull 手动编辑为 Default,SDK 将按预期工作。但是手工编辑生成的代码并不好。
DisallowNull 来自 swagger 中的 PropName 属性,没有将“可空”设置为 true。如果我暂停我的生成过程并将它们手动编辑到 swagger 中,我会在生成的 C# 代码中得到 Required.Default。但是手工编辑生成的swagger也不行。
所以解决它的一种方法是在 swagger 中为属性添加一个“可空”,但我不知道该怎么做。
或者有没有办法让 Swashbuckle 将 Props 字典更像 Dictionary
从 this post 看来, Dictionary
这是我最终得到的结果。它将类型转换为通用对象(其中属性必须是 valueType。请注意,我已经将枚举序列化为字符串,因此这可能不适合其他人。
/// <summary>
/// For properties that are Dictionary[SomeEnum, valueType] alter the schema
/// so the generated SDK code will be IDictionary[string, valueType].
/// </summary>
public class EnumDictionaryToStringDictionarySchemaFilter : ISchemaFilter
{
/// <summary>
/// Apply the schema changes
/// </summary>
/// <param name="schema">The schema model</param>
/// <param name="context">The schema filter context</param>
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
// Only for fields that are Dictionary<Enum, TValue>
//
if (!context.Type.IsGenericType)
return;
if (!context.Type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Dictionary<,>))
&& !context.Type.GetGenericTypeDefinition().IsAssignableFrom(typeof(IDictionary<,>)))
return;
var keyType = context.Type.GetGenericArguments()[0];
if (!keyType.IsEnum)
return;
var valueType = context.Type.GetGenericArguments()[1];
var valueTypeSchema = context.SchemaGenerator.GenerateSchema(valueType, context.SchemaRepository);
schema.Type = "object";
schema.Properties.Clear();
schema.AdditionalPropertiesAllowed = true;
schema.AdditionalProperties = valueTypeSchema;
}
}