测试自定义 JsonConverter 时出现异常

xer*_*him 3 .net c# json .net-core-3.0 system.text.json

我们以一种奇怪的格式从 API 获取序列化日期时间,如下所示:/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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,在尝试执行单元测试时,我得到了一个InvalidOperationExceptionatvar dateTimeString = reader.GetString();

System.InvalidOperationException: '无法以字符串形式获取标记类型 'None' 的值。

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

dbc*_*dbc 8

在您可以调用之前,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});
}
Run Code Online (Sandbox Code Playgroud)

演示小提琴 #1在这里

或者,您可以通过解析简单的 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});
}
Run Code Online (Sandbox Code Playgroud)

演示小提琴 #2在这里

笔记:

  • "\/Date(number of ticks)\/"是 Microsoft 原始JavaScriptSerializer用于将 a 序列化为DateTimeJSON 字符串的格式。有关详细信息,请参阅文档备注

    (在 JSON字符串文字中\/只是一个转义\,将被静默解释Utf8JsonReader,请参阅此处的小提琴 #3 。您无需\/在 JSON 转换器中检查以处理JavaScriptSerializer生成的日期和时间。)

  • DataContractSerializer使用了稍微不同的格式。从文档

    DateTime 值以“/Date(700000+0500)/”形式显示为 JSON 字符串,其中第一个数字(在提供的示例中为 700000)是 GMT 时区中的毫秒数,常规(非夏令时)自 1970 年 1 月 1 日午夜以来的时间。该数字可能为负数以表示更早的时间。示例中由“+0500”组成的部分是可选的,表示时间是本地类型的 - 即在反序列化时应转换为本地时区。如果不存在,则将时间反序列化为 Utc。实际数字(在本例中为“0500”)及其符号(+ 或 -)将被忽略。

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