JsonConverter 在集成测试中不起作用

问题描述

我已启用我的 API 以使用字符串值序列化/反序列化枚举。为此,我已将 JsonStringEnumConverter 添加到我的 API 启动类中受支持的 JsonConverters 列表中:

.AddJsonOptions(opts =>
{
    var enumConverter = new JsonStringEnumConverter();
    opts.JsonSerializerOptions.Converters.Add(enumConverter);
});

它工作正常 - 我的 API 成功地将枚举序列化和反序列化为字符串。

现在 - 我正在尝试为我的 API 构建集成测试,但遇到了一些问题。 我正在使用 HttpContentJsonExtensions.ReadFromJsonAsync 反序列化 API 响应,但在枚举属性上引发异常。

问题很明显 - HttpContentJsonExtensions.ReadFromJsonAsync 不知道 API 使用的转换器列表(因为,正如我之前提到的,我已将 JsonStringEnumConverter 添加到支持的转换器列表中,并且它工作正常)。

如果我在我的测试函数中这样做:

var options = new System.Text.Json.JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
SomeClass result= await response.Content.ReadFromJsonAsync<SomeClass>(options);

然后对 enum 属性进行反序列化,不会抛出异常。但是现在,ReadFromJsonAsync 只知道 JsonStringEnumConverter 而不是 API 使用的其他 JSON 转换器(如 Guid 转换器)

如何确保 HttpContentJsonExtensions.ReadFromJsonAsync 能够使用 API 使用的所有 JSON 转换器?

谢谢!

解决方法

不幸的是,没有办法开箱即用地实现这一点。原因是 System.Text.Json API 的“设计者”以令人难以置信的令人费解的举动决定使所述 API 成为静态的 - 可能是为了模仿众所周知的 Newtonsoft.Json - 但是静态 API 当然不能承载与他们一起陈述。有关更多上下文,我refer you to the feature request to fix this poor design

实际上,我在那个 FR 中想出了 a solution,自从我编造它以来,我已经对其进行了一些调整:

public interface IJsonSerializer
{
    JsonSerializerOptions Options { get; }

    Task<T> DeserializeAsync<T>(Stream utf8Json,CancellationToken cancellationToken);

    // other methods elided for brevity
}

public class DefaultJsonSerializer : IJsonSerializer
{
    private JsonSerializerOptions _options;
    public JsonSerializerOptions Options
        => new JsonSerializerOptions(_options); // copy constructor so that callers cannot mutate the options

    public DefaultJsonSerializer(IOptions<JsonSerializerOptions> options)
        => _options = options.Value;

    public async Task<T> DeserializeAsync<T>(Stream utf8Json,CancellationToken cancellationToken = default)
        => await JsonSerializer.DeserializeAsync<T>(utf8Json,Options,cancellationToken);

    // other methods elided for brevity
}

本质上,您定义了一个包装器接口,其方法签名与 JsonSerializer 的静态方法签名相同(减去 JsonSerializerOptions 参数,因为您想使用在类中定义的那些),然后是表示委托给静态 JsonSerializer 方法的接口。将接口映射到 Startup.ConfigureServices 中的实现,而不是在需要处理 JSON 的类中调用静态 JsonSerializer 方法,而是将 JSON 接口注入这些类并使用它。

鉴于您使用的是 HttpContentJsonExtensions,您还需要定义您自己的该扩展类的包装版本,该版本复制其方法签名,但将其 JsonSerializerOptions 参数替换为您的 JSON 序列化接口的实例,然后将所述接口的选项传递给底层 HttpContentJsonExtensions 实现:

public static class IJsonSerializerHttpContentJsonExtensions
{
    public static Task<object?> ReadFromJsonAsync(this HttpContent content,Type type,IJsonSerializer serializer,CancellationToken cancellationToken = default)
        => HttpContentJsonExtensions.ReadFromJsonAsync(content,type,serializer.Options,cancellationToken);

    // other methods elided for brevity
}

疼吗?是的。没有必要吗?也是。傻吗?第三次,是的。但这就是微软。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...