背景
nugetパッケージのSystem.Text.Jsonを使って、デシリアライズする際に苦しんだことをメモしておきます。
(より正確にはJsonSerializer.Deserialize()を使った際に苦しんだこと)
メモしないと何度もハマりそうですから。
問題
まとめると、苦しんだこととはjsonをデシリアライズするときの、デシリアライズ先クラスの書きかたでした。
今回は下記2点を知らずに苦しみました。
要するに、デシリアライズ先クラスのプロパティの書きかたを知りませんでした。
- publicなプロパティを書く
- 適当な型を用いたプロパティを書く
- JSONの構造によっては、プロパティの型にDictionaryを使う
- JSONの構造によっては、プロパティの型にListを使う
解決策
*サンプルは動作確認していません。
1. publicなプロパティを書く
具体的には、下記のサンプル正のような書きかたをします。
要するに、フィールドではなくプロパティを書きます。
これを見落としたせいで、何度もnull参照エラーを出してしまいました。
public class Sample
{
public string item;
}
public class Sample
{
public string item{ get; set; }
}
追記
下記リンク先の方法ならばフィールドを使うことが可能であるということを、コメントにて教えていただきました。
https://docs.microsoft.com/ja-jp/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-6-0#include-fields
2. 適当な型を用いたプロパティを書く
1. ケースα : プロパティの型にDictionaryを使う
具体的には、下記AのJson構造の時は、下記A'のようにして、Dictionary型のプロパティを定義します。
また、プロパティ名については、JSON内のキー(下記のコード例においては"item")と同名にします。
(同名にしないと、デシリアライズに失敗します。)
{
"item": {
"1": {
"value1": 1,
"value2": 2
},
"2": {
"value1": 1,
"value2": 2
}
}
}
public class Sample
{
public Dictionary<string, Dictionary<string, int>> item { get; set; }
}
2. ケースβ : プロパティの型にListを使う
具体的には、下記BのJson構造の時は、下記B'のようにして、List型のプロパティを定義します。
また、プロパティ名については、JSON内のキー(下記のコード例においては"item")と同名にします。
(同名にしないと、デシリアライズに失敗します。)
{
"item": [
{
"value1": 1,
"value2": 2
},
{
"value1": 1,
"value2": 2
}
]
}
public class Sample
{
public List<Dictionary<string, int>> item { get; set; }
}
【メモ】忘備のための蛇足
ケースβではfirefoxを使って、jsonデータの中身を整形表示したものを見ながら、クラスを書いていました。
下記のごとく、firefoxはケースβのようなjsonに対して、自動でitem直下の要素に対して連番を振ってくれていました。
その連番に惑わされました。
ケースαと同じで行けるだろうと勘違いしていて、Dictionary型で何度も挑戦してハマっていました。
よく見ると違いが判るのですが。
*追記:どの型を選べばよいかについて調べるにはこのようなJSONの説明を読むのが役立ちそうです。
補足(上記の方法でうまくいかなかった場合向け)
本補足にて、上記までの方法でうまくいかなかったときに限り、役立ちそうなことを書きます。
本補足の想定読者
- 特に、うまくいかなかった原因がプロパティ名である、方
それ以外の、本記事をここまで読んでくださった方には、役立たずの情報です。
なぜなら、体系的に書いていないので、殆ど局所的な状況でしか活きてこない情報だからです。
補足本文
結論から言うと、JsonDocumentクラスを使って私は対処しました。
問題
「うまくいかなかった原因がプロパティ名である」状況とは下記のようなものです。
それはつまり、本文の下記引用で示した部分の方法が、適用できないケースです。
プロパティ名については、JSON内のキー(下記のコード例においては"item")と同名にします。
つまり、プロパティ名とキーを同名に出来ないような状況があり、それが問題の原因になっているケースです。
具体例を挙げます。
下記のようなJSONをサンプルに説明します。
[
{"value1":1,"value2":"a"},
{"value1":2,"value2":"b"}
]
このJSONの構造を単純化して[ {~},{~} ]
と表したとき、{~}
に対するプロパティ名(=キー)は何でしょうか?
私はプロパティ名を付けられませんでした。従ってそのデータにアクセスできませんでした。
アクセスできない(=デシリアライズできない)というのが今回の問題です。
(蛇足:今回の問題に似ていて、問題にならないケースがあります。例えば、これが[ [~],[~] ]
の[~]
だったら、ListもしくはArray型としてデシリアライズできます。よって、キーではなくインデックスを用いてアクセスできるので問題にはなりません。)
対策
この問題に対処する方法の一つが、JsonDocumentクラスを使用することです。
JsonDocumentクラスを使うと下記のようにしてJSON内のデータにアクセスできます。
JsonDocument document = JsonDocument.Parse(jsonString, documentOptions);
var rootToList = document.RootElement.EnumerateArray().ToList();
int firstElement_value1 = rootToList[0].GetProperty("value1").GetInt32(); // value1には1が代入される
string firstElement_value2 = rootToList[0].GetProperty("value2").GetString(); // value2には"a"が代入される
int secondElement_value1 = rootToList[1].GetProperty("value1").GetInt32(); // value1には2が代入される
アクセスできれば、その内容をクラスに代入することができます。
つまりデシリアライズを半手動にて行う感じです。
以上、下記の公式リファレンスを参考に実装しました。
JsonDocumentを使用する方法
所感
JsonDocumentを利用した場合、データに動的にアクセスしたことになります。
なので、よくわからない構造のJSONなども読み込めそうです。
また、クラスを操作するような直感性でアクセスできます。
なので例えば、うまくいかない原因がプロパティ名以外である場合に対しても、役立つかもしれません。
(この方法についてこれ以上書くには余白が足りません。別記事に書き残そうか検討中です。)
反省
個人的に、今後jsonデータを扱う頻度が増えそうです。
できれば、jsonを今回のnugetパッケージでデシリアライズするとき、jsonデータを入力することでC#クラスファイルを出力するようなツールが欲しいです。
表示するだけのツールでは、目を凝らさなければ今回のような見落としが発生しそうなので、コード書いてくれるようなツールに頼りたいです。
jsonについての基礎知識を身につけることが先かもしれません。
参考リンク
下記、コメントで教えていただきました。ありがとうございます。