VB .NET で複雑にネストされたJSONデータを取り扱う

Visual Basic .NETでJSONデータを取り扱うのにJson .NETが便利。JSON文字列データをDeserialize時の格納クラスを円滑に定義するための備忘録である。

※Json .NET:NuGet上パッケージ名は NewtonSoft.Json

課題

Json .NET を用いると、以下コードのようにSourceのJSON文字列をMyClassの形式に格納出来る。

Dim StructuredData = JsonConvert.DeserializeObject(Of MyClass)(source)

しかしこのMyClassをどう定義するかが初心者には難しい。

もっと具体的に言えば、YouTube Data API V3のデータを取り扱おうと思っても、入れ子にされたデータに対してどうクラスを定義すればStructuredDataに格納できるかに悩む。複雑なデータをお手軽快適に取り扱うのが課題だった。

結論

直下のデータ

基本的なデータの取り扱い

配列ではない個別項目にアクセスしたい場合はそれぞれプロパティを定義する。つまり、以下例であればPublic Property kind As String と定義し Debug.Print (StructuredData.kind) という形で使える。

{
__”kind”: “youtube#playlistItemListResponse”,
__”etag”: “XXX_XXXXXXXXXXXXX_X-XXXXXXX”,
__”nextPageToken”: “XXXXXXXXXXXXXX”,

Public Class MyClass
__Public Property kind As String
__Public Property etag As String
__Public Property nextPageToken As String
End Class

同じ階層の全データが同じデータ型のKey-Valueの形になっている場合

以下 pageInfo 項目内、totalResults, resultsPerPage は両方とも String – Long 構成とみなせる。

{
__”kind”: “youtube#playlistItemListResponse”,
__”etag”: “XXX_XXXXXXXXXXXXX_X-XXXXXXX”,
__”pageInfo”: {
____”totalResults”: 11,
____”resultsPerPage”: 10
__}
}

この場合はPublic Property kv As Dictionary(Of DataTypeKey, DataTypeValue) の形で定義すればDebug.Print (kv(“kind”))のように必要項目のみKEY指定して扱える。

Public Class MyClass
__Public Property pageInfo As Dictionary(Of String, Long)
End Class

更に下の階層のデータを持つ項目がある場合・データ型が異なる場合

Public Class MyClass
__Public Property kind As String
__Public Property etag As String
__Public Property pageInfo As Dictionary(Of String, Long)
End Class

個別にプロパティを設けて厳密にアクセスしたい場合は個別に項目を定義する。つまり、Public Property kind As String と定義し Debug.Print (StructuredData.kind) という形で使える。

配列データ

変数名の右に [ 記号でくくってある場合は配列データである。

.NET で規定されている型のデータが配列になっている場合

以下構成であれば Public Property name As List(Of String) で定義すれば良い。 ※YouTubeAPI出力になかったので動作未確認。

__”name”: [
____”AAA”,
____”BBB”
__],

Public Class MyClass
__Public Property name As List(Of String)
End Class

.NET で規定されていない型のデータが配列になっている場合

対象の配列(下の例であればitems, snippet)それぞれをまるまるクラスとして定義し、上述の応用で配列のやり方(Public Property items As List(Of NestedClass), Public Property snippet As NestedNestedClass)で配列項目または個別項目と決めればよい。

{
__”kind”: “youtube#playlistItemListResponse”,
__”etag”: “XXX_XXXXXXXXXXXXX_X-XXXXXXX”,
__”nextPageToken”: “XXXXXXXXXXXXXX”,
__”items”: [
____{
______”kind”: “youtube#playlistItem”,
______”etag”: “XXXXXXXXXXXXXXXXXXXX_XXXXXX”,
______”id”: “XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX”,
______”snippet”: {
________”publishedAt”: “2022-02-20T17:23:00Z”,
________”resourceId”: {
__________”kind”: “youtube#video”,
__________”videoId”: “XXXXXXXXXXX”
________},

Public Class MyClass
__Public Property kind As String
__Public Property etag As String
__Public Property nextPageToken As String
__Public Property items As List(Of NestedClass)
End Class

Public Class NestedClass
__Public Property kind As String
__Public Property etag As String
__Public Property id As String
__Public Property snippet As NestedNestedClass
End Class

Public Class NestedNestedClass
__Public Property publishedAt As Date
__Public Property resourceId As Dictionary(Of String, String)
End Class

調査実施環境:

  • Windows 11
  • Visual Studio .NET 2019
  • .NET Core 3.1
  • Json .NET .NET Standard 2.0 Version 13.0.1.25517

Openldap再起動に失敗した復旧試行錯誤

dockerコンテナ osixia/openldap を使っていたが、コンテナを再起動したら即落ちで正常起動しなくなった不具合調査と対応結果。

状況

docker ps -a で見ても EXITED となる。表示されるログは以下の通り。

[root@hoge ~]# docker logs openldap
*** CONTAINER_LOG_LEVEL = 3 (info)
*** Copy /container/service to /container/run/service
*** Search service in CONTAINER_SERVICE_DIR = /container/run/service :
*** link /container/run/service/:ssl-tools/startup.sh to /container/run/startup/:ssl-tools
*** link /container/run/service/slapd/startup.sh to /container/run/startup/slapd
*** link /container/run/service/slapd/process.sh to /container/run/process/slapd/run
*** Set environment for startup files
*** Environment files will be proccessed in this order :
Caution: previously defined variables will not be overriden.
/container/environment/99-default/default.yaml
/container/environment/99-default/default.startup.yaml
~中略~
TLS: could not use CA certificate file `/container/run/service/slapd/assets/certs/ca.crt': Error while reading file. (-64)
61909a9b main: TLS init def ctx failed: -1
61909a9b slapd stopped.
61909a9b connections_destroy: nothing to destroy.
*** /container/run/process/slapd/run exited with status 1
*** Killing all processes...

結論

結論としては問題は3つあった。

  • 設定の上書きを禁止していた
  • TLSを無効化していなかった ※実際に使っているなら残して良い。使っていないのに無効化してなかった
  • 何故かよくわからないがdockerのパラメータHOSTNAMEの扱いに何か変更があった様子

対策

以下の docker パラメータ修正で立ち上がるようになった。

追加

LDAP_TLS: "false"

削除

HOSTNAME: "example.com"
KEEP_EXISTING_CONFIG: "true"

調査経緯

読めぬなら読ませて見せよう…ということで、 /container/run/service/slapd/assets/certs/ca.crt を配置してみた。

するとca.crt以外にも公開鍵・秘密鍵・DHPARAMも不足している旨のエラーが順に表示されたのでそれらも足した。

でもそもそも内部用でTLSは使っていないから LDAP_TLS : “false”を足した。けれど直らなかった。

よくよく警告文言を見ると定義済み変数は上書きされないと書いてあったから KEEP_EXISTING_CONFIG: “true” を消した。そして docker logs openldap のエラー文言部分が以下に変わった。

61a19a26 slapd starting
61a19a26 daemon: listen(ldap://516808931dce:389, 5) failed errno=98 (Address already in use)
61a19a26 slapd stopped.
*** INFO | 2021-11-27 02:38:30 | /container/run/process/slapd/run exited with status 0
*** INFO | 2021-11-27 02:38:30 | Killing all processes…

このサーバー自体での問題だから、Address already in use はコンテナ内部の問題。

この辺の文言でググっていたところ、HOSTNAMEが怪しいぞという話があった。何か故あってHOSTNAMEのパラメータは消すべきとなった様子だが具体的な理由が知りたいわけではないからよしとする。

docker 起動パラメータから消したら正常表示されるようになった。

エクセルテーブルをデータ入力規則リストに使う

エクセルでプルダウンメニューから選択して入力するにはデータ入力規則でリスト入力設定にすればよい。リストの元の値(Source)にテーブルを指定すれば選択肢数を増減させても即選択肢に反映されるので柔軟に扱える。

設定するには3ステップを行う。

  1. テーブルの作成
  2. テーブル名の設定 ※省略可
  3. プルダウンメニュー化範囲にデータ入力規則を設定 ※ドロップダウンリストとも呼ばれる

1.テーブルの作成

図1.テーブル化

テーブル化するデータを入力してから、範囲を選択(図1のA1:B4)→メニュー『挿入』タブの『テーブル』を選択

図2.テーブルの作成

適宜『先頭行をテーブルの見出しとして使用する』にチェックを入れて、『OK』ボタンを選択

図3.テーブル化完了

2.テーブル名の設定

図4.テーブル名の変更

テーブル選択状態で、メニュー『テーブルデザイン』タブの『テーブル名:』で適宜テーブル名を変更する

※デフォルトの名前がついているのでテーブル名設定は省略出来るが、後で分かりやすいので変更推奨

図5.テーブル名を食べ物に変更した

3.プルダウンメニュー化範囲にデータ入力規則を設定

図6.データ入力規則

プルダウンメニューから選びたい範囲を選択後、メニュー『データ』タブの『データの入力規則』を選択

図7.データの入力規則の設定

『入力値の種類:』は『リスト』に変更し、『元の値:』は『=INDIRECT(“テーブル名[列名]”)』の書き方にして『OK』を選択。

※この例では元の値は=INDIRECT(“食べ物[フルーツ]”)=INDIRECT(“食べ物[やさい]”) となる。テーブルの定義に合うよう適宜変更する

※INDIRECTを省略したりダブルクォーテーション記号を省略したりすると正常に動作しないから注意して入力する

図8.プルダウンメニューから選択可能