WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

JSON 文字列から指定した name の value を取得

by WebSurfer 11. February 2021 18:12

JSON 形式の文字列 {"name":"value"} から name を指定して value を取得する C# のプログラムのサンプルを書きます。

JSON 文字列から指定した name の value を取得

先の記事「Json.NET の JToken をパース」と「System.Text.Json の JsonElement をパース」で、Json.NET (Newtonsoft.Json) と System.Text.Json を使って JSON 文字列をパースするサンプルを書きました。

先の記事のサンプルの使い道はなさそうですが、その応用でいろいろできそうということで、まず JSON 文字列を JToken (Json.NET) または JsonElement (System.Text.Json) にデシリアライズしたオブジェクトから、name を指定して再帰的に value 値を取得するコードを書いてみました。

それが何の役に立つかというと、例えば JSON 文字列構造が不定なので特定のクラスにデシリアライズできないが、必要な情報のある name は事前に分かっているのでそれに該当する value を取得できれば目的は果たせるというようなケースです。

レアケースかもしれませんけど、ASP.NET Forum でそういう話があったので考えてみました。

JToken (Json.NET) および JsonElement (System.Text.Json) の両方の場合のサンプルコードを以下にアップしておきます。

(1) JToken (Json.NET)

Json.NET (Newtonsoft.Json) の DeserializeObject メソッドで JSON 文字列を JToken クラスのオブジェクトにデシリアライズし、その中から name が "ObjectArray" の value 値を FindJTokenByName メソッドで取得しています。

その結果を Visual Studio 2019 のデバッガで見たのが上の画像です。元になる JSON 文字列はこの記事の下の方に書いてありますのでそちらを見てください。

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;

namespace NewtonsoftJson
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:\Users\...\NewtonsoftJson\";
            string file = "TextFile5.txt";
            string jsonText = "";
            using (StreamReader sr = File.OpenText(path + file))
            {
                jsonText = sr.ReadToEnd();
            }

            JToken jtoken = JsonConvert.DeserializeObject<JToken>(jsonText);
            Parse(0, jtoken);
            JToken token = FindJTokenByName(jtoken, "ObjectArray");
        }

        private static void Parse(int padding, JToken jtoken)
        {
            // ・・・略・・・
        }

        private static JToken FindJTokenByName(JToken jtoken, string name)
        {
            if (jtoken is JObject)
            {
                foreach (KeyValuePair<string, JToken> kvp in (JObject)jtoken)
                {
                    if (kvp.Key == name)
                    {
                        return kvp.Value;
                    }
                    else
                    {
                        JToken retVal = FindJTokenByName(kvp.Value, name);
                        if (retVal != null)
                        {
                            return retVal;
                        }
                    }
                }
            }
            else if (jtoken is JArray)
            {
                foreach (JToken jtokenInArray in (JArray)jtoken)
                {
                    JToken retVal = FindJTokenByName(jtokenInArray, name);
                    if (retVal != null)
                    {
                        return retVal;
                    }
                }
            }
            else
            {
                return null;
            }
            return null;
        }
    }
}

(2) JsonElement (System.Text.Json)

System.Text.Json の Deserialize メソッドで JSON 文字列を JsonElement 構造体のオブジェクトにデシリアライズし、その中から name が "ObjectArray" の value 値を FindElementByName メソッドで取得しています。

using System;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.IO;

namespace ConsoleAppJson
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:\Users\surfe...\ConsoleAppJson\";
            string file = "TextFile5.txt";
            string jsonText = "";

            using (StreamReader sr = File.OpenText(path + file))
            {
                jsonText = sr.ReadToEnd();
            }

            JsonElement jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonText);
            Parse(0, jsonElement);
            var jelem = FindElementByName(jsonElement, "ObjectArray");
        }

        private static void Parse(int padding, JsonElement jelem)
        {
            // ・・・略・・・
        }

        // JsonElement は構造体。見つからないと null が返ってくるので
        // 戻り値は JsonElement? として null 許容にした
        private static JsonElement? FindElementByName(JsonElement jelem, string name)
        {
            if (jelem.ValueKind == JsonValueKind.Object)
            {
                foreach (JsonProperty jprop in jelem.EnumerateObject())
                {
                    if (jprop.Name == name)
                    {
                        return jprop.Value;
                    }
                    else
                    {
                        JsonElement? retVal = FindElementByName(jprop.Value, name);
                        if (retVal != null)
                        {
                            return retVal;
                        }
                    }
                }
            }
            else if (jelem.ValueKind == JsonValueKind.Array)
            {
                foreach (JsonElement jelemInArray in jelem.EnumerateArray())
                {
                    JsonElement? retVal = FindElementByName(jelemInArray, name);
                    if (retVal != null)
                    {
                        return retVal;
                    }
                }
            }
            else
            {
                return null;
            }
            return null;
        }
    }
}

上のコードで使った TextFile5.txt の JSON 文字列は以下の通りです。

{
  "Title": "This is my title",
  "Response": {
    "Version": 1,
    "StatusCode": "OK",
    "Result": {
      "Profile": {
        "UserName": "SampleUser",  
        "IsMobileNumberVerified": false,
        "MobilePhoneNumber": null
      },
      "ObjectArray" : [
          {"Code": 2000,"Description": "Fail"},
          {"Code": 3000,"Description": "Success"}
      ],
      "lstEnrollment": "2021-2-5"
    },
    "Message": {
      "Code": 1000,      
      "Description": "OK"
    }
  },
  "StringArray" : ["abc", "def", "ghi"]
}

Tags: , ,

.NET Framework

System.Text.Json の JsonElement をパース

by WebSurfer 8. February 2021 13:03

.NET Core 3.0 以降で利用できる JsonSerializer クラスDeserialize<TValue>(String, JsonSerializerOptions) メソッドを使って JSON 文字列を JsonElement 構造体のオブジェクトにデシリアライズし、それをパースして下の画像のように表示する方法を書きます。

JsonElement をパース

基本的には先の記事「Json.NET の JToken をパース」と同じことを行っています。違うのは先の記事では JToken クラスにデシリアライズしていたものが JsonElement 構造体になったところです。

それに伴い、先の記事では JToken が JObject, JArray, JValue のどれにキャストできるかによってオブジェクトなのか配列なのかプリミティブ値なのかを調べてそれぞれ処理を分けていたところが、この記事の JsonElement の場合は JsonElement.ValueKind プロパティを使って JsonValueKind 列挙型のどれに該当するかを調べて処理を分けることになります。

また、foreach ループでの反復処理を、Object が対象の場合は JsonElement.EnumerateObject メソッドを使って、Array が対象の場合は JsonElement.EnumerateArray メソッドを使って列挙子を取得して行うところも先の記事とは異なります。

そのあたりは以下のサンプルコードのコメントに書きましたので見てください。

まず、Deserialize<JsonElement>(jsonText) で JSON 文字列を JsonElement 型のオブジェクトにデシリアライズします。その後 Parse メソッドで JsonElement オブジェクトの中身がプリミティブ型なのか Object なのか Array なのかを再帰的に解析し結果をコンソールに出力したのが上の画像です。

using System;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.IO;

namespace ConsoleAppJson
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:\Users\...\ConsoleAppJson\";
            string file = "TextFile5.txt";
            string jsonText = "";

            using (StreamReader sr = File.OpenText(path + file))
            {
                jsonText = sr.ReadToEnd();
            }

            var jelem = JsonSerializer.Deserialize<JsonElement>(jsonText);
            Parse(0, jelem);
        }

        private static void Parse(int padding, JsonElement jelem)
        {
            // JsonElement.ValueKind プロパティを使って JsonValueKind 列挙型
            // のどれに該当するかを調べて処理を分ける
            if (jelem.ValueKind == JsonValueKind.False ||
                jelem.ValueKind == JsonValueKind.Null ||
                jelem.ValueKind == JsonValueKind.Number ||
                jelem.ValueKind == JsonValueKind.String ||
                jelem.ValueKind == JsonValueKind.True)
            {
                string str = $"value = {jelem}";
                Console.WriteLine(str.PadLeft(str.Length + padding));
            }
            else if (jelem.ValueKind == JsonValueKind.Object)
            {
                // Object の場合は EnumerateObject() で列挙子を取得し、以下の
                // ように foreach ループで JsonProperty を取得できる。
                // JsonProperty というのは単一の {"name":"value"} オブジェクト
                // と思えばよさそう
                foreach (JsonProperty jprop in jelem.EnumerateObject())
                {
                    if (jprop.Value.ValueKind == JsonValueKind.False ||
                        jprop.Value.ValueKind == JsonValueKind.Null ||
                        jprop.Value.ValueKind == JsonValueKind.Number ||
                        jprop.Value.ValueKind == JsonValueKind.String ||
                        jprop.Value.ValueKind == JsonValueKind.True)
                    {
                        string str = $"name = {jprop.Name}, value = {jprop.Value}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                    }
                    else if (jprop.Value.ValueKind == JsonValueKind.Object)
                    {
                        string str = $"name = {jprop.Name}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                        Parse(padding + 2, jprop.Value);
                    }
                    else if (jprop.Value.ValueKind == JsonValueKind.Array)
                    {
                        string str = $"name = {jprop.Name}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                        int index = 1;
                        // Array の場合は EnumerateArray() で列挙子を取得し、以下のよう
                        // に foreach ループで配列内の各要素 (JsonElement) を取得できる
                        foreach (JsonElement jelemInArray in jprop.Value.EnumerateArray())
                        {
                            string idx = $"array index {index}";
                            Console.WriteLine(idx.PadLeft(idx.Length + padding + 1));
                            Parse(padding + 2, jelemInArray);
                            index++;
                        }
                    }
                    else
                    {
                        // JsonValueKind.Undefined 以外はここに来ない(はず)
                        Console.WriteLine(jelem.ToString());
                    }
                }
            }
            else if (jelem.ValueKind == JsonValueKind.Array)
            {
                int index = 1;
                // Array の場合 EnumerateArray() で列挙子を取得し、以下のよう
                // に foreach ループで配列内の各要素 (JsonElement) を取得できる
                foreach (JsonElement jelemInArray in jelem.EnumerateArray())
                {
                    string idx = $"array index {index}";
                    Console.WriteLine(idx.PadLeft(idx.Length + padding + 1));
                    Parse(padding + 2, jelemInArray);
                    index++;
                }
            }
            else
            {
                // JsonValueKind.Undefined 以外はここに来ない(はず)
                Console.WriteLine(jelem.ToString());
            }
        }
    }
}

上の画像を出力するのに使った JSON 文字列は以下の通りです。先の記事と同じです。

{
  "Title": "This is my title",
  "Response": {
    "Version": 1,
    "StatusCode": "OK",
    "Result": {
      "Profile": {
        "UserName": "SampleUser",  
        "IsMobileNumberVerified": false,
        "MobilePhoneNumber": null
      },
      "ObjectArray" : [
          {"Code": 2000,"Description": "Fail"},
          {"Code": 3000,"Description": "Success"}
      ],
      "lstEnrollment": "2021-2-5"
    },
    "Message": {
      "Code": 1000,      
      "Description": "OK"
    }
  },
  "StringArray" : ["abc", "def", "ghi"]
}

Tags: , , , ,

CORE

Json.NET の JToken をパース

by WebSurfer 7. February 2021 15:04

Json.NET (Newtonsoft.Json) の JsonConvert.DeserializeObject メソッドを使って JSON 文字列を JToken 型のオブジェクトにデシリアライズし、それを以下のようにパースして表示する方法を書きます。

JToken をパース

使用するのは Newtonsoft.Json.Linq 名前空間にある JToken, JObject, JArray, JValue クラスですので、まずそれらの簡単な説明を以下に書きます。詳しい説明は Newtonsoft.Json.Linq Namespace を見てください。

  • JToken: 下のクラスの継承元の抽象クラス
  • JObject: {"name1":"value1","name2":"value2"} のようなオブジェクト。IDictionary<string, JToken> を継承
  • JArray: [{"name":"value1"},{"name":"value2"}, ...] のような配列。IList<JToken> を継承
  • JValue: string, number, boolean, null などのプリミティブな JSON 値

一般的な方法は DeserializeObject<T>(String) メソッドの T に先の記事「JSON 文字列から C# のクラス定義生成」のような方法で求めたクラス定義を設定して、そのクラスのオブジェクトにデシリアライズするのだと思います。

なので、この記事に書くような JToken クラスにデシリアライズしてそれをパースする必要はなさそうですが、ひょっとしたら JSON 文字列を受け取るまでその内容が不明でクラス定義が生成できないとかいうケースがあるかもしれないということで考えてみました。

そのコードを以下にアップしておきます。

DeserializeObject<JToken>(jsonText) で JSON 文字列を JToken 型のオブジェクトにデシリアライズします。その後 Parse メソッドで JToken オブジェクトを再帰的に解析し結果をコンソールに出力したのが上の画像です。

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;

namespace NewtonsoftJson
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:\Users\...\NewtonsoftJson\";
            string file = "TextFile5.txt";

            string jsonText = "";

            file = "TextFile5.txt";
            using (StreamReader sr = File.OpenText(path + file))
            {
                jsonText = sr.ReadToEnd();
            }

            JToken jtoken = JsonConvert.DeserializeObject<JToken>(jsonText);
            Parse(0, jtoken);

        }

        private static void Parse(int padding, JToken jtoken)
        {
            if (jtoken is JValue)
            {
                // プリミティブ型の場合 Value プロパティで値を取得
                JValue jvalue = (JValue)jtoken;
                string str = $"value = {jvalue.Value}";
                Console.WriteLine(str.PadLeft(str.Length + padding));
            }
            else if (jtoken is JObject)
            {
                // JObject は IDictionary<string, JToken> を継承しているので、
                // 以下のように foreach ループで {"name":"value"} を取得できる
                foreach (KeyValuePair<string, JToken> kvp in (JObject)jtoken)
                {
                    if (kvp.Value is JValue)
                    {
                        JValue jvalue = (JValue)kvp.Value;
                        string str = $"name = {kvp.Key}, value = {jvalue.Value}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                    }
                    else if (kvp.Value is JObject)
                    {
                        string str = $"name = {kvp.Key}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                        Parse(padding + 2, kvp.Value);
                    }
                    else if (kvp.Value is JArray)
                    {
                        string str = $"name = {kvp.Key}";
                        Console.WriteLine(str.PadLeft(str.Length + padding));
                        JArray jarray = (JArray)kvp.Value;
                        int index = 1;

                        // JArray は IList<JToken> を継承しているので、以下の
                        // ように foreach で配列の各要素の JToken を取得できる
                        foreach (JToken token in jarray)
                        {
                            string idx = $"array index {index}";
                            Console.WriteLine(idx.PadLeft(idx.Length + padding + 1));
                            Parse(padding + 2, token);
                            index++;
                        }
                    }
                }
            }
            else if (jtoken is JArray)
            {
                JArray jarray = (JArray)jtoken;
                int index = 1;
                foreach (JToken token in jarray)
                {
                    string str = $"array index {index}";
                    Console.WriteLine(str.PadLeft(str.Length + padding + 1));
                    Parse(padding + 2, token);
                    index++;
                }
            }
            else
            {
                // ここには来ないはずだが念のため
                Console.WriteLine(jtoken.ToString());
            }
        }
    }
}

ちなみに、上のコードで使った JSON 文字列は以下の通りです。他にもいろいろ試してみましたが、JSON 文字列としてはかなり無理目でクラス定義が生成できない場合も上のコードでパースは可能でした。ダメなケースもあるかもしれませんが・・・

{
  "Title": "This is my title",
  "Response": {
    "Version": 1,
    "StatusCode": "OK",
    "Result": {
      "Profile": {
        "UserName": "SampleUser",  
        "IsMobileNumberVerified": false,
        "MobilePhoneNumber": null
      },
      "ObjectArray" : [
          {"Code": 2000,"Description": "Fail"},
          {"Code": 3000,"Description": "Success"}
      ],
      "lstEnrollment": "2021-2-5"
    },
    "Message": {
      "Code": 1000,      
      "Description": "OK"
    }
  },
  "StringArray" : ["abc", "def", "ghi"]
}

Tags: , , , ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  March 2021  >>
MoTuWeThFrSaSu
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar