MS_JSONParseExperiments - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

JSONのparseを色々詊しおみた。

抂芁

ずある案件の芁件で、

非構造化デヌタを粘土现工のように
ゎリゎリず凊理したいが、そういったこずは、可胜なのだろうか

ずいうこずを調査した。

結論ずしおは、コチラの LINQ to JSON を䜿甚するこずで可胜。

補足本ペヌゞの構成: 怜蚌蚘録のため長いが、
論旚は䞀本道である。

① JSON に合わせおクラスを定矩する基本
     ↓
② 型が特定できる構造なら、JSON.NET が党郚やっおくれる
     ↓
③ 【型が特定できない構造】だず POCO にマップできない
     ↓
④ その堎合は JObjectLINQ to JSONで手動で扱う  ← 結論

぀たり、**「静的な型付けができない JSON をどう扱うか」**ずいう
䞀貫した問いに察する調査蚘録である。

JSON フォヌマットずクラスの定矩

JSON フォヌマットの確認

たず、どのようなフォヌマットの JSON を出力したいのかを確認する。

クラスの定矩

次に、出力したい JSON に合わせお、クラスを定矩する。

補足: この順序JSON が先、クラスが埌が重芁で、
JSONを送信するRESTサヌビスを䜜成する方法の
「JSON フォヌマットを、コントラクトずする」ず同じ䞻匵である。

基本

基本的には、以䞋のルヌルに埓っお、クラス・プロパティを定矩する。

  • JSON オブゞェクト{ キヌ名 : 倀 }の堎合
    • 「キヌ名」を名前に持぀プロパティを定矩する
  • JSON 配列[ 倀, 倀, ... ]の堎合
    • リストたたは配列のプロパティを定矩する
  • キヌ名が䞍定の堎合
    • キヌ名が䞍定実行時に決たるの堎合、事前のクラスプロパティ定矩ができない。
    • このようなずきは、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));

補足匿名型が有効な堎面: 匿名型は
**「䞀床きりの、返すだけの JSON」**に非垞に有効である。

堎面 評䟡
ログ出力、デバッグ情報 最適䞊のコヌド䟋がたさにこれ
単発の API の応答 有効
公開 API の応答 非掚奚契玄が型ずしお残らない

3 ぀目が芁点で、匿名型は
「どんな JSON を返す API なのか」がコヌド䞊に定矩ずしお残らない。
JSONを送信するRESTサヌビスを䜜成する方法で述べた
スキヌマ ファヌストの芳点では、
公開する API では明瀺的なクラスDTOを定矩すべきである。

なお、C# 9 以降は record を䜿うず
匿名型に近い簡朔さで名前付きの型を定矩できる。

Web Essentialsを䜿甚

Visual Studio の拡匵機胜である、Web Essentials を䜿うず、JSON からクラスを自動生成できる。
Web Essentials は、Visual Studio のメニュヌから、ツヌル-拡匵機胜ず曎新プログラムで远加できる。

Web Essentials

JSON 文字列をコピヌし、Visual Studio の゚ディタヌで、右クリックたたは線集メニュヌから、
圢匏を遞択しお貌り付け-JSON をクラスずしお貌り付けるを遞択するず、
その JSON フォヌマットに合ったクラスが生成される。

補足最新化拡匵機胜なしで䜿える: この機胜は
Visual Studio 2013 以降、暙準機胜ずしお組み蟌たれおいる。
Web Essentials を远加する必芁は無い。

線集→圢匏を遞択しお貌り付け
  ├─ JSON をクラスずしお貌り付ける
  └─ XML をクラスずしお貌り付ける

JSON をクリップボヌドにコピヌした状態でないずメニュヌが出ない
点に泚意する。

なお、生成されるクラスは

  • プロパティ名が Rootobject、Class1 ずいった機械的な名前になる、
  • null の項目は object 型になる、
  • 数倀が int になる実際には long や decimal が必芁かもしれない

ため、生成埌に手で調敎する前提で䜿うのが正しい。
「たたき台を䜜る」道具である。

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"}
  • デシリアラむズ

    • 型を指定しおデシリアラむズ

      • コヌド
        AAA 型にデシリアラむズされる。

        AAA aaa = JsonConvert.DeserializeObject<AAA>(json);
    • 型を指定せずにデシリアラむズ

      • コヌド
        JObject型にデシリアラむズされる。

        JObject jobj = (JObject)JsonConvert.DeserializeObject(json);
  • 参考

シリアラむズ・デシリアラむズを制埡する

シリアラむズ・デシリアラむズのメ゜ッドに、

する方法がある。

シリアラむズ・デシリアラむズの既定倀を制埡するオブゞェクト

  • シリアラむズ・デシリアラむズ時に、
    シリアラむズ・デシリアラむズの既定倀を制埡する
    オブゞェクト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 の同時指定。
    • NullValueHandling
      null の倀をどう凊理するか
      • Include
        オブゞェクトをシリアラむズおよびデシリアラむズするずきには、null 倀を含めたす。
      • Ignore
        オブゞェクトのシリアラむズおよびデシリアラむズ時には null 倀を無芖したす。
    • MissingMemberHandling
      デシリアラむズ時、メンバが存圚しない堎合の動䜜
      • Ignore
        無芖する既定
      • Error
        䟋倖を出力
  • ContractResolver

    • DefaultContractResolver
      メンバ名は PascalCase で JSON 化される。
    • CamelCasePropertyNamesContractResolver
      メンバ名は CamelCase で JSON 化される。
  • Formatting

    • None
      むンデントなし
    • Indented
      むンデントを぀ける

補足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
  • 属性の蚭定方法
[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を䜿甚する

  • 型を明瀺
    • 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を䜿甚する

埓っお、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 利甚方法を考えるず、もう䜿われない機胜になった。
  • 凊理
    • 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 以降が
正しい答えで、蚱可する型を宣蚀的に限定したうえで
倚態的なシリアラむズデシリアラむズができる。

型が特定できない構造のJSON

型が特定できない構造のJSONの䟋

シリアラむズの察象に object 型を䜿甚しおおり、

シリアラむズの際、

  • 任意の型が指定される。
  • 出力される JSON の構造が䞀定ではない。

移行メモ䜓裁: 原兞の「出力されるされるJSON」は
「される」が重耇しおいたため修正した。

object型を䜿甚せざるを埗ないケヌス

以䞋の様な構造が䞀定ではないケヌスでは、
シリアラむズの察象に object 型を䜿甚せざるを埗ない。

  • 子芁玠 JSON の構造が䞀定ではないField に object 型を䜿甚する
  • 配列内の JSON の構造が䞀定ではないobject[], Hashtable を䜿甚する

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) { ... }
  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, Nonsensical dictionary serialization

この違いは、以䞋の機胜のサポヌト状況によるものらしい。

System.Text.Json.NET Core 3.0以降の暙準

抂芁

経緯を読んでみるず、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 ぞの倉換自䜓が無駄だった、ずいうのが芁点である。

詳现

実装方法

..オむオむ曞くカモ...

移行方法

補足「掚枬や解釈関連の機胜はサポヌトされない」の具䜓䟋: 未蚘茉の
節だが、この䞀行が移行時の躓きどころを的確に予告しおいる。

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 の付け替えを忘れるず属性が効かなくなる
コンパむルは通る。移行時の兞型的な事故である。

参考

JSON.parse、JSON.stringifyJavaScript

JavaScript では、JSON.parseシリアラむズ、JSON.stringifyデシリアラむズが䜿甚できる。

JSON.parse

JSON 文字列を解析しお JavaScript のオブゞェクトに倉換するシリアラむズ。

JSON.stringify

JavaScript の倀を JSON 文字列に倉換するデシリアラむズ。

移行メモ正誀: 原兞は
**JSON.parse を「シリアラむズ」、JSON.stringify を「デシリアラむズ」**ず
蚘茉しおいるが、逆である。

メ゜ッド 動䜜 正しい呌称
JSON.parse 文字列 → オブゞェクト デシリアラむズ埩元
JSON.stringify オブゞェクト → 文字列 シリアラむズ盎列化

各節の説明文「JSON 文字列を解析しお〜倉換する」等は正しいため、
括匧内の甚語のみが取り違えられおいる。
原兞どおりに移行したうえで、ここに蚂正を蚘した。

参考

応甚

コンボ生成でJSONを䜿甚する際の泚意事項順番保蚌。

順番保蚌がされないので、

  • 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 蚭蚈の基本
である
メニュヌ、手順、ランキングなども同様。

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 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 のステヌタス コヌドで衚す

方が、クラむアントの実装が単玔になる。

参考

参考


Tags: 移行, .NET開発, .NET Core, ASP.NET, ASP.NET Web API

⚠ **GitHub.com Fallback** ⚠