-
Notifications
You must be signed in to change notification settings - Fork 0
MS_JSONParseExperiments
- 戻る(JSON)
- JSONを送信するRESTサービスを作成する方法
- JSONを受信するJSON-RPCサービスを作成する方法
- JSONのparseを色々試してみた。
とある案件の要件で、
非構造化データを粘土細工のように
ゴリゴリと処理したいが、そういったことは、可能なのだろうか?
ということを調査した。
結論としては、コチラの LINQ to JSON を使用することで可能。
補足(本ページの構成): 検証記録のため長いが、
論旨は一本道である。① JSON に合わせてクラスを定義する(基本) ↓ ② 型が特定できる構造なら、JSON.NET が全部やってくれる ↓ ③ 【型が特定できない構造】だと POCO にマップできない ↓ ④ その場合は JObject(LINQ to JSON)で手動で扱う ← 結論つまり、**「静的な型付けができない JSON をどう扱うか」**という
一貫した問いに対する調査記録である。
まず、どのようなフォーマットの JSON を出力したいのかを確認する。
次に、出力したい JSON に合わせて、クラスを定義する。
補足: この順序(JSON が先、クラスが後)が重要で、
JSONを送信するRESTサービスを作成する方法の
「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 を作成することができる。
- ただし、Dictionary を DataContractJsonSerializer で
JsonConvert.SerializeObject(new
{
date = DateTime.Now,
command = SqlExecuteType.Reader,
text = this.ClearText(this._commandText),
param = this._commandParameters,
ms = this._stopwatch.ElapsedMilliseconds
}, Formatting.None));補足(匿名型が有効な場面): 匿名型は
**「一度きりの、返すだけの JSON」**に非常に有効である。
場面 評価 ログ出力、デバッグ情報 最適(上のコード例がまさにこれ) 単発の API の応答 有効 公開 API の応答 非推奨(契約が型として残らない) 3 つ目が要点で、匿名型は
「どんな JSON を返す API なのか」がコード上に定義として残らない。
JSONを送信するRESTサービスを作成する方法で述べた
スキーマ ファーストの観点では、
公開する API では明示的なクラス(DTO)を定義すべきである。なお、C# 9 以降は
recordを使うと
匿名型に近い簡潔さで名前付きの型を定義できる。
Visual Studio の拡張機能である、Web Essentials を使うと、JSON からクラスを自動生成できる。
Web Essentials は、Visual Studio のメニューから、[ツール]-[拡張機能と更新プログラム]で追加できる。

JSON 文字列をコピーし、Visual Studio のエディターで、右クリックまたは[編集]メニューから、
[形式を選択して貼り付け]-[JSON をクラスとして貼り付ける]を選択すると、
その JSON フォーマットに合ったクラスが生成される。
補足(最新化:拡張機能なしで使える): この機能は
Visual Studio 2013 以降、標準機能として組み込まれている。
Web Essentials を追加する必要は無い。[編集]→[形式を選択して貼り付け] ├─ JSON をクラスとして貼り付ける └─ XML をクラスとして貼り付けるJSON をクリップボードにコピーした状態でないとメニューが出ない
点に注意する。なお、生成されるクラスは
- プロパティ名が
Rootobject、Class1といった機械的な名前になる、nullの項目はobject型になる、- 数値が
intになる(実際にはlongやdecimalが必要かもしれない)ため、生成後に手で調整する前提で使うのが正しい。
「たたき台を作る」道具である。
ココが参考になる。
- C# で JSON を扱うライブラリ「Json.NET」を使ってみました - Qiita
http://qiita.com/ta-yamaoka/items/a7ff1d9651310ade4e76
[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型にデシリアライズされる。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
- JsonConvert.SerializeObject Method
シリアライズ・デシリアライズのメソッドに、
する方法がある。
-
シリアライズ・デシリアライズ時に、
シリアライズ・デシリアライズの既定値を制御する
オブジェクト(JsonSerializerSettings)を指定できる。-
シリアライズ時に指定
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 の同時指定。
- Include
- NullValueHandling
null の値をどう処理するか- Include
オブジェクトをシリアライズおよびデシリアライズするときには、null 値を含めます。 - Ignore
オブジェクトのシリアライズおよびデシリアライズ時には null 値を無視します。
- Include
- MissingMemberHandling
デシリアライズ時、メンバが存在しない場合の動作- Ignore
無視する(既定) - Error
例外を出力
- Ignore
- DefaultValueHandling
-
ContractResolver
- DefaultContractResolver
メンバ名は PascalCase で JSON 化される。 - CamelCasePropertyNamesContractResolver
メンバ名は CamelCase で JSON 化される。
- DefaultContractResolver
-
Formatting
- None
インデントなし - Indented
インデントをつける
- None
補足(
MissingMemberHandlingの既定値が「無視」である意味): この既定値は
API のバージョン互換性に直結するため、意識して選ぶ必要がある。【Ignore(既定)】 サーバが新しい項目を追加した JSON を返す ↓ 古いクライアントは知らない項目を【黙って捨てる】 → 前方互換性がある(壊れない) 【Error】 知らない項目があると例外 ↓ → サーバが項目を 1 つ足しただけでクライアントが全滅するつまり、既定の
Ignoreが API 連携では正しい。
これは「知らないものは無視する」という
**堅牢性の原則(Postel の法則)**に沿った挙動である。逆に、設定ファイルの読み込みなど
「タイプミスに気付きたい」場面ではErrorが適切になる。なお、**
System.Text.Jsonでも既定は「無視」**である。
- 様々な属性がある。
- JsonProperty 属性
JSON のキーを指定する - JsonIgnore 属性
シリアライズ対象外を設定する - DefaultValue 属性
既定値を設定する。 - , etc.
Serialization Attributes
https://www.newtonsoft.com/json/help/html/SerializationAttributes.htm
- JsonProperty 属性
- 属性の設定方法
[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; }補足(
JsonIgnoreはセキュリティ上も重要):JsonIgnoreは
「不要な項目を省く」だけでなく、
機密情報を応答に漏らさないための手段でもある。public class User { public string Id { get; set; } public string Name { get; set; } [JsonIgnore] // ← これが無いと API 応答に載る public string PasswordHash { get; set; } }ただし、「付け忘れ」に依存する設計は危険である。
より確実なのは、
手段 内容 DTO を分ける エンティティをそのまま返さない。応答専用のクラスを作る JsonIgnore補助的に エンティティ(DB のモデル)をそのまま API で返す設計は、
新しい列を追加した瞬間にそれが外部に露出するため、
公開 API では避けるのが原則である。
上記のリンク先の記事に
「階層構造を持った JSON でも問題なくデシリアライズ可能。
JSON 配列も List などにパースしてくれる。」
とあるが、何処までやってくれるか?
・・・検証の結果、
- 型が特定できる構造の JSON はシリアライズできる。
-
型が特定できない構造の JSONは、シリアライズは可能だが、
デシリアライズ結果は(インデクサが使用可能な)JObjectに格納される。
ということが解った。
以下は、型が特定できる構造の JSON を parse する例。
- 型を明示
- 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 型の範囲であれば、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> 等と言った、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);- 派生型をシリアライズできるが、問題は派生型にデシリアライズできないこと。
(継承クラスのオブジェクトインスタンスを基底クラスでハンドルしようとした場合)- バイナリ・シリアライズではこういうことができたが、それは型情報を持たせているから。
- 型情報を持たない 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
- .net - How do I tell DataContractJsonSerializer to not include the "__type" property - Stack Overflow
- 処理
- 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 のシリアライズでは、こういうことはできない」という
結論は正確であり、かつ重要な設計上の含意を持つ。【バイナリ シリアライズ(BinaryFormatter 等)】 "型名を含む" ので、復元時に元のクラスに戻せる ↓ しかし 【任意の型を復元できる = 攻撃者が任意の型を作らせられる】 → 重大な脆弱性(デシリアライズ攻撃) 【JSON】 型情報を持たない = 復元先の型は【受け取る側が決める】 → 安全だが、派生型は復元できないつまり、「できない」のは制約ではなく、安全側の設計である。
実際、BinaryFormatterは .NET 5 で非推奨、
.NET 9 で削除された(デシリアライズ攻撃のため)。どうしても多態性が必要な場合の手段は次のとおり。
手段 内容 安全性 判別子(discriminator)を自分で持つ {"type":"BBB", ...}を見て自分で分岐する安全(推奨) TypeNameHandling(JSON.NET)型名を JSON に埋め込む 危険(任意の型を生成され得る) [JsonDerivedType](.NET 7+)許可する派生型を明示して多態性を実現 安全
TypeNameHandling.Allは既知の脆弱性であり、
外部から受け取る JSON には絶対に使ってはならない。現在は
System.Text.Jsonの[JsonDerivedType](.NET 7 以降)が
正しい答えで、許可する型を宣言的に限定したうえで
多態的なシリアライズ/デシリアライズができる。
シリアライズの対象に object 型を使用しており、
シリアライズの際、
- 任意の型が指定される。
- 出力される JSON の構造が一定ではない。
移行メモ(体裁): 原典の「出力されるされるJSON」は
「される」が重複していたため修正した。
以下の様な構造が一定ではないケースでは、
シリアライズの対象に object 型を使用せざるを得ない。
- 子要素 JSON の構造が一定ではない(Field に object 型を使用する)
- 配列内の JSON の構造が一定ではない(
object[], Hashtable を使用する)
型が特定できない構造の(object 型をシリアライズした)JSON を
デシリアライズすると JSON データは JObject 型に格納される。
この場合、JSON.NET の JObject の LINQ to JSON で型の手動のデシリアライズを行う。
- Json.NET - Newtonsoft
http://www.newtonsoft.com/json
WCFの既定のシリアライザ。
流行ってないので基本スルーだが、
使う場合は、以下の点に注意する。
.NET の DataContractJsonSerializer は Java や JSON.NET と、シリアライザの挙動が違う。
そのため、同じ形式の POJO または POCO であっても、シリアライズ後の JSON フォーマットが異なる。
特に、以下のように Dictionary の JSON シリアライズ結果が異なる。
| # | 技術 | POJO / POCO サンプル | シリアライズされた JSON |
|---|---|---|---|
| 1 | Java, JSON.NET |
public class Sample {public String _key1;public String _key2;public void setKey1(String key1) { ... }public String getKey1() { ... }public void setKey2(String key2) { ... }public String getKey2() { ... }}
|
{ "key1" : "value1", "key2" : "value2" } |
| 2 | 同上 |
public class Sample {public HashMap<String, String> _key;public void setKey(HashMap<String, String> key) { ... }public void getKey() { ... }}
|
{ "key1" : "value1", "key2" : "value2" } |
| 3 | .NET の DataContractJsonSerializer |
public class Sample {public string key1 { get; set; }public string key2 { get; set; }}
|
{ "key1" : "value1", "key2" : "value2" } |
| 4 | 同上 |
public class Sample {public Dictionary<string, string> key { get; set; }}
|
[{"Key": "Prop1","Value": "Value1"},{"Key": "Prop2","Value": "Value2"}]同じ形式のジェネリック型であっても、 使用する Serializer 次第でシリアライズ結果が異なる |
移行メモ(表の変換): 原典は PukiWiki のセル結合記法(
~)で
「上のセルと同じ」を表現していたため、同じ値を展開して記載した。
また、Java 側の getter / setter の中身は
定型的な実装であったため{ ... }に省略した。
補足(この表が示す問題の深刻さ): 4 行目が本ページで
最も実務的な発見である。【Java / JSON.NET】 Map / Dictionary → { "key1":"value1", "key2":"value2" } ↑ 直感的。JSON オブジェクトになる 【DataContractJsonSerializer】 Dictionary → [ {"Key":"key1","Value":"value1"}, {"Key":"key2","Value":"value2"} ] ↑ KeyValuePair の【配列】になる(!)これがなぜ起きるかというと、
DataContractJsonSerializerは
Dictionary<K,V>を「KeyValuePair<K,V>のコレクション」として
そのまま素直にシリアライズしているためである。
.NET の内部構造をそのまま JSON に写した結果であり、
JSON としては不自然だが、.NET としては一貫している。実務上の影響は深刻で、
場面 何が起きるか Java 側と連携 同じ POCO/POJO なのに JSON が一致しない JavaScript から利用 obj.key1でアクセスできない(配列を走査する必要)後からシリアライザを変更 JSON の形が変わり、既存クライアントが壊れる これこそが、
JSONを送信するRESTサービスを作成する方法の
**「いきなり POCO をデータコントラクトとしない」**という主張の
根拠になっている実例である。なお、
System.Text.Jsonは Java / JSON.NET と同じ挙動
(Dictionaryを JSON オブジェクトにする)であるため、
現在この問題に当たるのは
WCF の既定設定を使っている場合に限られる。
この違いは、以下の機能のサポート状況によるものらしい。
- 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
- Json.NET - Home
経緯を読んでみると、JSON.NETに何か問題があるという話ではなく、
- JSON parse の組込サポートが無く、また、外部依存を外したかった。
- MS が主体的に JSON の parse 処理の性能向上を図っていきたいと思っているらしい。
-
Span<T>を使用する。 - UTF-16 に変換せず UTF-8 を直接処理する。
-
- 確定的な動作を重視し(推測や解釈は行われない)、
パフォーマンスとセキュリティを確保する。
と言う事の様です。
補足(3 つの理由のうち「外部依存を外したかった」が最も大きい): 経緯として
正確な整理だが、なぜ外部依存が問題だったのかを補足する。【従来】ASP.NET Core が Newtonsoft.Json に依存 ↓ アプリも Newtonsoft.Json を使う ↓ 【バージョンの衝突】 ・フレームワークが要求する版と、アプリが使いたい版が違う ・ライブラリごとに要求する版が違うフレームワークが特定のサードパーティ製ライブラリに
依存していること自体が、利用者にとっての制約になっていた。
標準ライブラリに取り込むことで、この問題が解消された。また、性能面の理由も実際に大きい。
観点 Newtonsoft.Json System.Text.Json 文字列の扱い UTF-16 に変換してから処理 UTF-8 のまま処理 メモリ 文字列を都度確保 Span<T>で確保を減らす速度 - 概ね 1.3〜2 倍程度速い HTTP の本文は元々 UTF-8 であるため、
UTF-16 への変換自体が無駄だった、というのが要点である。
..オイオイ書くカモ...
- ..オイオイ書くカモ...
- 推測や解釈関連の機能はサポートされない。
- 参考
- 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
- Newtonsoft.Json から System.Text.Json に移行する - .NET | Microsoft Docs
補足(「推測や解釈関連の機能はサポートされない」の具体例): 未記載の
節だが、この一行が移行時の躓きどころを的確に予告している。
System.Text.Jsonは厳格であり、
Newtonsoft.Json が「気を利かせて」やっていたことをしない。
挙動 Newtonsoft.Json System.Text.Json プロパティ名の大文字小文字 区別しない(既定) 区別する(既定)※ 数値を文字列で受け取る 変換する 例外(オプションで許可可) 末尾のカンマ、コメント 許容 例外(オプションで許可可) TypeNameHandlingあり 無し(安全側) 循環参照 設定で対応 ReferenceHandlerで対応(.NET 5 以降)※ ASP.NET Core の MVC では既定で大文字小文字を区別しない設定になる。
属性の対応は次のとおり。
Newtonsoft.Json System.Text.Json [JsonProperty("name")][JsonPropertyName("name")][JsonIgnore][JsonIgnore](名前空間が違う)[JsonObject]相当なし
[JsonIgnore]は名前が同じで名前空間が違うため、
usingの付け替えを忘れると属性が効かなくなる
(コンパイルは通る)。移行時の典型的な事故である。
- 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
JavaScript では、JSON.parse(シリアライズ)、JSON.stringify(デシリアライズ)が使用できる。
JSON 文字列を解析して JavaScript のオブジェクトに変換する(シリアライズ)。
JavaScript の値を JSON 文字列に変換する(デシリアライズ)。
移行メモ(正誤): 原典は
**JSON.parseを「シリアライズ」、JSON.stringifyを「デシリアライズ」**と
記載しているが、逆である。
メソッド 動作 正しい呼称 JSON.parse文字列 → オブジェクト デシリアライズ(復元) JSON.stringifyオブジェクト → 文字列 シリアライズ(直列化) 各節の説明文(「JSON 文字列を解析して〜変換する」等)は正しいため、
括弧内の用語のみが取り違えられている。
原典どおりに移行したうえで、ここに訂正を記した。
- 【JavaScript】JSON の parse と stringify メソッドの使い方 - TASK NOTES
http://www.task-notes.com/entry/20160719/1468858991 - JSON.stringify を改めて調べる。 - Qiita
http://qiita.com/qoAop/items/57d35a41ef9629351c3c
順番保証がされないので、
-
Dictionary<string, string>ではなく、{ "key1" : "value1", "key2" : "value2" } -
List<List<String>>を使用する。[ [ "key1", "value1" ], [ "key2", "value2" ] ]
補足(この回避策の理由): 短いが、実務上きわめて有用な指摘である。
【問題】 JSON のオブジェクトは、仕様上【キーの順序に意味が無い】 ↓ ・シリアライザが並べ替えるかもしれない ・JavaScript のオブジェクトも順序を保証しない(数値キーは特に) ↓ コンボボックスの選択肢が【勝手に並び替わる】 【解決】 配列は【順序に意味がある】 ↓ [ ["key1","value1"], ["key2","value2"] ] とすれば順序が保たれる現在の書き方であれば、オブジェクトの配列にする方が読みやすい。
[ { "value": "1", "text": "選択肢A" }, { "value": "2", "text": "選択肢B" } ]「順序に意味があるものは配列で表す」という原則は、
コンボボックスに限らずJSON 設計の基本である
(メニュー、手順、ランキングなども同様)。
JSON.NETの JObject では、
- LINQ to JSON を使用して、任意のノードにアクセスできる。
- LINQ to JSON と言っても、単純にインデクサ的に処理できる模様。
以下のようにデシリアライズできる。
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 の方が変わる事例。
上記のシナリオに合わせて、以下のようにシリアライズできる。
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 階層構造を可変にすればイイ。
補足(これが本ページの結論部分): 冒頭の
**「非構造化データを粘土細工のようにゴリゴリと処理したい」**という
要件に対する答えがここである。【型が決まっている部分】 → POCO にデシリアライズ(型安全) 【型が決まっていない部分】 → JObject で受けて、インデクサで辿る ↓ 中身が判明した時点で JsonConvert.DeserializeObject<T>(jObject["Result"].ToString()) ↓ その部分だけ型付きで扱うつまり、「全部を型で受ける」か「全部を動的に扱う」かの
二択ではなく、部分ごとに使い分けられるというのが要点である。なお、コード例では
jObject["Result"].ToString()で文字列に戻してから
再度デシリアライズしているが、
現在はToObject<T>()を使う方が簡潔かつ効率的である。var list = jObject["Result"].ToObject<List<Dictionary<string, string>>>();
System.Text.JsonではJsonNode/JsonDocumentが
JObject に相当する。
JSON.NET System.Text.Json JObject(可変)JsonNode/JsonObject(可変。.NET 6 以降)JToken.ToObject<T>()JsonNode.Deserialize<T>()- JsonDocument(読み取り専用・高速)読むだけなら
JsonDocument(確保が少ない)、
組み立て・書き換えが要るならJsonNodeという使い分けになる。なお、この設計(応答の形が状況で変わる)は
使い勝手はよいが、API の契約としては曖昧になる。
JSONを送信するRESTサービスを作成する方法の
スキーマ ファーストの観点では、
- 正常時と異常時で形を変えない(常に同じ枠を返す)、
- 異常は HTTP のステータス コードで表す
方が、クライアントの実装が単純になる。
- 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
- Querying JSON with LINQ
- 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
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。