雑記
 

VB/C#でJSONPathを使う

2020/6/21

この記事が対象とする製品・バージョン

VS2019 Visual Studio 2019 ◎ 対象です。
VS2017 Visual Studio 2017 ◎ 対象です。
VS2015 Visual Studio 2015 ◎ 対象です。
VS2013 Visual Studio 2013 ◎ 対象です。
VS2012 Visual Studio 2012 ◎ 対象です。
VS2010 Visual Studio 2010 ◎ 対象です。
VS2008 Visual Studio 2008 ◎ 対象です。
VS2005 Visual Studio 2005 × 対象外です。
VS.NET 2003 Visual Studio 2003 × 対象外です。
VS.NET 2002 Visual Studio (2002) × 対象外です。

目次

1.概要

JSONPathは文字列で記述する式で、これを利用するとJSONから値を取り出すことができます。

たとえば、aurhotが直木孝次郎であるすべての要素をとりだすということが簡単にできます。

VB/C#でJSONPathを使う場合、JSON.NET(Newtonsoft JSON)を使用するのが一般的です。

この記事ではJSON.NETを使ってJSONPathを使用する方法とJSONPathの構文・実例を紹介します。

参考

JSONの仕様

VB/C#でJSONの読み込み(JSON.NET編)

 

2.準備

JSON.NET(Newtonsoft JSON)は、プロジェクトの種類によってははじめから使えるようになっていますが、WindowsフォームアプリケーションなどではNuGetからインストールする必要があります。

NuGetからインストールする場合 Newtonsoft.Json  で検索します。かなりメジャーなパッケージなので検索しなくてもはじめから一番上に表示されているかもしれません。

 

3.JSONPathでのクエリの実行

プログラムでJSONパスのクエリを実行して単一の値を取得するにはJToken.SelectTokenメソッドを使用します。複数の値を取得するにはJToken.SelectTokensメソッドを使用します。

戻り値はJTokenまたはJTokenのコレクションです。取得した値が単純な文字列や数値であれば、JTokenをToStringするなどして直接値を取り出せます。

取得した値がオブジェクトの場合は、戻り値に対してさらにSelectToken(s)してJSONPathでクエリをかけることもできます。

SelectTokensで取得した場合は複数の値が該当するのでFor Each~Nextでループなどしながら取り出します。

 

使用例は下記の通りです。

この例ではbooks.jsonという外部のファイルからJSONを読み込みJSONPathで値を取り出します。 $.info.category の部分がJSONPathです。結果は変数 Items から取り出せます。

VB

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応 VB2017対応 VB2019対応

Dim appPath As String = AppDomain.CurrentDomain.BaseDirectory.TrimEnd("\"c)
Dim jsonText As String = System.IO.File.ReadAllText(appPath & "\books.json")

Dim root As JToken = DirectCast(Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText), JToken)
Dim items = root.SelectTokens("$.info.category")

For Each item In items
    Debug.WriteLine(item.ToString)
Next

C#

string appPath = AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\');
string jsonText = System.IO.File.ReadAllText(appPath + @"\books.json");

JToken root = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText) as JToken;
var items = root.SelectTokens(@"$.info.category");

foreach(var item in items)
{
    System.Diagnostics.Debug.WriteLine(item.ToString());
}

→ Debug.WriteLineが表示される場所

 

4.JSONPathの構文

JSONPathでは次の記号が使用できます。

要素 説明
$ ルートオブジェクトまたは配列を表します。
.xxx 親オブジェクトからxxxプロパティを選択します。
['xxx'] 親オブジェクトからxxxプロパティを選択します。プロパティ名にスペースなど特殊な文字を含む場合にはこの構文を使用します。
[n] 配列のn番目の要素を選択します。先頭の要素は 0 です。
[index1, index2, ...] 配列の複数の要素を選択します。
..xxx すべての子孫のxxxプロパティを選択します。
* すべての要素を選択します。たとえば、address.* は addressオブジェクトのすべてのプロパティを表します。book[*]は配列bookのすべての要素を表します。
[start:end] 配列のstart番目からend番目までのすべての項目を選択します。ただし、end番目の項目自体は含みません。
[start:] 配列のstart番目以降のすべての項目を選択します。
[:n] 配列の先頭からn個の項目を選択します。
[-n:] 配列の末尾からさかのぼってn個の項目を選択します。
[?(filter)] フィルターにマッチするすべての要素を選択します。フィルターで使用できる演算子等については別表で後述します。
[(expression)] 式に一致するすべての項目を選択します。

 

フィルター

要素 説明
@ フィルターの中で現在のノードを表します。
@.xxx xxxプロパティを持っている要素にマッチします。例 [?(@.price)]
=== 型と値が等しい。例 [?(@.price===450)]
== 値が等しい。例 [?(@.color=='red')]
!= 等しくない。
> より大きい
>= 以上
< より小さい
<= 以下
=~ JavaScriptの正規表現に一致する。例 [?(@.description =~ /cat.*/i)] この例は catからはじまるdescriptionにマッチします。
! フィルターの成否を反転させます。例 [?(!@.price)] は priceプロパティを持っていない要素にマッチします。
※JSON.NETでは使用できないようです。
&& 条件をANDで結合します。例 [?(@.category=='fiction' && @.price < 10)]
|| 条件をORで結合します。例 [?(@.category=='fiction' || @.price < 10)]

参考

https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html

https://goessner.net/articles/JsonPath/

 

5.JSONPathの実例

JSONのサンプル

以下では下記JSONを前提にさまざまなJSONPathと結果を紹介します。

実際に試すには上記で紹介しているVB/C#のサンプルのJSONPath部分を置き換えてみてください。

下記サイトはブラウザー上でJSONPathを手軽に試せるので便利です。

https://jsonpath.com/

 

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

 

単一のプロパティの値(結果は文字列)

$.info.category

ルートから info, categoru でたどった値を返します。

取得される値は "日本史" です。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

単一のプロパティの値(結果はオブジェクト)

$.books

ルートから books でたどった値を返します。

booksの値はオブジェクトなので戻り地は JObjectになります。

ToStringメソッドで文字列として黄色い部分を取得できます。また、戻り値のJObjectに対してさらにJSONPathでクエリーをかけることもできます。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

すべての子孫要素のtitleの値

$.books..title

ルートから books でたどった先にある全子孫の title を取得します。

『「関ヶ原」の決算書』、『古代史の人びと』、『日本の歴史』、『古代国家の成立』、『奈良の都』の5つが取得できます。

このJSONの場合 $..title と書いても同じ結果です。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

直接のすべての子要素のtitleの値

$.books[*].title

ルートから books でたどった先のオブジェクトの title を取得します。

『「関ヶ原」の決算書』と『古代史の人びと』の2つが取得できます。

『日本の歴史』や『古代国家の成立』はseries要素の下にあるので取得されません。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

配列の先頭の要素

$.books[0]

ルート直下の books にある1つ目の要素を取得します。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

配列の2番目の要素

$.books[1]

ルート直下の books にある2つ目の要素を取得します。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

配列の3番目の要素

$.books[2]

ルート直下の books にある3つ目の要素を取得します。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

配列の最後の要素

$.books[-1:]

ルート直下の books にある最後の要素を取得します。

この例では要素が3つしかないので、結果は$.books[2]と同じです。

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

 

配列の1つ目の要素と2つ目の要素

$.books[0:2]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

配列の2つ目の要素と3つ目の要素

$.books[1:2]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

priceプロパティをもつすべてのオブジェクト

$..*[?(@.price)]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

booksプロパティ以下でpriceプロパティをもつすべてのオブジェクト

$..books[?(@.price)]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

authorが直木孝次郎であるすべての要素

$..[?(@.author==='直木孝次郎')]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

priceが 500以下のすべての要素

$..[?(@.price <= 500)]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

authorが直木孝次郎 または titleが奈良の都 であるすべての要素

$..[?(@.author==='直木孝次郎' || @.title==='奈良の都')]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "boos": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

authorが直木孝次郎ではないすべての要素

$..[?(@.author!='直木孝次郎')]

{
  "info": {
    "category": "日本史",
    "lastupdate": "2021/6/27"
  },
  "books": [
    {
      "title": "「関ケ原」の決算書",
      "author": "山本博文",
      "price": 880
    },
    {
      "title": "古代史の人びと",
      "author": "直木孝次郎"
    },
    {
      "series": {
        "title": "日本の歴史",
        "books": [
          {
            "title": "古代国家の成立",
            "author": "直木孝次郎",
            "price": 450
          },
          {
            "title": "奈良の都",
            "author": "青木和夫"
          }
        ]
      }
    }
  ]
}

 

6.参考

Querying JSON with JSON Path

https://www.newtonsoft.com/json/help/html/QueryJsonSelectToken.htm

JSONPath Syntax

https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html

JSONPath XPath for JSON

https://goessner.net/articles/JsonPath/

Jayway JsonPath

https://github.com/json-path/JsonPath

JSONPath Online Evaluator

https://jsonpath.com/