NDW

アプリ開発やトラブルシューティング等のノウハウ、キャンプや登山の紹介や体験談など。

.NET Core 1. システムエンジニアリング 実装技術

C#: キー・値の順番を保持してJSONをデシリアライズ

投稿日:

はじめに

  • キー・値形式のJSONを解析する場合、JsonSerializer.Deserialize<Dictionary<T, V>>()を使って簡単に実現できますが、キー・値の順番は保証されません。
    ここでは、このキー・値の順番を保証する方法を紹介します。
  • 次の環境を使用して動作確認しています。
    OS Windows 10(64ビット)
    IDE Microsoft Visual Studio Community 2019(16.8.5) + C#(8.0)
  • 完全なソースコードはこちらで公開しています。
  • なお、System.Text.Jsonでサポートされているコレクション型はこちらのリファレンスをご覧ください。

前提データ・クラス

サンプルで使用するJSONデータ、JSONデータを格納するクラスです。

  • キー・値形式になっているJSONを使用します。
    本来であれば、JsonSerializer.Deserialize<Dictionary<string, MyEntity>>()で解析できます。
    {
      "key1": {
        "value1": 111,
        "value2": "test1",
        "value3": [1, 2, 3]
      },
      "key2": {
        "value1": 222,
        "value2": "test2",
        "value3": [2, 3, 4]
      }
    }
    
  • 次のクラスにJSONデータ(キー・値形式)の値を格納する想定です。
    class MyEntity
    {
    	public int Value1 { get; set; }
    	public string Value2 { get; set; }
    	public int[] Value3 { get; set; }
    	public override string ToString()
    	    => $"{{Value1={Value1}, Value2={Value2}, Value3=[{string.Join(", ", Value3)}]}}";
    }
    

OrderedDictionaryを使用する方法

  • 順番を保持できるDictionaryであるOrderedDictionary(System.Collections.Specialized)を使用できます。
  • このクラスはジェネリックに対応していないため、値の解析処理を自身で実装する必要があります。
  • OrderedDictionaryの列挙子としてDictionaryEntry型が返却されるので、そこからキーと値を取得できます。
  • 値はJsonElement型として返却されるので、そこから数値や文字列を取得できます。詳細はこちらをご覧ください。
  • 次の例では、JsonElementオブジェクトから個別に値を取得してMyEntityクラスに格納します。
    var result = JsonSerializer.Deserialize<OrderedDictionary>(TestJson);
    foreach (DictionaryEntry e in result)
    {
        var key = e.Key;
        var valElement = (JsonElement)e.Value;
        var entity = new MyEntity()
        {
            Value1 = valElement.GetProperty("value1").GetInt32(),
            Value2 = valElement.GetProperty("value2").GetString(),
            Value3 = valElement.GetProperty("value3").EnumerateArray()
                .Select(je => je.GetInt32()).ToArray()
        };
        Console.WriteLine("{0}={1}", key, entity);
    }
    
  • 次の例では、JsonElementオブジェクトから取得したJSONを再度デシリアライズしてMyEntityクラスに値を格納します。
    var result = JsonSerializer.Deserialize<OrderedDictionary>(TestJson);
    var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
    foreach (DictionaryEntry e in result)
    {
        var key = e.Key;
        var valElement = (JsonElement)e.Value;
        var valJson = valElement.GetRawText();
        var entity = JsonSerializer.Deserialize<MyEntity>(valJson, options);
        Console.WriteLine("{0}={1}", key, entity);
    }
    

JsonDocumentを使用する方法

上記の例では、Dictionaryの延長の位置づけでOrderedDictionaryを使う方法を紹介しました。
ジェネリックに対応しておらず、結局JsonElementを使って手動で解析する必要があります。
これだったら、素直にJsonDocumentを使って解析した方がいいかとも思います。

using var document = JsonDocument.Parse(TestJson);
JsonElement root = document.RootElement;

foreach (var jsonProperty in root.EnumerateObject())
{
    var key = jsonProperty.Name;
    var valElement = jsonProperty.Value;
    var entity = new MyEntity()
    {
        Value1 = valElement.GetProperty("value1").GetInt32(),
        Value2 = valElement.GetProperty("value2").GetString(),
        Value3 = valElement.GetProperty("value3").EnumerateArray()
            .Select(ve => ve.GetInt32()).ToArray()
    };
    Console.WriteLine("{0}={1}", key, entity);
}

参考)JsonElementの使い方

  • Jsonデータの解析結果は、(内部的に)JsonElementで再帰的に表現されています。
  • JsonElementは、値またはオブジェクト(入れ子になったデータ)を表現しています。
    • 値の例:「”value1″: 111」というJSONの値「111」
    • オブジェクトの例:「”key1″: { “value1”: 123, “value2”: “test1”,…}」というJSONの値「{ “value1”: 123, “value2”: “test1”,…}」
  • 何を表現しているかはValueKind(JsonValueKind列挙型)で判定できます。
  • 数値、文字列、真偽等の値を表現している場合、その値をGetInt32(), GetString()等で整数型や文字列として取得できます。値が配列形式の場合、EnumerateArray()で列挙子を取得できるので、ループ処理で配列値の取得や変換を行えます。
  • オブジェクトを表現している場合、オブジェクトに含まれるキー・値(「”value1″: 111」等)はプロパティ(JsonProperty型)として保持されています。JsonPropertyのName, Valueがキー・値に対応しています。
  • プロパティの一覧を取得する場合はEnumerateObject()を使用します。プロパティの値を直接取得する場合は、GetProperty(string)を使用します。
  • JsonElementが表現する値やオブジェクトのJSONはGetRawText()で取得できます。






-.NET Core, 1. システムエンジニアリング, 実装技術

関連記事

EAP7のインストールとパッチ適用

仕事でredhat社のEnterprise Application Server7(EAP7)を扱うことが多いので備忘録として記載します。 なお、このサイトでWebアプリを公開したいと考えていますが、 …

Javaでのパスワード付きzipファイルの圧縮/解凍方法(ZipCrypto/AES)

先日、JavaでのZIP暗号化の考察という記事を書きましたが、zip4jのメンテナンスが再開されており、バージョン2系が公開されていましたので、これを使って通常のzip圧縮/解凍、パスワード付きzip …

WindowsにApacheを構築

アプリケーション開発環境として主にWindowsを使っています。 開発時の検証で使用するミドルウェアがApacheを使用することが多いので、ここでは開発環境であるWindowsにApacheを構築する …

マスタデータ生成ツール

開発や結合試験、本番環境等で使用するマスタデータをExcelで管理することがあります。 そのようなExcelファイルからDBに登録するためのインサート文を作成するために、いつもツールに悩むので作成して …

.NET Core(C#): enumで文字列を保持

はじめに サンプルは.NET Core 3.1 + C# 8.0で動作確認しています。 列挙体Enumの基本的な使い方はリファレンスやこちらのサンプルをご覧ください。 完全なソースコードはこちらで公開 …