.Net Core 3.0 TimeSpan 反序列化错误 - 已在 .Net 5.0 中修复
.Net Core 3.0 TimeSpan deserialization error - Fixed in .Net 5.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!"
}
]
}
并遇到以下错误:
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.
读取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();
服务器控制器方法returns:
return new JsonResult(result) { StatusCode = whatever; };
REST API 服务不应生成这样的 JSON 字符串。我敢打赌,以前的版本返回 00:0:00
而不是 TimeSpan 对象的所有属性。
原因是.NET Core 3.0 replaced JSON.NET with a new, bult-in JSON serializer, System.Text.Json. This serializer doesn't support TimeSpan。新的序列化器更快,在大多数情况下不分配,但不涵盖 所有 JSON.NET 所做的情况。
无论如何,在 JSON 中 没有 表示日期或时间段的标准方法。甚至 ISO8601 格式也是一种约定,而不是标准本身的一部分。 JSON.NET 使用可读格式 (23:00:00
),但 ISO8601's duration 格式看起来像 P23DT23H
(23 天 23 小时)或 P4Y
(4 年)。
一种解决方案是返回 JSON.NET。文档中描述了这些步骤:
添加对 Microsoft.AspNetCore.Mvc.NewtonsoftJson 的包引用。
更新Startup.ConfigureServices调用AddNewtonsoftJson。
services.AddMvc()
.AddNewtonsoftJson();
另一种选择是为该类型使用自定义转换器,例如:
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());
}
}
并在Startup.ConfigureServices
中用AddJsonOptions
注册,例如:
services.AddControllers()
.AddJsonOptions(options=>
options.JsonSerializerOptions.Converters.Add(new TimeSpanToStringConverter()));
我的解决方案是使用自定义转换器,但使用明确指定的非文化敏感标准TimeSpan format specifier。
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));
}
}
然后在HostBuilder的Startup中注册:
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services
...
.AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.Converters.Add(new JsonTimeSpanConverter());
});
...
}
}
不确定我使用的是否是可为空的 TimeSpan,但即使使用 .NET 5.0,我仍然无法在使用 System.Text.Json
.
时反序列化 TimeSpan
无论如何,这是我在多个模型中用于自定义序列化的解决方案。请注意,仅当您计划将 class 用作数据库模型时才需要 [NotMapped]
属性。
using System.Text.Json.Serialization;
using System.ComponentModel.DataAnnotations.Schema;
// Add your namespace and class declarations
[JsonIgnore]
public TimeSpan? MyTimeSpan { get; set; }
[NotMapped]
public long MyTimeSpanSerializer
{
get => MyTimeSpan?.Ticks ?? -1;
set => MyTimeSpan = value >= 0 ? new TimeSpan(value) : (TimeSpan?)null;
}
TimeSpanConverter 在 .NET 6.0 中可用。因此 TimeSpan serialization/deserialization 无需开箱即用的自定义转换器即可工作。
我正在使用 .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!"
}
]
}
并遇到以下错误:
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.
读取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();
服务器控制器方法returns:
return new JsonResult(result) { StatusCode = whatever; };
REST API 服务不应生成这样的 JSON 字符串。我敢打赌,以前的版本返回 00:0:00
而不是 TimeSpan 对象的所有属性。
原因是.NET Core 3.0 replaced JSON.NET with a new, bult-in JSON serializer, System.Text.Json. This serializer doesn't support TimeSpan。新的序列化器更快,在大多数情况下不分配,但不涵盖 所有 JSON.NET 所做的情况。
无论如何,在 JSON 中 没有 表示日期或时间段的标准方法。甚至 ISO8601 格式也是一种约定,而不是标准本身的一部分。 JSON.NET 使用可读格式 (23:00:00
),但 ISO8601's duration 格式看起来像 P23DT23H
(23 天 23 小时)或 P4Y
(4 年)。
一种解决方案是返回 JSON.NET。文档中描述了这些步骤:
添加对 Microsoft.AspNetCore.Mvc.NewtonsoftJson 的包引用。
更新Startup.ConfigureServices调用AddNewtonsoftJson。
services.AddMvc()
.AddNewtonsoftJson();
另一种选择是为该类型使用自定义转换器,例如:
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());
}
}
并在Startup.ConfigureServices
中用AddJsonOptions
注册,例如:
services.AddControllers()
.AddJsonOptions(options=>
options.JsonSerializerOptions.Converters.Add(new TimeSpanToStringConverter()));
我的解决方案是使用自定义转换器,但使用明确指定的非文化敏感标准TimeSpan format specifier。
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));
}
}
然后在HostBuilder的Startup中注册:
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services
...
.AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.Converters.Add(new JsonTimeSpanConverter());
});
...
}
}
不确定我使用的是否是可为空的 TimeSpan,但即使使用 .NET 5.0,我仍然无法在使用 System.Text.Json
.
无论如何,这是我在多个模型中用于自定义序列化的解决方案。请注意,仅当您计划将 class 用作数据库模型时才需要 [NotMapped]
属性。
using System.Text.Json.Serialization;
using System.ComponentModel.DataAnnotations.Schema;
// Add your namespace and class declarations
[JsonIgnore]
public TimeSpan? MyTimeSpan { get; set; }
[NotMapped]
public long MyTimeSpanSerializer
{
get => MyTimeSpan?.Ticks ?? -1;
set => MyTimeSpan = value >= 0 ? new TimeSpan(value) : (TimeSpan?)null;
}
TimeSpanConverter 在 .NET 6.0 中可用。因此 TimeSpan serialization/deserialization 无需开箱即用的自定义转换器即可工作。