c# - 测试自定义 JsonConverter 时出现异常

标签 c# .net json .net-core-3.0 system.text.json

我们以一种奇怪的格式从 API 获取序列化的 DateTimes:/Date(1574487012797)/ 为了使用 System.Text.Json 反序列化这个值,我们编写了自己的 JsonConverter:

public class DateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var dateTimeString = reader.GetString();
        dateTimeString = dateTimeString.Replace("/Date(", "");
        dateTimeString = dateTimeString.Replace(")/", "");
        var epoch = Convert.ToInt64(dateTimeString);
        var dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(epoch);
        return dateTimeOffset.UtcDateTime;
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"));
    }
}

我想为此转换器编写单元测试。 我尝试的是以下内容:

public class DateTimeConverterTest
{
    private readonly DateTimeConverter testee;

    public DateTimeConverterTest()
    {
        this.testee = new DateTimeConverter();
    }

    [Fact]
    public void Read_WhenCalledWithSerializedDateTime_ThenReturnDeserializedDateTime()
    {
        var a = "{\r\n \"PublikationsDatum\": \"/Date(1573581177000)/\" \r\n}";
        //var serializedDateTime = "/Date(1573581177000)/";
        var utf8JsonReader = new Utf8JsonReader(Encoding.UTF8.GetBytes(a), false, new JsonReaderState(new JsonReaderOptions()));
        //utf8JsonReader.TokenType = JsonTokenType.String;
        var deserializedDateTime = this.testee.Read(ref utf8JsonReader, typeof(DateTime), new JsonSerializerOptions {IgnoreNullValues = true});

    }

    private class TestClass
    {
        public DateTime PublikationsDatum { get; set; }
    }
}

不幸的是,在尝试执行单元测试时,我在 var dateTimeString = reader.GetString();

处收到 InvalidOperationException

System.InvalidOperationException: 'Cannot get the value of a token type 'None' as a string.'

如何正确设置测试/我做错了什么?

最佳答案

在调用 JsonConverter<T>.Read() 之前,您必须将 Utf8JsonReader 向前移动,直到它位于"PublikationsDatum"属性的值上,例如像这样:

public void Read_WhenCalledWithSerializedDateTime_ThenReturnDeserializedDateTime()
{
    var a = "{\r\n \"PublikationsDatum\": \"/Date(1573581177000)/\" \r\n}";
    var utf8JsonReader = new Utf8JsonReader(Encoding.UTF8.GetBytes(a), false, new JsonReaderState(new JsonReaderOptions()));
    while (utf8JsonReader.Read())
        if (utf8JsonReader.TokenType == JsonTokenType.String)
            break;
    var deserializedDateTime = this.testee.Read(ref utf8JsonReader, typeof(DateTime), new JsonSerializerOptions {IgnoreNullValues = true});
}

演示 fiddle #1 here

或者,您可以通过解析简单的 JSON 字符串文字"/Date(1573581177000)/"来简化单元测试。但是,您仍然需要将阅读器推进一次,因为它最初位于第一个标记的开头之前,即utf8JsonReader.TokenType == JsonTokenType.None:

public void Read_WhenCalledWithSerializedDateTime_ThenReturnDeserializedDateTime()
{
    var a = "\"/Date(1573581177000)/\"";
    var utf8JsonReader = new Utf8JsonReader(Encoding.UTF8.GetBytes(a), false, new JsonReaderState(new JsonReaderOptions()));
    // Reader always starts out without having read anything yet, so TokenType == JsonTokenType.None initially
    Assert.IsTrue(utf8JsonReader.TokenType == JsonTokenType.None);
    utf8JsonReader.Read();
    var deserializedDateTime = this.testee.Read(ref utf8JsonReader, typeof(DateTime), new JsonSerializerOptions {IgnoreNullValues = true});
}

演示 fiddle #2 here

注意事项:

  • "\/Date(number of ticks)\/"是微软最初的JavaScriptSerializer用于将DateTime序列化为JSON字符串的格式。详情请参阅the documentation remarks

(在 JSON string literal 中,\/ 只是一个转义的 \ 并将被 Utf8JsonReader 默默地解释为这样,参见 fiddle #3 here 。你不需要检查\/在您的 JSON 转换器中处理JavaScriptSerializer生成的日期和时间。)

  • DataContractSerializer使用了稍微不同的格式。来自docs:

    DateTime values appear as JSON strings in the form of "/Date(700000+0500)/", where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.

  • Newtonsoft 对 DateTimeUtils.TryParseDateTimeMicrosoft() 的实现可能有助于指导您的实现。

关于c# - 测试自定义 JsonConverter 时出现异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59028159/

相关文章:

c# - WCF:使用什么超时属性?

c# - 在代码中将透明PNG转换为System.Drawing.Icon

json - 如何在逻辑应用中通过表达式语法访问数组值

json - Go - 在 json.Marshal 中自动将字符串值转换为 int 值

javascript - Jquery 的 $.getJSON 的 vanilla JS 版本是什么

c# - WPF 使 ServiceController[] 可观察

c# - 将多个对象序列化为一个二进制文件

c# - 如何从数据库动态重新安排 C# 中的 Quartz 作业调度程序?

c# - Xamarin.Forms - 与单击事件中的事件类型错误不匹配

c# - 如何在 C# 中使用 Azure 通信服务 SMS 发送多个收件人 SMS