日付時刻を表す JSON 文字列を .NET の DateTime 型のオブジェクトにデシリアライズするにはどのようにするかという話を書きます。
{"name":"value"} という JSON 文字列の value に直接設定できるのは string, number, object, array, true, false, null だけですので、.NET の DateTime オブジェクトは string 型に変換されて JSON 文字列に設定されます。
変換の結果はシリアライザによって違います。詳しくは先の記事「日付時刻と JSON 文字列」に書きましたが、それをまとめた表を以下に再掲しておきます。表の一番最後の項目 (6) ASP.NET Web API は Newtonsoft.Json および System.Text.Json 名前空間の JsonSerializer クラスを使ったシリアライズ結果です。
No. |
方法 |
結果 |
(1) |
JSON.stringify() |
2017-02-01T03:15:45.000Z |
(2) |
DataContractJsonSerializer |
\/Date(1503727168573+0900)\/ |
(3) |
WCF |
上記 (2) の結果と同じ |
(4) |
JavaScriptSerializer |
\/Date(1030806000000)\/ |
(5) |
ASP.NET Web サービス |
上記 (4) の結果と同じ |
(6) |
ASP.NET Web API |
2017-08-26T15:39:32.6330349+09:00 |
上の表のように string 型にシリアライズされた JSON 文字列を元の DateTime 型のオブジェクトに変換するには、自力でコードを書いて文字列をパースする必要があると思っていました。
しかしそれは思い違いで、上の表の形式にシリアライズした文字列は Newtonsoft.Json であれば全て、System.Text.Json 名前空間の JsonSerializer クラスの場合も (1) と (6) は DateTime 型にデシリアライズしてくれました。
知ってましたか? 実は自分は最近まで知らなかったです。(汗) 以下にその話を書きます。
まず、JSON 文字列を C# のオブジェクトにデシリアライズする際、C# のオブジェクトのクラス定義が必要ですが、Visual Studio のツールを利用してそれが可能です。詳しくは先の記事「JSON 文字列から C# のクラス定義生成」を見てください。
Visual Studio のツールを使って C# のクラス定義を生成すると、上の表の日付日時の JSON 文字列に該当するプロパティの型は、上の表の例 (1) ~ (6) 全てが DateTime 型になります。
例えば、以下のような JSON 文字列から Visual Studio のツールを使って C# のクラス定義を生成すると、
{
"name":"WebSurfer",
"date":"2017-02-01T03:15:45.000Z"
}
日付時間の JSON 文字列に該当するプロパティの型は以下のよう DataTime 型になります。
public class Rootobject
{
public string name { get; set; }
public DateTime date { get; set; }
}
シリアライザによって JSON 文字列の形式が上の表の (1) ~ (6) のように異なりますが、いずれも同じ結果すなわち C# のクラスの当該プロパティの型は DateTime になります。
次にデシリアライザが上の表の (1) ~ (6) の文字列に対応しているかを調べました。結果は上にも述べましたが Newtonsoft.Json は (1) ~ (6) 全てに対応していました。検証に使ったコードを以下に載せておきます。
using Newtonsoft.Json;
using System;
namespace ConsoleNewtonsoftJson
{
internal class Program
{
static void Main(string[] args)
{
string jsonString1 = "{\"name\":\"WebSurfer\",\"date\":\"2017-02-01T03:15:45.000Z\"}";
string jsonString2 = "{\"name\":\"WebSurfer\",\"date\":\"\\/Date(1503727168573+0900)\\/\"}";
string jsonString3 = "{\"name\":\"WebSurfer\",\"date\":\"\\/Date(1030806000000)\\/\"}";
string jsonString4 = "{\"name\":\"WebSurfer\",\"date\":\"2017-08-26T15:39:32.6330349+09:00\"}";
Rootobject rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString1);
Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");
rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString2);
Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");
rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString3);
Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");
rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString4);
Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");
}
}
public class Rootobject
{
public string name { get; set; }
public DateTime date { get; set; }
}
}
/*
実行結果は:
name: WebSurfer, date: 2017/02/01 3:15:45
name: WebSurfer, date: 2017/08/26 14:59:28
name: WebSurfer, date: 2002/08/31 15:00:00
name: WebSurfer, date: 2017/08/26 15:39:32
*/
System.Text.Json 名前空間の JsonSerializer クラスのデシリアライザも同様に検証してみましたが、jsonString2 と jsonString3 場合は JsonException がスローされ "The JSON value could not be converted to System.DateTime." というエラーメッセージが出ます。jsonString1 と jsonString4 は問題なく DateTime 型のオブジェクトにデシリアライズされました。
例外のスタックトレースを見ると、Utf8JsonReader.GetDateTime() メソッドで例外がスローされており、そのメソッドの Microsoft のドキュメントを見ると Remarks に "This method only creates a DateTime representation of JSON strings that conform to the ISO 8601-1 extended format" と書いてあります。
ということで、上の表の (1) と (6) の文字列は ISO 8601-1 extended format に準拠しているので DateTime にデシリアライズできた、他の形式はダメだったということのようです。