マイクロソフト系技術情報 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 のメニューから、[ツール] - [拡張機能と更新プログラム] で追加できる。

WebEssentials.png

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"}
  • デシリアライズ
  • 型を指定してデシリアライズ
    • コード
      AAA型にデシリアライズされる。
      AAA aaa = JsonConvert.DeserializeObject<AAA>(json);
  • 型を指定せずにデシリアライズ
    • コード
      JObject型にデシリアライズされる。
      JObject jobj = (JObject)JsonConvert.DeserializeObject(json);

既定値を制御する属性

  • プロパティの既定値の設定方法
    プロパティの属性にSystem.ComponentModel?.DefaultValue?属性を指定する。
    [JsonObject("aaa")]
    public class AAA
    {
      [JsonProperty("prop1")]
      public int Property1 { get; set; }
      [JsonProperty("prop2")]
      [DefaultValue("hogehoge")]
      public string Property2  { get; set; }
    }
  • シリアライズ(デシリアライズ時)の既定値の取り扱い
    DefaultValueHandling? 列挙体でシリアライズ(デシリアライズ時)の既定値の取り扱い方法を制御できる。
  • DefaultValueHandling?
    #設定値動作
    1Includeシリアライズ時に既定値の項目をJSONに含める(既定)。
    2Ignoreシリアライズ時に既定値の項目をJSONに含めない。
    3Populateデシリアライズ時にJSON文字列中に要素が存在しない場合でも既定値を設定。
    4IgnoreAndPopulate?IgnoreとPopulateの同時指定。
  • 指定方法
    • プロパティの属性として指定
      [JsonProperty("prop2", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
      [DefaultValue("hogehoge")]
      public string Property2 { get; set; }
  • シリアライズ時に指定
    string json = JsonConvert.SerializeObject(aaa, Formatting.Indented, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });
  • デシリアライズ時に指定
    AAA aaa = JsonConvert.DeserializeObject<UserModel>(jsonstring, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });

その他、属性

様々な属性がある。

  • JsonProperty?属性
    JSONのキーを指定する
  • JsonIgnore?属性
    シリアライズ対象外を設定する

応用

上記のリンク先の記事に

「階層構造を持った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型にデシリアライズされる)。
    [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);

配列(List<AAA>)を使用する

配列(Generic)も問題なく処理できる。

[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);

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);

継承を使用する

  • 派生型をシリアライズできるが、問題は派生型にデシリアライズできないこと。
  • バイナリ・シリアライズではこういうことができたが、それは型情報を持たせているから。
  • 型情報を持たないJSONのシリアライズでは、こういうことはできない。
  • DataContractJsonSerializer?では型情報の出力などもサポートされていたが、
    昨今の相互運用性が重視されるJSON利用方法を考えると、もう使われない機能になった。
[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

型が特定できない構造の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
1Java, JSON.NETpublic 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" }
2public 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; }
}
4public class Sample {
    public Dictionary<string, string> key { get; set; }
}
[
    {
        "Key": "Prop1",
        "Value": "Value1"
    },
    {
        "Key": "Prop2",
        "Value": "Value2"
    }
]

同じ形式のジェネリック型であっても、
使用するSerializer次第でシリアライズ結果が異なる

Efficient dictionary serialization, Nonsensical dictionary serialization

この違いは、以下の機能のサポート状況によるものらしい。

JSON.parse、JSON.stringify(JavaScript

JavaScriptでは、JSON.parse(シリアライズ)、JSON.stringify(デシリアライズ)が使用できる。

JSON.parse

JSON 文字列を解析して JavaScript のオブジェクトに変換する(シリアライズ)。

JSON.stringify

JavaScript の値を JSON 文字列に変換する(デシリアライズ)。

参考

応用

JSONを送信するRESTサービスを作成する方法

JSONを受信するJSON-RPCサービスを作成する方法

コンボ生成で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


添付ファイル: fileWebEssentials.png 309件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-05-10 (木) 10:32:07 (12d)