- 投稿日:2020-03-27T12:40:42+09:00
今更ですが、C#でJsonを扱う
はじめに
あるようで、なかったので。
Newtonsoft.Json
の使い方です。DataContractJsonSerializer
ではありません。準備
- 「参照」を右クリック、「NuGet パッケージの管理...」
- 「参照」タブから、「Newtonsoft.Json」を選択して、インストールする
基本
JsonConvert.SerializeObject()
でシリアル化(オブジェクト → 文字列)、JsonConvert.DeserializeObject<T>()
でデシリアル化(文字列 → オブジェクト)。※サンプルなので、
Formatting.Indented
を指定して、JSONを見やすく改行して出力するようにしていますが、実際には無くても良いです。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public DateTimeOffset CreatedDate { get; set; } public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; var json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); Console.WriteLine("\n---"); // デシリアライズ var obj = JsonConvert.DeserializeObject<Account>(json); Console.WriteLine(string.Join("\n", typeof(Account).GetProperties().Select(info => $"{info.Name}: {info.GetValue(obj)}"))); Console.ReadLine(); } }出力
{ "ID": 1, "Name": "hoge@example.com", "IsActive": true, "CreatedDate": "2020-03-18T14:29:30.1143602+09:00", "Role": 0, "Telephones": [ "010-1111-2222", "020-2222-3333" ] } --- ID: 1 Name: hoge@example.com IsActive: True CreatedDate: 2020/03/18 14:28:21 +09:00 Role: Role1 Telephones: System.Collections.Generic.List`1[System.String]
enum のシリアライズ
C#では、enumは内部では数値なので、デフォルトでは数値で出力されてしまう。
JsonConverterAttribute
を使って、StringEnumConverter
を指定すると、文字列にできる。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public DateTimeOffset CreatedDate { get; set; } [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; var json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "ID": 1, "Name": "hoge@example.com", "IsActive": true, "CreatedDate": "2020-03-18T15:01:36.37789+09:00", "Role": "Role1", "Telephones": [ "010-1111-2222", "020-2222-3333" ] }
enumのデシリアライズ
なぜか、
JsonConverterAttribute
を付けていなくても、解釈できる。(しかし、シリアライズのことを考えると、JsonConverterAttribute
を付けておいたほうが良いと思う)class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public DateTimeOffset CreatedDate { get; set; } // [JsonConverter]付けない public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // デシリアライズ var json = $@"{{ ""ID"": 999, ""Name"": ""foo"", ""IsActive"": true, ""CreatedDate"": ""2020-01-02T03:04:05+09:00"", ""Role"": ""Role2"" }}"; var obj = JsonConvert.DeserializeObject<Account>(json); Console.WriteLine(string.Join("\n", typeof(Account).GetProperties().Select(info => $"{info.Name}: {info.GetValue(obj)}"))); Console.ReadLine(); } }出力
ID: 999 Name: foo IsActive: True CreatedDate: 2020/01/02 3:04:05 +09:00 Role: Role2 Telephones: System.Collections.Generic.List`1[System.String]
Jsonに enum に対応する属性がないと、enum は値型なので、
null
ではなく0
に対応する値になってしまう。class Program { static void Main(string[] args) { // デシリアライズ var json = $@"{ ""ID"": 999, ""Name": ""foo"", ""IsActive"": true, ""CreatedDate"": ""2020-01-02T03:04:05+09:00"" }}"; var obj = JsonConvert.DeserializeObject<Account>(json); Console.WriteLine(string.Join("\n", typeof(Account).GetProperties().Select(info => $"{info.Name}: {info.GetValue(obj)}"))); Console.ReadLine(); } }出力(
Role
が0
に対応するRole1
になっている)ID: 999 Name: foo IsActive: True CreatedDate: 2020/01/02 3:04:05 +09:00 Role: Role1 Telephones: System.Collections.Generic.List`1[System.String]
Json の enum に対応する値が
null
だと、例外を throw してしまう。class Program { static void Main(string[] args) { // デシリアライズ var json = $@"{ ""ID"": 999, ""Name"": ""foo"", ""IsActive"": true, ""CreatedDate"": ""2020-01-02T03:04:05+09:00"", ""Role"": null }}"; var obj = JsonConvert.DeserializeObject<Account>(json); Console.WriteLine(string.Join("\n", typeof(Account).GetProperties().Select(info => $"{info.Name}: {info.GetValue(obj)}"))); Console.ReadLine(); } }DateTime のシリアライズ
一括で指定
JsonSerializerSettings
のDateFormatHandling
プロパティでフォーマットを指定する。しかし、MicrosoftDateFormat
とIsoDateFormat
(+0900
というオフセットが付く、ISO 8601の書式)しか無い。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public DateTimeOffset CreatedDate { get; set; } [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; JsonSerializerSettings settings = new JsonSerializerSettings() { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, Formatting = Formatting.Indented, }; var json = JsonConvert.SerializeObject(account, settings); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "ID": 1, "Name": "hoge@example.com", "IsActive": true, "CreatedDate": "\/Date(1584586457933+0900)\/", "Role": "Role1", "Telephones": [ "010-1111-2222", "020-2222-3333" ] }
個別に指定
JsonConverterAttribute
で指定する。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } [JsonConverter(typeof(IsoDateTimeConverter))] public DateTimeOffset CreatedDate { get; set; } [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; var json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "ID": 1, "Name": "hoge@example.com", "IsActive": true, "CreatedDate": "2020-03-19T14:46:17.3561825+09:00", "Role": "Role1", "Telephones": [ "010-1111-2222", "020-2222-3333" ] }
フォーマットを自分で指定
フォーマット指定ができるConverterが無いので、自分でそういうConverterを作るしかない。
IsoDateTimeConverter
のDateTimeFormat
プロパティでフォーマットを指定できるので、次のようにする。DateTimeFormatConverter.csclass DateTimeFormatConverter : IsoDateTimeConverter { public DateTimeFormatConverter(string format) { DateTimeFormat = format; } }あとは、
JsonConverterAttribute
を付けるだけ。フォーマット文字列は第2引数で指定できる。ちなみに、フォーマット文字列は DateTime のものと同じです。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } [JsonConverter(typeof(DateTimeFormatConverter), "yyyy/MM/dd HH:mm:ss")] public DateTimeOffset CreatedDate { get; set; } [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; var json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "ID": 1, "Name": "hoge@example.com", "IsActive": true, "CreatedDate": "2020/03/19 14:25:18", "Role": "Role1", "Telephones": [ "010-1111-2222", "020-2222-3333" ] }
DateTimeのデシリアライズ
ちゃんとソースコードを見てないが、おそらく内部では
DateTime.Parse()
が使われているっぽいので、多少ルーズな書式でも読んでくれる。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public DateTimeOffset CreatedDate { get; set; } [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // デシリアライズ var json = $@"{{ ""ID"": 999, ""Name"": ""foo"", ""IsActive"": true, ""CreatedDate"": ""2020/01/02 03:04:05"", ""Role"": ""Role2"" }}"; var obj = JsonConvert.DeserializeObject<Account>(json); Console.WriteLine(string.Join("\n", typeof(Account).GetProperties().Select(info => $"{info.Name}: {info.GetValue(obj)}"))); Console.ReadLine(); } }出力
ID: 999 Name: foo IsActive: True CreatedDate: 2020/01/02 3:04:05 +09:00 Role: Role2 Telephones: System.Collections.Generic.List`1[System.String]
ちゃんと書式を指定したい場合は、シリアライズで書いたように、
DateTimeFormatConverter
を自作して指定する。
※次のサンプルでは、書式を指定しないと、例外が出ます。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } [JsonConverter(typeof(DateTimeFormatConverter), "yyyy.MM.dd HH.mm.ss")] public DateTimeOffset CreatedDate { get; set; } [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // デシリアライズ var json = $@"{{ ""ID"": 999, ""Name"": ""foo"", ""IsActive"": true, ""CreatedDate"": ""2020.01.02 03.04.05"", ""Role"": ""Role2"" }}"; var obj = JsonConvert.DeserializeObject<Account>(json); Console.WriteLine(string.Join("\n", typeof(Account).GetProperties().Select(info => $"{info.Name}: {info.GetValue(obj)}"))); Console.ReadLine(); } }出力
ID: 999 Name: foo IsActive: True CreatedDate: 2020/01/02 3:04:05 +09:00 Role: Role2 Telephones: System.Collections.Generic.List`1[System.String]
ネストクラス
特に何も考える必要はない。
class Account { public enum AccountRole { Role1, Role2, Role3 } public class AccountAddress { public string Address1 { get; set; } public string Address2 { get; set; } } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } [JsonConverter(typeof(StringEnumConverter))] public DateTimeOffset CreatedDate { get; set; } public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); public AccountAddress Address { get; set; } = new AccountAddress(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, Address = new Account.AccountAddress() { Address1 = "address1", Address2 = "address2", }, }; var json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "ID": 1, "Name": "hoge@example.com", "IsActive": true, "CreatedDate": "2020-03-19T11:27:59.1366704+09:00", "Role": "Role1", "Telephones": [ "010-1111-2222", "020-2222-3333" ], "Address": { "Address1": "address1", "Address2": "address2" } }
Jsonの属性名を指定する
一律ヘビ記法にする
シリアル化するときに、次のように、
DefaultContractResolver
でSnakeCaseNamingStrategy
を指定して、それをJsonSerializerSettings
に設定する。class Account { public enum AccountRole { Role1, Role2, Role3 } public int ID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public DateTimeOffset CreatedDate { get; set; } [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; var resolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() }; var json = JsonConvert.SerializeObject(account, new JsonSerializerSettings() { ContractResolver = resolver, Formatting = Formatting.Indented, }); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "id": 1, "name": "hoge@example.com", "is_active": true, "created_date": "2020-03-19T11:29:38.0498159+09:00", "role": "Role1", "telephones": [ "010-1111-2222", "020-2222-3333" ] }
個別に指定する
JsonPropertyAttribute
の引数で指定する。class Account { public enum AccountRole { Role1, Role2, Role3 } [JsonProperty("user_id")] public int ID { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("active")] public bool IsActive { get; set; } [JsonProperty("created_date")] public DateTimeOffset CreatedDate { get; set; } [JsonProperty("role")] [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } [JsonProperty("telepohones")] public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; var json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "user_id": 1, "name": "hoge@example.com", "active": true, "created_date": "2020-03-19T11:34:57.7137179+09:00", "role": "Role1", "telephones": [ "010-1111-2222", "020-2222-3333" ] }
Jsonに含めない
JsonIgnoreAttribute
を付ける。class Account { public enum AccountRole { Role1, Role2, Role3 } [JsonProperty("user_id")] public int ID { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("active")] public bool IsActive { get; set; } [JsonProperty("created_date")] public DateTimeOffset CreatedDate { get; set; } [JsonProperty("role")] [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } [JsonIgnore] public IList<string> Telephones { get; set; } = new List<string>(); } class Program { static void Main(string[] args) { // シリアライズ var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, }; var json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); Console.ReadLine(); } }出力
{ "user_id": 1, "name": "hoge@example.com", "active": true, "created_date": "2020-03-19T11:40:14.0203605+09:00", "role": "Role1" }
その他の属性を Dictionary にまとめる
JsonExtensionDataAttribte
を付ける。
※[JsonIgnore]
が付いているプロパティでも、[JsonExtensionData]
が付いているプロパティに含められる。class Account { public enum AccountRole { Role1, Role2, Role3 } [JsonProperty("user_id")] public int ID { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("active")] public bool IsActive { get; set; } [JsonProperty("created_date")] public DateTimeOffset CreatedDate { get; set; } [JsonProperty("role")] [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } [JsonProperty("telepohones")] public IList<string> Telephones { get; set; } = new List<string>(); [JsonIgnore] public string Note { get; set; } [JsonExtensionData] public IDictionary<string, object> Extra { get; set; } = new Dictionary<string, object>(); } class Program { static void Main(string[] args) { // デシリアライズ var json = $@"{{ ""user_id"": 999, ""name"": ""foo"", ""active"": true, ""created_date"": ""2020/01/02 03:04:05"", ""role"": ""Role2"", ""note"": ""メモ"", ""hoge"": ""fuga"" }}"; var obj = JsonConvert.DeserializeObject<Account>(json); Console.WriteLine($"ID: {obj.ID}"); Console.WriteLine($"Name: {obj.Name}"); Console.WriteLine($"IsActive: {obj.IsActive}"); Console.WriteLine($"CreatedDate: {obj.CreatedDate}"); Console.WriteLine($"Role: {obj.Role}"); Console.WriteLine($"Note: {obj.Note}"); Console.WriteLine($"Extra: {string.Join(", ", obj.Extra.Select(pair => pair))}"); Console.ReadLine(); } }出力
ID: 999 Name: foo IsActive: True CreatedDate: 2020/01/02 3:04:05 +09:00 Role: Role2 Note: Extra: [note, メモ], [hoge, fuga]
JsonをまとめてDictionaryに突っ込む
Jsonが key-value 形式になっていて読み込む場合、いちいちクラスを作るのではなく、
Dictionary
に入れたいときがある。その場合も、JsonConvert.DeserializeObject<T>
のT
にDictioanry<string, object>
を指定すればいい。class Program { static void Main(string[] args) { // デシリアライズ var json = $@"{{ ""user_id"": 999, ""name"": ""foo"", ""active"": true, ""created_date"": ""2020/01/02 03:04:05"", ""role"": ""Role2"", ""telephone"": [""010-1111-2222"", ""020-2222-3333""], ""address"": {{""address1"":""住所1"", ""address2"":""住所2""}} }}"; var obj = JsonConvert.DeserializeObject<Dictionary<string, object>>(json); Console.WriteLine(string.Join("\n", obj.Select(pair => pair))); Console.ReadLine(); } }出力
[user_id, 999] [name, foo] [active, True] [created_date, 2020/01/02 03:04:05] [role, Role2] [telephone, [ "010-1111-2222", "020-2222-3333" ]] [address, { "address1": "住所1", "address2": "住所2" }]
ストリームから直接読み書き
ファイルや外部のAPIからJsonを読み書きする場合、
JsonSerializer
を使うと、いちいち文字列を経由することなく、直接読み書きができる。class Account { public enum AccountRole { Role1, Role2, Role3 } public class AccountAddress { [JsonProperty("address1")] public string Address1 { get; set; } [JsonProperty("address2")] public string Address2 { get; set; } } [JsonProperty("user_id")] public int ID { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("active")] public bool IsActive { get; set; } [JsonProperty("created_date")] [JsonConverter(typeof(DateTimeFormatConverter), "yyyy/MM/dd HH:mm:ss")] public DateTimeOffset CreatedDate { get; set; } [JsonProperty("role")] [JsonConverter(typeof(StringEnumConverter))] public AccountRole Role { get; set; } [JsonProperty("telephones")] public IList<string> Telephones { get; set; } = new List<string>(); [JsonProperty("address")] public AccountAddress Address { get; set; } = new AccountAddress(); [JsonIgnore] public string Note { get; set; } } class Program { static void Main(string[] args) { var account = new Account() { ID = 1, Name = @"hoge@example.com", IsActive = true, CreatedDate = DateTimeOffset.Now, Role = Account.AccountRole.Role1, Address = new Account.AccountAddress() { Address1 = "address1", Address2 = "address2", }, Telephones = new List<string>() { "010-1111-2222", "020-2222-3333", }, Note = "ほげほげ", }; // シリアライズ using (StreamWriter writer = File.CreateText(@"account.json")) { var serializer = new JsonSerializer(); serializer.Serialize(writer, account); } // デシリアライズ using (StreamReader reader = File.OpenText(@"account.json")) { var serializer = new JsonSerializer(); var obj = (Account) serializer.Deserialize(reader, typeof(Account)); Console.WriteLine($"ID: {obj.ID}"); Console.WriteLine($"Name: {obj.Name}"); Console.WriteLine($"IsActive: {obj.IsActive}"); Console.WriteLine($"CreatedDate: {obj.CreatedDate}"); Console.WriteLine($"Role: {obj.Role}"); Console.WriteLine($"Note: {obj.Note}"); Console.WriteLine($"Telephones: {string.Join(", ", obj.Telephones)}"); Console.WriteLine($"Addresses: {obj.Address.Address1}, {obj.Address.Address2}"); } Console.ReadLine(); } }出力
ID: 1 Name: hoge@example.com IsActive: True CreatedDate: 2020/03/27 12:11:03 +09:00 Role: Role1 Note: Telephones: 010-1111-2222, 020-2222-3333 Addresses: address1, address2
json(見やすいように改行とインデントを入れていますが、実際は1行です)
{ "user_id": 1, "name": "hoge@example.com", "active": true, "created_date": "2020/03/27 12:11:03", "role": "Role1", "telephones": ["010-1111-2222", "020-2222-3333"], "address": { "address1":"address1", "address2":"address2" } }まとめ
まだ不足しているパターンがありそうですが、そのときは追記します。
ここに載せた機能以外には、コンストラクタを使ってデシリアライズ、エラーハンドリング、デバッグ出力などがあります。この辺の機能はマニアックなので、書く予定ないですが。
参考
- 本家のマニュアル(英語)