为字典生成 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;
        }
    }