「[[マイクロソフト系技術情報 Wiki>http://techinfoofmicrosofttech.osscons.jp/]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。 -[[戻る>JSON]] * 目次 [#pe665aa8] #contents *概要 [#o65cd171] とある案件の要件で、 >非構造化データを粘土細工のように~ ゴリゴリと処理したいが、そういったことは、可能なのだろうか? ということを調査した。 結論としては、[[コチラ>#ud85b2ed]]のLINQ to JSONを使用することで可能。 *JSON フォーマットとクラスの定義 [#vf3529ba] **JSON フォーマットの確認 [#dbc5c33d] まず、どのようなフォーマットの JSON を出力したいのかを確認する。 **クラスの定義 [#e10b9cbb] 次に、出力したい JSON に合わせて、クラスを定義する。 ***基本 [#i16a4292] 基本的には、以下のルールに従って、クラス・プロパティを定義する。 -JSON オブジェクト ({ キー名 : 値 }) の場合 --「キー名」を名前に持つプロパティを定義する -JSON 配列 ([ 値1, 値2, ... ]) の場合 --リストまたは配列のプロパティを定義する -キー名が不定の場合 --キー名が不定 (実行時に決まる) の場合、事前のクラス/プロパティ定義ができない。~ --このようなときは、Dictionary<TKey, TValue> を、Dictionary<string, object> として定義する。 ***ルール [#t6d4fac1] 基本的に、以下のようにJSON出力を想定し、クラスを定義する。 |#|出力したいJSON|定義するクラス・プロパティ|h |1|{ "key1" : "value1", "key2" : "value2" }|public class Sample&br;{&br; ''string key1'' { get; set; }&br; ''string key2'' { get; set; }&br;}&br;&br;若しくは、&br;''Dictionary<string, string>'' dic;| |2|[ "1", "2", "3" ]|''List<string>'' listData { get; set; }| |3|{ "key1" : [ "1", "2", "3" ] }|public class Sample&br;{&br; ''List<string> key1'' { get; set; }&br;}| |4|{ "key" : { "key1" : "value1", "key2" : "value2" } }|public class Sample&br;{&br; ''string key1'' { get; set; }&br; ''string key2'' { get; set; }&br;}&br;public class Sample2&br;{&br; ''Sample key'' { get; set; }&br;}&br;&br;若しくは、&br;''Dictionary<string, Sample>'' dic;&br;&br;若しくは、&br;''Dictionary<string, Dictionary<string, string>>'' dic;| |5|[ { "key1" : "value1", "key2" : "value2" }, &br;{ "key1" : "value3", "key2" : "value4" } ]|public class Sample&br;{&br; ''string key1'' { get; set; }&br; ''string key2'' { get; set; }&br;}&br;&br;''List<Sample>'' list;| -補足 --ただし、DictionaryをDataContractJsonSerializerで~ シリアライズ・デシリアライズする場合、[[下記の様に>#w5b8ea38]]、相互運用性に問題があるので注意する。 --上記のように、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を使用 [#y3ecc544] Visual Studio の拡張機能である、Web Essentials を使うと、JSON からクラスを自動生成できる。~ Web Essentials は、Visual Studio のメニューから、[ツール] - [拡張機能と更新プログラム] で追加できる。~ &ref(WebEssentials.png,nolink,80%); JSON 文字列をコピーし、Visual Studio のエディターで、右クリックまたは [編集] メニューから、~ [形式を選択して貼り付け] - [JSON をクラスとして貼り付ける] を選択すると、~ その JSON フォーマットに合ったクラスが生成される。 *JSON.NET [#tf2395f2] ココが参考になる。 -C#でJSONを扱うライブラリ「Json.NET」を使ってみました - Qiita~ http://qiita.com/ta-yamaoka/items/a7ff1d9651310ade4e76 **基本(Bean, POCO) [#tf0108ab] ***シリアライズ・デシリアライズ [#df8d3cd2] [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"} -デシリアライズ --型を指定してデシリアライズ ---コード~ AAA型にデシリアライズされる。 AAA aaa = JsonConvert.DeserializeObject<AAA>(json); --型を指定せずにデシリアライズ ---コード~ [[JObject>#p9a9850a]]型にデシリアライズされる。 JObject jobj = (JObject)JsonConvert.DeserializeObject(json); -参考 --JsonConvert.SerializeObject Method~ http://www.newtonsoft.com/json/help/html/Overload_Newtonsoft_Json_JsonConvert_SerializeObject.htm --JsonConvert.DeserializeObject Method~ http://www.newtonsoft.com/json/help/html/Overload_Newtonsoft_Json_JsonConvert_DeserializeObject.htm ***シリアライズ・デシリアライズを制御する [#yea55b9b] シリアライズ・デシリアライズのメソッドに、 -[[引数(オブジェクト)を指定>#q78529c3]] -[[POCOプロパティ属性を指定>#adf4b3f4]] する方法がある。 ***シリアライズ・デシリアライズの既定値を制御するオブジェクト [#q78529c3] -シリアライズ・デシリアライズ時に、~ シリアライズ・デシリアライズの既定値を制御する~ オブジェクト([[JsonSerializerSettings>https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonSerializerSettings.htm]])を指定できる。 --シリアライズ時に指定 string json = JsonConvert.SerializeObject(aaa, Formatting.Indented, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate }); --デシリアライズ時に指定 AAA aaa = JsonConvert.DeserializeObject<UserModel>(jsonstring, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate }); -Handling --DefaultValueHandling~ ---Include~ シリアライズ時に既定値の項目をJSONに含める(既定)。 ---Ignore~ シリアライズ時に既定値の項目をJSONに含めない。 ---Populate~ デシリアライズ時にJSON文字列中に要素が存在しない場合でも既定値を設定。 ---IgnoreAndPopulate~ IgnoreとPopulateの同時指定。 --NullValueHandling~ nullの値をどう処理するか ---Include~ オブジェクトをシリアライズおよびデシリアライズするときには、null値を含めます。 ---Ignore~ オブジェクトのシリアライズおよびデシリアライズ時にはnull値を無視します。 --MissingMemberHandling~ デシリアライズ時、メンバが存在しない場合の動作 ---Ignore~ 無視する(既定) ---Error~ 例外を出力 -ContractResolver~ --DefaultContractResolver~ メンバ名はPascalCaseでJSON化される。 --CamelCasePropertyNamesContractResolver~ メンバ名はCamelCaseでJSON化される。 -Formatting --None~ インデントなし --Indented~ インデントをつける ***シリアライズ・デシリアライズの既定値を制御する属性 [#adf4b3f4] -様々な属性がある。 --JsonProperty属性~ JSONのキーを指定する --JsonIgnore属性~ シリアライズ対象外を設定する --DefaultValue属性~ 既定値を設定する。 --, etc.~ Serialization Attributes~ https://www.newtonsoft.com/json/help/html/SerializationAttributes.htm -属性の設定方法 [JsonObject("aaa")] public class AAA { [JsonProperty("prop1")] public int Property1 { get; set; } [JsonProperty("prop2")] [DefaultValue("hogehoge")] public string Property2 { get; set; } [JsonIgnore] public string Property3 { get; set; } } -JsonProperty属性には、更に様々なプロパティを設定可能。 [JsonProperty("prop2", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] [DefaultValue("hogehoge")] public string Property2 { get; set; } **応用 [#j19bc906] 上記のリンク先の記事に >「階層構造を持ったJSONでも問題なくデシリアライズ可能。~ JSON配列もListなどにパースしてくれる。」 とあるが、何処までやってくれるか? ・・・検証の結果、 -型が特定できる構造のJSONはシリアライズできる。 -[[型が特定できない構造のJSON>#ud85b2ed]]は、シリアライズは可能だが、~ デシリアライズ結果は(インデクサが使用可能な)[[JObject>#p9a9850a]]に格納される。 ということが解った。 以下は、型が特定できる構造のJSONをparseする例。 ***階層構造を持ったBean, POCOを使用する [#q9cc3f69] -型を明示 --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>#p9a9850a]]型にデシリアライズされる)。 --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型を使用する [#p067db84] -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>を使用する [#c749f4e3] 従って、List<POCO>等と言った、Generic+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 aaa1 = new AAA(); AAA aaa2 = new AAA(); aaa1.Property1 = 100; aaa1.Property2 = "xxx"; aaa2.Property1 = 200; aaa2.Property2 = "yyy"; aaa1.Property3 = aaa2; List<AAA> lstaaa = new List<AAA>(); lstaaa.Add(aaa1); lstaaa.Add(aaa2); string json = JsonConvert.SerializeObject(lstaaa); Console.WriteLine(json); lstaaa = JsonConvert.DeserializeObject<List<AAA>>(json); ***継承クラスで基底クラスの型を使用する [#q73512eb] -派生型をシリアライズできるが、問題は派生型にデシリアライズできないこと。~ (継承クラスのオブジェクトインスタンスを基底クラスでハンドルしようとした場合) --バイナリ・シリアライズではこういうことができたが、それは型情報を持たせているから。 --型情報を持たないJSONのシリアライズでは、こういうことはできない。 -DataContractJsonSerializerでは型情報の出力などもサポートされていたが、~ 昨今の相互運用性が重視されるJSON利用方法を考えると、もう使われない機能になった。 --.net - How do I tell DataContractJsonSerializer to not include the "__type" property - Stack Overflow~ https://stackoverflow.com/questions/17815772/how-do-i-tell-datacontractjsonserializer-to-not-include-the-type-property -処理 --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; } } [JsonObject("bbb")] public class BBB : AAA { [JsonProperty("prop4")] public int Property4 { get; set; } } [JsonObject("ccc")] public class CCC : AAA { [JsonProperty("prop4")] public string Property4 { get; set; } } --シリアライズ・デシリアライズ static void Main(string[] args) { AAA aaa1 = new AAA(); AAA aaa2 = new AAA(); aaa1.Property1 = 100; aaa1.Property2 = "xxx"; aaa1.Property3 = new BBB(); // AAAの派生のBBB aaa2.Property1 = 200; aaa2.Property2 = "yyy"; aaa2.Property3 = new CCC(); // AAAの派生のCCC List<AAA> lstaaa = new List<AAA>(); lstaaa.Add(aaa1); lstaaa.Add(aaa2); string json = JsonConvert.SerializeObject(lstaaa); Console.WriteLine(json); // BBBとCCCがAAA型のProperty3フィールドにデシリアライズされない。 lstaaa = JsonConvert.DeserializeObject<List<AAA>>(json); **型が特定できない構造のJSON [#ud85b2ed] ***型が特定できない構造のJSONの例 [#sdf7fbe0] シリアライズの対象にobject型を使用しており、 シリアライズの際、 -任意の型が指定される。 -出力されるされるJSONの構造が一定ではない。 ***object型を使用せざるを得ないケース [#ea76ada5] 以下の様な構造が一定ではないケースでは、~ シリアライズの対象にobject型を使用せざるを得ない。 -子要素JSONの構造が一定ではない(Fieldにobject型を使用する) -配列内のJSONの構造が一定ではない(object[], Hashtableを使用する) ***JObjectのLINQ to JSONで手動のデシリアライズ [#vd7fdcd8] 型が特定できない構造の(object型をシリアライズした)JSONを~ デシリアライズするとJSONデータは[[JObject>#p9a9850a]]型に格納される。 この場合、JSON.NETの[[JObject>#p9a9850a]]のLINQ to JSONで型の[[手動のデシリアライズを行う>#p9a9850a]]。 ***参考 [#w963879c] -Json.NET - Newtonsoft~ http://www.newtonsoft.com/json *DataContractJsonSerializer [#t365ab51] [[WCF]]の既定のシリアライザ。 流行ってないので基本スルーだが、~ 使う場合は、以下の点に注意する。 **Javaや[[JSON.NET>#tf2395f2]]のシリアライザとの違い。 [#w5b8ea38] .NETのDataContractJsonSerializerはJavaや[[JSON.NET>#tf2395f2]]と、シリアライザの挙動が違う。~ そのため、同じ形式の POJO または POCO であっても、シリアライズ後の JSON フォーマットが異なる。 ***Dictionaryのシリアライズ結果 [#oe9db4c8] 特に、以下のように Dictionaryの JSON シリアライズ結果が異なる。 |#|技術|POJO / POCO サンプル|シリアライズされた JSON|h |1|Java, JSON.NET|public class Sample {&br(); public String _key1;&br(); public String _key2;&br(); public void setKey1(String key1) {&br(); this._key1 = key1;&br(); }&br(); public String getKey1() {&br(); return this._key1;&br(); }&br(); public void setKey2(String key2) {&br(); this._key2 = key2;&br(); }&br(); public String getKey2() {&br(); return this._key2;&br(); }&br();}|{ "key1" : "value1", "key2" : "value2" }| |2|~|public class Sample {&br(); public HashMap<String, String> _key;&br(); public void setKey(HashMap<String, String> key) {&br(); this._key = key;&br(); }&br(); public void getKey() {&br(); return this._key;&br(); }&br();}|~| |3|.NETのDataContractJsonSerializer|public class Sample {&br(); public string key1 { get; set; }&br(); public string key2 { get; set; }&br();}|~| |4|~|public class Sample {&br(); public Dictionary<string, string> key { get; set; }&br();}|[&br(); {&br(); "Key": "Prop1", &br(); "Value": "Value1"&br(); }, &br(); {&br(); "Key": "Prop2", &br(); "Value": "Value2"&br(); }&br();]&br();&br();''同じ形式のジェネリック型であっても、&br();使用するSerializer次第でシリアライズ結果が異なる''| ***Efficient dictionary serialization, Nonsensical dictionary serialization [#q3d85516] この違いは、以下の機能のサポート状況によるものらしい。 -Efficient dictionary serialization~ http://stackoverflow.com/questions/1207731/how-can-i-deserialize-json-to-a-simple-dictionarystring-string-in-asp-net -Nonsensical dictionary serialization~ http://stackoverflow.com/questions/4559991/any-way-to-make-datacontractjsonserializer-serialize-dictionaries-properly -参考 --Json.NET - Home~ http://json.codeplex.com/ --Json.NET - Newtonsoft~ http://www.newtonsoft.com/json *System.Text.Json(.NET Core 3.0以降の標準) [#yffffda0] **概要 [#p858ff72] 経緯を読んでみると、[[JSON.NET>#tf2395f2]]に何か問題があるという話ではなく、 -JSON parse の組込サポートが無く、また、外部依存を外したかった。 -MSが主体的にJSONのparse処理の性能向上を図っていきたいと思っているらしい。 --Span <T>を使用する。 --UTF-16に変換せずUTF-8を直接処理する。 -確定的な動作を重視し(推測や解釈は行われない)、~ パフォーマンスとセキュリティを確保する。 と言う事の様です。 **詳細 [#o670eb6c] ***実装方法 [#i5429254] ..オイオイ書くカモ... ***移行方法 [#ve67325d] ..オイオイ書くカモ... -..オイオイ書くカモ... -推測や解釈関連の機能はサポートされない。 -参考 --Newtonsoft.Json から System.Text.Json に移行する - .NET | Microsoft Docs~ https://docs.microsoft.com/ja-jp/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to **参考 [#c26024a7] -Try the new System.Text.Json APIs | .NET Blog~ https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/ -System.Text.Json and new built-in JSON support in .NET Core - Scott Hanselman~ https://www.hanselman.com/blog/SystemTextJsonAndNewBuiltinJSONSupportInNETCore.aspx *JSON.parse、JSON.stringify(JavaScript) [#e56c0f75] JavaScriptでは、JSON.parse(シリアライズ)、JSON.stringify(デシリアライズ)が使用できる。 **JSON.parse [#a6e99c68] JSON 文字列を解析して JavaScript のオブジェクトに変換する(シリアライズ)。 **JSON.stringify [#t99dd427] JavaScript の値を JSON 文字列に変換する(デシリアライズ)。 **参考 [#w271bb4b] -【JavaScript】JSONのparseとstringifyメソッドの使い方 - TASK NOTES~ http://www.task-notes.com/entry/20160719/1468858991 -JSON.stringifyを改めて調べる。 - Qiita~ http://qiita.com/qoAop/items/57d35a41ef9629351c3c *応用 [#wa17d326] **[[JSONを送信するRESTサービスを作成する方法]] [#qd92d5cc] **[[JSONを受信するJSON-RPCサービスを作成する方法]] [#t15e8c2b] **コンボ生成でJSONを使用する際の注意事項(順番保証)。 [#x5ceca64] 順番保証がされないので、 -Dictionary<string, string>ではなく、 { "key1" : "value1", "key2" : "value2" } -List<List<String>>を使用する。 [ [ "key1", "value1" ], [ "key2", "value2" ] ] **JObjectを使って手動で任意の型にデシリアライズする。 [#p9a9850a] [[JSON.NET>#tf2395f2]]のJObjectでは、 -LINQ to JSONを使用して、任意のノードにアクセスできる。 -LINQ to JSONと言っても、単純にインデクサ的に処理できる模様。 ***Deserialize [#ada4a763] 以下のようにデシリアライズできる。 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 [#d3e7cfe9] [[上記のシナリオ>#ada4a763]]に合わせて、以下のようにシリアライズできる。 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); 単にobject型を使用したり、匿名型を使用したりして、~ シリアライズするobject階層構造を可変にすればイイ。 ***参考 [#udde6f7d] -memo: C#でJSON配列のパースに苦労した話~ https://kazyx.blogspot.com/2013/09/cjson.html -JSON.NETで変なJSONを読み込む方法 - かずきのBlog@hatena~ http://blog.okazuki.jp/entry/2014/04/19/235806 -C# + LINQ でJSONのパースをする - Qiita~ http://qiita.com/numa08/items/475ab3e8aa9e10441a26 -Json.NET (Newtonsoft.Json) の基本的な使い方 | ITメモ~ https://netweblog.wordpress.com/2016/10/24/json-net-newtonsoft-json-usage/ -LINQ to JSON~ http://www.newtonsoft.com/json/help/html/LINQtoJSON.htm --Querying JSON with LINQ~ http://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm *参考 [#p0c686c1] -Introduction~ http://www.newtonsoft.com/json/help/html/Introduction.htm -[C#] C#でJSONを扱う方法まとめ | Developers.IO~ http://dev.classmethod.jp/etc/c-sharp-json/ ---- Tags: [[:.NET開発]], [[:.NET Core]], [[:ASP.NET]], [[:ASP.NET Web API]]