.NET 5中的System.Text.Json字段序列化在Swashbuckle API定义中未显示

16

问题

我正在使用ASP.NET Core和.NET 5,并使用System.Text.Json序列化器来序列化包含字段的类型(例如System.Numerics.Vector3(X,Y和Z是字段),尽管任何具有字段的类型在此处的行为相同)。

我已经通过Postman调用API验证了字段被正确序列化,但是由Swashbuckle生成的Swagger API定义并没有正确地反映出这一点。(该定义仅显示一个空类型)

重现

我创建了一个gist来重现这个问题。 它提供了一个HTTP Get方法/api/Test,返回一个类型为Test的对象,其中包含一个字段和一个属性。两者都是字符串。 通过Postman调用此API会返回两个值的正确值。 查看Swagger UI的/swagger/swagger/v1/swagger.json中的定义仅显示属性。

这种行为也适用于Swagger UI中的示例,它们仅包括属性。

期望的行为

根据文档,Swagger生成器应该自动复制 System.Text.Json 的行为,而 System.Text.Json 明确配置为序列化字段 (请参见第47行), 所以我期望 Swagger 定义会包含这个字段。

摘要

再次说明,我使用 System.Text.Json 序列化具有公共字段的类型。这很好用,我希望保持这种做法。

我尝试使用 Swashbuckle 生成返回这些序列化内容的 API 文档。但是只对属性有效,而不适用于字段。

是否还需要进行其他明确的配置才能使其工作?

2个回答

8

看起来 Swashbuckle 不使用 JsonSerializerOptions 来生成文档。我发现的一个解决办法是手动处理类型:

public class FieldsSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        var fields = context.Type.GetFields();

        if (fields == null) return;
        if (fields.Length == 0) return;

        foreach (var field in fields)
        {
            schema.Properties[field.Name] = new OpenApiSchema
            {
                // this should be mapped to an OpenApiSchema type
                Type = field.FieldType.Name
            };
        }
    }
}

然后在你的Startup.cs中的ConfigureServices方法中:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
    c.SchemaFilter<FieldsSchemaFilter>();
});

当进行步骤时,您将在SchemaFilterContextSchemaGenerator)中看到使用JsonSerializerOptionsIncludeFields设置为true。尽管如此,仅使用属性进行文档,因此我认为这样的过滤器是最好的选择。

2
酷的解决方案,之前不知道。谢谢。 - Maytham Fahmi

3
这个问题与Swagger无关,纯粹是序列化问题。
你有三种解决方案:
1.编写自己定制的向量JSON。(只是概念)
2.使用带基本类型的自定义对象并映射它。(只是概念)
3.使用Newtonsoft.Json (建议的解决方案)
关于Microsoft doc,可以在比较列表中看到System.Text.Json,它可能有一些限制。
如果您想要建议的解决方案,请直接跳转到解决方案3。
让我们来看看自定义serialized的第一个概念。顺便说一句,这个自定义示例只是为了演示,不是完整的解决方案。
所以你可以做以下事情:
  1. 创建一个自定义向量 CustomVector 模型。
  2. 创建一个自定义的 VectorConverter 类,继承自 JsonConverter
  3. 添加一些映射。
  4. 将属性 VectorConverter 放到向量属性中。

这是我的 CustomVector 尝试:

public class CustomVector
{
    public float? X { get; set; }
    public float? Y { get; set; }
    public float? Z { get; set; }
}

并自定义VectorConverter:

public class VectorConverter : JsonConverter<Vector3>
{
    public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // just for now
        return new Vector3();
    }

    public override void Write(Utf8JsonWriter writer, Vector3 data, JsonSerializerOptions options)
    {
        // just for now
        var customVector = new CustomVector
        {
            X = data.X,
            Y = data.Y,
            Z = data.Z
        };

        var result = JsonSerializer.Serialize(customVector);

        writer.WriteStringValue(result);
    }
}

并且您的矢量属性,添加了以下属性:

[JsonConverter(typeof(VectorConverter))]
public Vector3 Vector { get; set; }

这将返回以下结果:

enter image description here

现在这解决了部分问题,如果您想发布矢量对象,您将面临另一个挑战,这也取决于您的实现逻辑。
因此,我尝试了第二个解决方案,在这里我们公开了我们的自定义向量并忽略了json中的vector3,并将其映射到/从我们的代码中的Vector3:
因此,我们引入了一个CustomVector,我们可以在模型中使用它代替Vector3,然后将其映射到我们的Vector3。
public class Test
{
    public string Field { get; set; }
    public string Property { get; set; }
    [JsonIgnore]
    public Vector3 Vector { get; set; }
    public CustomVector CustomVector { get; set; }
}

enter image description here

这是一个带有映射的GET和POST方法示例:
[HttpGet]
public Test Get()
{
    var vector = new CustomVector() { X = 1, Y = 1, Z = 1 };
    var test = new Test
    {
        Field = "Field",
        Property = "Property",
        CustomVector = vector
    };
    VectorMapping(test);
    return test;
}

[HttpPost]
public Test Post(Test test)
{
    VectorMapping(test);
    return test;
}

private static void VectorMapping(Test test)
{
    test.Vector = new Vector3
    {
        X = test.CustomVector.X.GetValueOrDefault(),
        Y = test.CustomVector.Y.GetValueOrDefault(),
        Z = test.CustomVector.Z.GetValueOrDefault()
    };
}

第一种解决方案的缺点是需要编写完整自定义序列化,而在第二种解决方案中,我们引入了额外的模型和映射。
建议的解决方案是,进行第三次尝试:
保留您的现有解决方案不变,只需向您的项目添加NuGet Swashbuckle.AspNetCore.Newtonsoft ,例如:
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.6.3" />

而在你的创业公司中

services.AddSwaggerGenNewtonsoftSupport();

启动,这将生成文档,因为它允许序列化和反序列化Vector3和其他不受System.Text.Json支持的类类型。

正如您所看到的,现在文档中包括了Vector3:

enter image description here

我相信还有其他方法可以实现这个,所以这是我尝试解决它的方式。


1
这些字段已经正确序列化了(.NET 5支持字段序列化,尽管您需要先启用它),问题在于Swagger没有正确生成文档。 - blenderfreaky
感谢您的反馈。我认为您的问题中有一些不清楚的内容,您提到了Vector3的问题,但在您的代码片段中并没有出现,所以我只是根据我的理解来处理这个问题。如果您能更新代码片段并展示期望的文档样式,那么我就可以看到并更新我的回答。 - Maytham Fahmi
@blenderfreaky,我已经添加了第三个建议的解决方案,并正确地添加了文档,希望能对你有所帮助。 - Maytham Fahmi
我不明白为什么需要Newtonsoft,Vector3和其他包含字段的类型受到System.Text.Json的支持(在代码片段中明确启用,参见line 47)。您提到的文档也指出支持字段 - blenderfreaky
@blenderfreaky 我并不反对你的说法,它可能仍存在某些漏洞或其他超出提供系统.text.json问题的范围的问题。在许多情况下,我也使用system.text.json,在一些继承对象的情况下,我们也会遇到难以解释的问题。但我仍认为最好的选择是最后一个选项(IMO)。 - Maytham Fahmi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接