.Net Core 3.0 TimeSpan 反序列化错误 - 在 .Net 5.0 中修复

Kok*_*Teh 16 timespan json.net json-deserialization .net-core .net-core-3.0

我正在使用 .Net Core 3.0 并有以下字符串,我需要用 Newtonsoft.Json 反序列化:

{
    "userId": null,
    "accessToken": null,
    "refreshToken": null,
    "sessionId": null,
    "cookieExpireTimeSpan": {
        "ticks": 0,
        "days": 0,
        "hours": 0,
        "milliseconds": 0,
        "minutes": 0,
        "seconds": 0,
        "totalDays": 0,
        "totalHours": 0,
        "totalMilliseconds": 0,
        "totalMinutes": 0,
        "totalSeconds": 0
    },
    "claims": null,
    "success": false,
    "errors": [
        {
            "code": "Forbidden",
            "description": "Invalid username unknown!"
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

并遇到以下错误:

   Newtonsoft.Json.JsonSerializationException : Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.TimeSpan' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly.
To fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'cookieExpireTimeSpan.ticks', line 1, position 103.
Run Code Online (Sandbox Code Playgroud)

错误字符串实际上是在读取 HttpResponseMessage 的内容时发生的:

var httpResponse = await _client.PostAsync("/api/auth/login", new StringContent(JsonConvert.SerializeObject(new API.Models.Request.LoginRequest()), Encoding.UTF8, "application/json"));
var stringResponse = await httpResponse.Content.ReadAsStringAsync();
Run Code Online (Sandbox Code Playgroud)

服务器控制器方法返回:

return new JsonResult(result) { StatusCode = whatever; };
Run Code Online (Sandbox Code Playgroud)

Pan*_*vos 52

REST API 服务不应生成这样的 JSON 字符串。我敢打赌,以前的版本会返回00:0:00而不是 TimeSpan 对象的所有属性。

原因是 .NET Core 3.0新的内置 JSON 序列化程序System.Text.Json替换了 JSON.NET。此序列化程序不支持 TimeSpan。新的序列化器速度更快,在大多数情况下不分配,但并未涵盖JSON.NET 所做的所有情况。

在任何情况下,都没有用 JSON 表示日期或句点的标准方法。甚至 ISO8601 格式也是一种约定,而不是标准本身的一部分。JSON.NET 使用可读格式 ( 23:00:00),但ISO8601 的持续时间格式类似于P23DT23H(23 天 23 小时) 或P4Y(4 年)。

一种解决方案是返回到 JSON.NET。文档中描述了这些步骤:

services.AddMvc()
    .AddNewtonsoftJson();
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用该类型的自定义转换器,例如:

public class TimeSpanToStringConverter : JsonConverter<TimeSpan>
{
    public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var value=reader.GetString();
        return TimeSpan.Parse(value);
    }

    public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}
Run Code Online (Sandbox Code Playgroud)

并且在登记之Startup.ConfigureServicesAddJsonOptions,如:

services.AddControllers()                    
        .AddJsonOptions(options=>
            options.JsonSerializerOptions.Converters.Add(new TimeSpanToStringConverter()));

Run Code Online (Sandbox Code Playgroud)

  • 在“TimeSpanToStringConverter”中,我建议在不变区域性中进行解析和格式化,即“TimeSpan.Parse(value, CultureInfo.InvariantCulture)”和“((TimeSpan)value).ToString(null, CultureInfo.InvariantCulture)” (6认同)
  • @KokHowTeh 事实上,我什至在 Twitter 上对 Immo Landwerth 进行了攻击 (3认同)
  • “TimeSpanJsonConverter”似乎比“TimeSpanToStringConverter”更好。 (3认同)
  • @KokHowTeh 添加了如何创建和注册自定义转换器 (2认同)
  • @KokHowTeh 这个问题是由 *server* 序列化 TimeSpan 引起的,就好像它是一个对象而不是持续时间一样。您需要修复服务器,而不是客户端。 (2认同)

kal*_*sov 6

我的解决方案是使用自定义转换器,但使用明确指定的标准非区域性敏感的TimeSpan 格式说明符

public class JsonTimeSpanConverter : JsonConverter<TimeSpan>
{
    public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return TimeSpan.ParseExact(reader.GetString(), "c", CultureInfo.InvariantCulture);
    }

    public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("c", CultureInfo.InvariantCulture));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在 HostBuilder 的 Startup 中注册它:

public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        ...
        services
            ...
            .AddJsonOptions(opts =>
            {
                opts.JsonSerializerOptions.Converters.Add(new JsonTimeSpanConverter());                    
            });
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 使用“c”格式说明符时,不需要指定“CultureInfo.InvariantCulture”(格式已经是区域性不变的,请参阅[文档](https://learn.microsoft.com/en-us/ dotnet/standard/base-types/standard-timespan-format-strings#the-constant-c-format-specifier))。事实上 `"c"` 是 `ToString()` 使用的默认值,所以你可以简单地写 `writer.WriteStringValue(value.ToString());` (2认同)