System.Text.Json.JsonException:JSON 値を System.DateTime に変換できませんでした

System.Text.Json.JsonSerializer を使用して datetime 値を逆シリアル化するときに、値が期待される形式でない場合、JsonException がスローされます。日時が ISO-8601-1:2019 形式であることが想定されています (例:2021-07-12T12:35:34+00:00)。

たとえば、次のコードは、datetime 値を予期しない形式で逆シリアル化しようとしています:

var eventJson = "{\"HappenedAt\":\"2021-05-26 18:30:41.720\", \"Name\":\"Meltdown\"}";
var sysEvent = JsonSerializer.Deserialize<SystemEvent>(eventJson);
Code language: C# (cs)

これにより、次の例外がスローされます:

日時形式をそのまま使用して JSON を逆シリアル化する必要がある (そして変更できない) と仮定すると、解決策はカスタム コンバーターを作成して使用することです。その方法を以下に示します。

注:この問題は、DateTime、DateTime?、DateTimeOffset、および DateTimeOffset? で発生し、解決策はこれらすべてのケースでほぼ同じです。

解決策 – カスタム日時コンバーターを使用する

次の手順は、使用している形式で datetime 値を逆シリアル化するカスタム コンバーターを作成する方法を示しています。

ステップ 1 – JsonConverter のサブクラス化

カスタム日時コンバーターを作成するには、次のように JsonConverter をサブクラス化します。

using System.Text.Json;
using System.Text.Json.Serialization;

public class CustomDateTimeConverter : JsonConverter<DateTime>
{
	public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		throw new NotImplementedException();
	}

	public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
	{
		//Don't implement this unless you're going to use the custom converter for serialization too
		throw new NotImplementedException();
	}
}
Code language: C# (cs)

ステップ 2 – Read() を実装する

逆シリアル化をカスタマイズしたいので、Read() を実装する必要があります。 Write() を実装する必要はありません (シリアル化にカスタム コンバーターも使用する場合を除きます)。

たとえば、正確な形式のみを解析できるようにする場合は、DateTime.ParseExact() を使用できます。

public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
	return DateTime.ParseExact(reader.GetString(), "yyyy-MM-dd H:mm:ss.fff", null);
}
Code language: C# (cs)

DateTime.Parse() は、この記事で使用されている日時 (2021-05-26 18:30:41.720) でも機能します:

public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
	return DateTime.Parse(reader.GetString());
}
Code language: C# (cs)

DateTime.Parse() はさまざまな形式を解析できるため、注意して使用してください。非常に特殊な形式のみを受け入れたい場合は、代わりに ParseExact() を使用してください。

ステップ 3 – カスタム コンバーターを JsonSerializer に渡す

カスタム コンバーターを使用するには、それを JsonSerializerOptions.Converters に追加し、次のようにオプションを JsonSerializer.Deserialize() に渡します。

var eventJson = "{\"HappenedAt\":\"2021-05-26 18:30:41.720\", \"Name\":\"Meltdown\"}";

var options = new JsonSerializerOptions();
options.Converters.Add(new CustomDateTimeConverter());

var sysEvent = JsonSerializer.Deserialize<SystemEvent>(eventJson, options);

Code language: C# (cs)

JsonSerializer が HappenedAt プロパティに到達すると、カスタム コンバーターが呼び出され、datetime 値が正常に解析されます。