Error deserialising a DateTime that ASP.NET converted to JSON

huangapple go评论85阅读模式
英文:

Error deserialising a DateTime that ASP.NET converted to JSON

问题

我们有一个 MAUI 应用程序(虽然我认为这实际上并不相关),它将数据发送到一个 ASP.NET Core 最小 API。

我们使用一个通用的 DTO(简化的)...

public record MyDto(DateTime Date);

应用程序将其序列化如下(同样,简化)...

string json = JsonSerializer.Serialize(new MyDto(DateTime.Now));

...然后将其发送到 API。

API 有一些中间件,它获取传入的 JSON 并对其进行反序列化...

MyDto dto = JsonHelpers.Deserialise<MyDto>(httpContext.Request.Query["data"].ToString());

然而,这会抛出一个异常 "无法转换 JSON 值",原因是日期的格式。该应用程序发送的 JSON 包含一个日期格式为 2023-05-24T20:20:46.9351772 01:00,这导致反序列化程序无法处理。

根据许多答案,我尝试了自定义 JSON 转换器,但无法使其工作。例如,我尝试了...

public class CustomDateTimeConverter : JsonConverter<DateTime?> {
  const string Format = "yyyy-MM-dd hh:mm:ss.fffffff zzz";

  public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
    DateTime.Parse(reader.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);

  public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) =>
    writer.WriteStringValue(value?.ToUniversalTime().ToString(Format, CultureInfo.InvariantCulture));
}

然而,这仍然会引发异常。如果我在手动编写的 JSON 上使用此代码,其中日期格式为 2023-05-24T20:20:46.9351772,那么反序列化程序将正常工作,因此似乎偏移量是问题所在。我无法弄清楚如何设置 Format 字符串以使其与偏移量一起工作。

根据此页面zzz 应该对偏移量有效,并且在对日期使用 ToString() 时确实有效。

让我感到困惑的是,我们在代码中有许多包含 DateTime 属性的 DTO,它们在 API 端点中成功反序列化。我不明白为什么这一个会有所不同。

让我更加困惑的是,所有这些都应该由框架处理。毫无疑问,JsonSerializer.Deserialize 应该能够处理 JsonSerializer.Serialize 的输出吧?

有人能提供建议吗?谢谢。

英文:

We have a MAUI app (although I don't think that is actually relevant) that sends data to an ASP.NET Core minimal API.

We use a common DTO (simplified)...

public record MyDto(DateTime Date);

The app serialises this as follows (again, simplified)...

string json = JsonSerializer.Serialize(new MyDto(DateTime.Now));

...and sends it to the API.

The API has some middleware that grabs the incoming JSON and deserialises it...

MyDto dto = JsonHelpers.Deserialise<MyDto>(httpContext.Request.Query["data"].ToString());

However, this throws an exception "The JSON value could not be converted" because of the format of the date. The app is sending JSON that includes a date formatted as 2023-05-24T20:20:46.9351772 01:00, which causes the deserialiser to choke.

Following many answers here, I tried a custom JSON converter, but couldn't get it to work. For example, I tried...

public class CustomDateTimeConverter : JsonConverter<DateTime?> {
  const string Format = "yyyy-MM-dd hh:mm:ss.fffffff zzz";

  public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
    DateTime.Parse(reader.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);

  public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) =>
    writer.WriteStringValue(value?.ToUniversalTime().ToString(Format, CultureInfo.InvariantCulture));
}

However this still gives the exception. If I use this code on a manually written piece of JSON, where the date is formatted 2023-05-24T20:20:46.9351772, then the deserialiser works fine, so it seems to be the offset that's the problem. I can't work out how to set the Format string to work with the offset.

According to this page, zzz should work fine for the offset, and indeed does work fine when using ToString() on a date.

What puzzles me is that we have loads of DTOs in the code that include a DateTime property, and are successfully deserialised in API endpoints. I don't understand why this one should be any different.

What puzzles me further is that all of this should be handled by the framework. Surely JsonSerializer.Deserialize should be able to work with the output of JsonSerializer.Serialize?

Anyone able to advise? Thanks

答案1

得分: 2

你的代码中存在几个问题。

  1. 你在反序列化时没有使用格式,所以要使用它。例如:
public class CustomDateTimeConverter : JsonConverter<DateTime?>
{
    const string Format = "yyyy-MM-dd HH:mm:ss.fffffff zzz";

    public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        DateTime.ParseExact(reader.GetString(), Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal);

    public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) =>
        writer.WriteStringValue(value?.ToString(Format, CultureInfo.InvariantCulture));
}
public record MyDto([property: JsonConverter(typeof(CustomDateTimeConverter))]DateTime? Date);
var myDto = new MyDto(DateTime.Now);
var ser = JsonSerializer.Serialize(myDto);
var deserialize = JsonSerializer.Deserialize<MyDto>(ser);
Console.WriteLine(deserialize.Date == myDto.Date); // true
  1. 你的格式没有正确表示提供的示例字符串。应该是类似这样的 - yyyy-MM-ddTHH:mm:ss.fffffff zzz(在日期和时间之间添加了 T,使用 HH 而不是 'hh')。

  2. 看起来 zzz 需要带有符号 (+/-) - docs,所以你可能需要修复输入(例如使用正则表达式替换来修复它):

var myDto = JsonSerializer.Deserialize<MyDto>(@"
{
   ""Date"": ""2023-05-24T20:20:46.9351772 +01:00""
}");

public class CustomDateTimeConverter : JsonConverter<DateTime> 
{
    const string Format = "yyyy-MM-ddTHH:mm:ss.fffffff zzz";

    // TODO - check for empty/null strings, use TryParse
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        DateTime.ParseExact(reader.GetString(), Format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) =>
        throw new JsonException();
}

P.S.

实际上,如果你在原始字符串中将空格改为 +/- 符号,它看起来像是 ISO 8601 格式的有效日期,应该可以直接使用:

var myDto = JsonSerializer.Deserialize<MyDto>(@"
{
   ""Date"": ""2023-05-24T20:20:46.9351772+01:00""
}");

public record MyDto(DateTime? Date);
英文:

There are several issues in your code.

  1. You are not using the format when deserializing, so use it. For example:
public class CustomDateTimeConverter : JsonConverter&lt;DateTime?&gt; {
    const string Format = &quot;yyyy-MM-dd HH:mm:ss.fffffff zzz&quot;;

    public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =&gt;
        DateTime.ParseExact(reader.GetString(), Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal);

    public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) =&gt;
        writer.WriteStringValue(value?.ToString(Format, CultureInfo.InvariantCulture));
}
public record MyDto([property: JsonConverter(typeof(CustomDateTimeConverter))]DateTime? Date);
var myDto = new MyDto(DateTime.Now);
var ser = JsonSerializer.Serialize(myDto);
var deserialize = JsonSerializer.Deserialize&lt;MyDto&gt;(ser);
Console.WriteLine(deserialize.Date == myDto.Date); // true
  1. Your format does not represent correctly the provided sample string. It should be something like this - yyyy-MM-ddTHH:mm:ss.fffffff zzz (T added between date and time, HH used instead of 'hh')

  2. It seems that zzz requires sign (+/-) - docs so you might need to fix the input (for example using regex replace to fix it):

var myDto = JsonSerializer.Deserialize&lt;MyDto&gt;(&quot;&quot;&quot;
{
   &quot;Date&quot;: &quot;2023-05-24T20:20:46.9351772 +01:00&quot;
}
&quot;&quot;&quot;);

public class CustomDateTimeConverter : JsonConverter&lt;DateTime&gt; {
    const string Format = &quot;yyyy-MM-ddTHH:mm:ss.fffffff zzz&quot;;

    // TODO - check for empty/null strings, use TryParse
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =&gt;
        DateTime.ParseExact(reader.GetString(), Format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) =&gt;
        throw new JsonException();
}

P.S.

Actually if you change space to +/'-' sign in the original string it looks like a valid date in the ISO 8601 format which should work out of the box:

var myDto = JsonSerializer.Deserialize&lt;MyDto&gt;(&quot;&quot;&quot;
{
   &quot;Date&quot;: &quot;2023-05-24T20:20:46.9351772+01:00&quot;
}
&quot;&quot;&quot;);

public record MyDto(DateTime? Date);

huangapple
  • 本文由 发表于 2023年5月25日 04:00:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76327022.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定