「マイクロソフト系技術情報 Wiki」は、「Open棟梁Project」,「OSSコンソーシアム .NET開発基盤部会」によって運営されています。
目次 †
概要 †
とある案件の要件で、
非構造化データを粘土細工のように
ゴリゴリと処理したいが、そういったことは、可能なのだろうか?
ということを調査した。
結論としては、コチラのLINQ to JSONを使用することで可能。
JSON フォーマットとクラスの定義 †
JSON フォーマットの確認 †
まず、どのようなフォーマットの JSON を出力したいのかを確認する。
クラスの定義 †
次に、出力したい JSON に合わせて、クラスを定義する。
基本 †
基本的には、以下のルールに従って、クラス・プロパティを定義する。
- JSON オブジェクト ({ キー名 : 値 }) の場合
- JSON 配列 ([ 値1, 値2, ... ]) の場合
- キー名が不定の場合
- キー名が不定 (実行時に決まる) の場合、事前のクラス/プロパティ定義ができない。
- このようなときは、Dictionary<TKey, TValue> を、Dictionary<string, object> として定義する。
ルール †
基本的に、以下のようにJSON出力を想定し、クラスを定義する。
# | 出力したいJSON | 定義するクラス・プロパティ |
1 | { "key1" : "value1", "key2" : "value2" } | public class Sample { string key1 { get; set; } string key2 { get; set; } }
若しくは、 Dictionary<string, string> dic; |
2 | [ "1", "2", "3" ] | List<string> listData { get; set; } |
3 | { "key1" : [ "1", "2", "3" ] } | public class Sample { List<string> key1 { get; set; } } |
4 | { "key" : { "key1" : "value1", "key2" : "value2" } } | public class Sample { string key1 { get; set; } string key2 { get; set; } } public class Sample2 { Sample key { get; set; } }
若しくは、 Dictionary<string, Sample> dic;
若しくは、 Dictionary<string, Dictionary<string, string>> dic; |
5 | [ { "key1" : "value1", "key2" : "value2" }, { "key1" : "value3", "key2" : "value4" } ] | public class Sample { string key1 { get; set; } string key2 { get; set; } }
List<Sample> list; |
- ただし、DictionaryをDataContractJsonSerializer?で
シリアライズ・デシリアライズする場合、下記の様に、相互運用性に問題があるので注意する。
- 上記のように、public class Sampleを定義するのが面倒な場合は、
以下のように、匿名型を使用することで、簡単にJSONを作成することができる。
JsonConvert.SerializeObject(new
{
date = DateTime.Now,
command = SqlExecuteType.Reader,
text = this.ClearText(this._commandText),
param = this._commandParameters,
ms = this._stopwatch.ElapsedMilliseconds
}, Formatting.None));
Web Essentialsを使用 †
Visual Studio の拡張機能である、Web Essentials を使うと、JSON からクラスを自動生成できる。
Web Essentials は、Visual Studio のメニューから、[ツール] - [拡張機能と更新プログラム] で追加できる。

JSON 文字列をコピーし、Visual Studio のエディターで、右クリックまたは [編集] メニューから、
[形式を選択して貼り付け] - [JSON をクラスとして貼り付ける] を選択すると、
その JSON フォーマットに合ったクラスが生成される。
JSON.NET †
ココが参考になる。
基本(Bean, POCO) †
シリアライズ・デシリアライズ †
[JsonObject("aaa")]
public class AAA
{
[JsonProperty("prop1")]
public int Property1 { get; set; }
[JsonProperty("prop2")]
public string Property2 { get; set; }
}
- シリアライズ
- コード
AAA aaa = new AAA();
aaa.Property1 = 100;
aaa.Property2 = "xxx";
string json = JsonConvert.SerializeObject(aaa);
- 文字列
{"prop1":100,"prop2":"xxx"}
シリアライズ・デシリアライズを制御する †
シリアライズ・デシリアライズのメソッドに、
する方法がある。
シリアライズ・デシリアライズの既定値を制御するオブジェクト †
- Handling
- DefaultValueHandling?
- Include
シリアライズ時に既定値の項目をJSONに含める(既定)。
- Ignore
シリアライズ時に既定値の項目をJSONに含めない。
- Populate
デシリアライズ時にJSON文字列中に要素が存在しない場合でも既定値を設定。
- IgnoreAndPopulate?
IgnoreとPopulateの同時指定。
- NullValueHandling?
nullの値をどう処理するか
- Include
オブジェクトをシリアライズおよびデシリアライズするときには、null値を含めます。
- Ignore
オブジェクトのシリアライズおよびデシリアライズ時にはnull値を無視します。
- MissingMemberHandling?
デシリアライズ時、メンバが存在しない場合の動作
- Ignore
無視する(既定)
- Error
例外を出力
- DefaultContractResolver?
メンバ名はPascalCase?でJSON化される。
- CamelCasePropertyNamesContractResolver?
メンバ名はCamelCase?でJSON化される。
- Formatting
- None
インデントなし
- Indented
インデントをつける
シリアライズ・デシリアライズの既定値を制御する属性 †
- JsonProperty?属性
JSONのキーを指定する
- JsonIgnore?属性
シリアライズ対象外を設定する
- DefaultValue?属性
既定値を設定する。
応用 †
上記のリンク先の記事に
「階層構造を持ったJSONでも問題なくデシリアライズ可能。
JSON配列もListなどにパースしてくれる。」
とあるが、何処までやってくれるか?
・・・検証の結果、
ということが解った。
以下は、型が特定できる構造のJSONをparseする例。
階層構造を持ったBean, POCOを使用する †
- Bean, POCO
[JsonObject("aaa")]
public class AAA
{
[JsonProperty("prop1")]
public int Property1 { get; set; }
[JsonProperty("prop2")]
public string Property2 { get; set; }
[JsonProperty("prop3")]
public AAA Property3 { get; set; }
}
- シリアライズ・デシリアライズ
static void Main(string[] args)
{
AAA aaa = new AAA();
aaa.Property1 = 100;
aaa.Property2 = "xxx";
aaa.Property3 = new AAA();
aaa.Property3.Property1 = 200;
aaa.Property3.Property2 = "yyy";
string json = JsonConvert.SerializeObject(aaa);
Console.WriteLine(json);
aaa = JsonConvert.DeserializeObject<AAA>(json);
- 型を明示しない
オブジェクト型を使用する(JObject型にデシリアライズされる)。
- Bean, POCO
[JsonObject("aaa")]
public class AAA
{
[JsonProperty("prop1")]
public int Property1 { get; set; }
[JsonProperty("prop2")]
public string Property2 { get; set; }
[JsonProperty("prop3")]
public object Property3 { get; set; }
}
- シリアライズ・デシリアライズ
static void Main(string[] args)
{
AAA aaa1 = new AAA();
AAA aaa2 = new AAA();
aaa1.Property1 = 100;
aaa1.Property2 = "xxx";
aaa2.Property1 = 200;
aaa2.Property2 = "yyy";
aaa1.Property3 = aaa2;
string json = JsonConvert.SerializeObject(aaa1);
Console.WriteLine(json);
JObject jobj= (JObject)JsonConvert.DeserializeObject(json);
Primitive型とGeneric型を使用する †
- Primitive型とGeneric型の範囲であれば、JSONの型が特定できるので、parseできる。
- このため、Primitive型とGeneric型の範囲で自由な構造を組むことができる。
- しかし、Genericでは、Bean, POCOを使用するため(フィールド名・フィールド型の組み合わせが)自由な構造を組めない。
static void Main(string[] args)
{
List<Dictionary<string, string>> parent = new List<Dictionary<string, string>>();
Dictionary<string, string> child;
child = new Dictionary<string, string>();
child["aaa"] = "AAA";
child["bbb"] = "BBB";
child["ccc"] = "CCC";
parent.Add(child);
child = new Dictionary<string, string>();
child["xxx"] = "XXX";
child["yyy"] = "YYY";
child["zzz"] = "ZZZ";
parent.Add(child);
string json = JsonConvert.SerializeObject(parent);
Console.WriteLine(json);
parent = JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(json);
List<POCO>を使用する †
従って、List<POCO>等と言った、Generic+POCOも問題なく処理できる。
継承クラスで基底クラスの型を使用する †
- 派生型をシリアライズできるが、問題は派生型にデシリアライズできないこと。
(継承クラスのオブジェクトインスタンスを基底クラスでハンドルしようとした場合)
- バイナリ・シリアライズではこういうことができたが、それは型情報を持たせているから。
- 型情報を持たないJSONのシリアライズでは、こういうことはできない。
- DataContractJsonSerializer?では型情報の出力などもサポートされていたが、
昨今の相互運用性が重視されるJSON利用方法を考えると、もう使われない機能になった。
型が特定できない構造のJSON †
型が特定できない構造のJSONの例 †
シリアライズの対象にobject型を使用しており、
シリアライズの際、
- 任意の型が指定される。
- 出力されるされるJSONの構造が一定ではない。
object型を使用せざるを得ないケース †
以下の様な構造が一定ではないケースでは、
シリアライズの対象にobject型を使用せざるを得ない。
- 子要素JSONの構造が一定ではない(Fieldにobject型を使用する)
- 配列内のJSONの構造が一定ではない(object[], Hashtableを使用する)
JSON.NETのJObjectのLINQ to JSONで手動のデシリアライズ †
型が特定できない構造の(object型をシリアライズした)JSONを
デシリアライズするとJSONデータはJObject型に格納される。
この場合、JSON.NETのJObjectのLINQ to JSONで型の手動のデシリアライズを行う。
参考 †
DataContractJsonSerializer? †
WCFの既定のシリアライザ。
流行ってないので基本スルーだが、
使う場合は、以下の点に注意する。
JavaやJSON.NETのシリアライザとの違い。 †
.NETのDataContractJsonSerializer?はJavaやJSON.NETと、シリアライザの挙動が違う。
そのため、同じ形式の POJO または POCO であっても、シリアライズ後の JSON フォーマットが異なる。
Dictionaryのシリアライズ結果 †
特に、以下のように Dictionaryの JSON シリアライズ結果が異なる。
# | 技術 | POJO / POCO サンプル | シリアライズされた JSON |
1 | Java, JSON.NET | public class Sample { public String _key1; public String _key2; public void setKey1(String key1) { this._key1 = key1; } public String getKey1() { return this._key1; } public void setKey2(String key2) { this._key2 = key2; } public String getKey2() { return this._key2; } } | { "key1" : "value1", "key2" : "value2" } |
2 | public class Sample { public HashMap?<String, String> _key; public void setKey(HashMap?<String, String> key) { this._key = key; } public void getKey() { return this._key; } } |
3 | .NETのDataContractJsonSerializer? | public class Sample { public string key1 { get; set; } public string key2 { get; set; } } |
4 | public class Sample { public Dictionary<string, string> key { get; set; } } | [ { "Key": "Prop1", "Value": "Value1" }, { "Key": "Prop2", "Value": "Value2" } ]
同じ形式のジェネリック型であっても、 使用するSerializer次第でシリアライズ結果が異なる |
Efficient dictionary serialization, Nonsensical dictionary serialization †
この違いは、以下の機能のサポート状況によるものらしい。
System.Text.Json(.NET Core 3.0以降の標準) †
概要 †
経緯を読んでみると、JSON.NETに何か問題があるという話ではなく、
- JSON parse の組込サポートが無く、また、外部依存を外したかった。
- MSが主体的にJSONのparse処理の性能向上を図っていきたいと思っているらしい。
- Span <T>を使用する。
- UTF-16に変換せずUTF-8を直接処理する。
と言う事の様です。
詳細 †
実装方法 †
..オイオイ書くカモ...
移行方法 †
..オイオイ書くカモ...
参考 †
JSON.parse、JSON.stringify(JavaScript) †
JavaScriptでは、JSON.parse(シリアライズ)、JSON.stringify(デシリアライズ)が使用できる。
JSON.parse †
JSON 文字列を解析して JavaScript のオブジェクトに変換する(シリアライズ)。
JSON.stringify †
JavaScript の値を JSON 文字列に変換する(デシリアライズ)。
参考 †
応用 †
コンボ生成でJSONを使用する際の注意事項(順番保証)。 †
順番保証がされないので、
- Dictionary<string, string>ではなく、
{ "key1" : "value1", "key2" : "value2" }
- List<List<String>>を使用する。
[ [ "key1", "value1" ], [ "key2", "value2" ] ]
JSON.NETのJObjectを使って手動で任意の型にデシリアライズする。 †
- JSON.NETのJObjectでは、
- LINQ to JSONを使用して、任意のノードにアクセスできる。
- LINQ to JSONと言っても、単純にインデクサ的に処理できる模様。
Deserialize †
以下のようにデシリアライズできる。
JObject jObject = (JObject)JsonConvert.DeserializeObject(jsonResult);
if (jObject["Message"] != null)
{
// 正常終了
List<Dictionary<string, string>> list =
JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(jObject["Result"].ToString());
this.lstRecords.ItemsSource = list;
message = "正常終了しました";
}
else
{
・・・
Messageの戻りによって、Resultの方が変わる事例。
Serialize †
上記のシナリオに合わせて、以下のようにシリアライズできる。
単にobject型を使用したり、匿名型を使用したりして、
シリアライズするobject階層構造を可変にすればイイ。
object ret = null;
if(・・・)
{
ret = new { ErrorMSG = message };
}
else
{
List<Dictionary<string, string>> list = new List<Dictionary<string, string>>();
・・・
ret = new { Message = "", Result = list };
}
return Request.CreateResponse(HttpStatusCode.OK, ret);
参考 †
参考 †
Tags: :.NET開発, :.NET Core, :ASP.NET, :ASP.NET Web API