JSONデータをDataTableに格納したい
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2021/4/16 10:34:47
> 読み込むJSONは以下の形式です。
Json2 のカンマの位置がおかしいので、構文エラーになりそうです。
> ただしgrouplistの要素数とそれに対応するグループのキー及び要素数は読込むJSONごとに異なります。
たとえば、animal が 3 要素で、flower が 7 要素といった非対称性もありえるのですね。
(実際、Json2 の human は要素数が他より少ない)
> 全てのグループの要素を一行目からセットするにはどのように記述すればよいでしょうか?
やり方は色々あると思いますが、例えばこんな感じにしてみるとか。
Json2 のカンマの位置がおかしいので、構文エラーになりそうです。
> ただしgrouplistの要素数とそれに対応するグループのキー及び要素数は読込むJSONごとに異なります。
たとえば、animal が 3 要素で、flower が 7 要素といった非対称性もありえるのですね。
(実際、Json2 の human は要素数が他より少ない)
> 全てのグループの要素を一行目からセットするにはどのように記述すればよいでしょうか?
やり方は色々あると思いますが、例えばこんな感じにしてみるとか。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim jsonObj = JsonConvert.DeserializeObject(Of JObject)(TextBox1.Text)
Dim jsonDataTable As New DataTable("JsonDataTable")
For Each grp In jsonObj("list")("grouplist")
Dim key = grp.ToString()
Dim tbl As New DataTable()
tbl.Columns.Add("ID", GetType(Long)).AutoIncrement = True
tbl.Columns.Add(key)
tbl.PrimaryKey = New DataColumn() {tbl.Columns(0)}
For Each row In jsonObj("list")(key)
Dim newRow = tbl.NewRow()
newRow.SetField(Of String)(key, row.ToString())
tbl.Rows.Add(newRow)
Next
jsonDataTable.Merge(tbl, False, MissingSchemaAction.AddWithKey)
Next
jsonDataTable.PrimaryKey = Nothing
jsonDataTable.Columns.RemoveAt(0)
jsonDataTable.AcceptChanges()
DataGridView1.DataSource = jsonDataTable
End Sub
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2021/4/16 13:44:02
> やり方は色々あると思いますが、例えばこんな感じにしてみるとか。
別例
別例
Dim jsonList = JsonConvert.DeserializeObject(Of JObject)(TextBox1.Text)("list")
Dim cols = jsonList("grouplist").Select(Function(g) g.ToString())
Dim jsonDataTable As New DataTable("JsonDataTable")
jsonDataTable.Columns.AddRange(cols.Select(Function(c) New DataColumn(c)).ToArray())
For rowIndex = 0 To Integer.MaxValue - 1
Dim newRow = jsonDataTable.NewRow()
For Each col In cols
newRow(col) = jsonList(col).ElementAtOrDefault(rowIndex)
Next
If newRow.ItemArray.All(AddressOf IsDBNull) Then
Exit For
End If
jsonDataTable.Rows.Add(newRow)
Next
jsonDataTable.AcceptChanges()
DataGridView1.DataSource = jsonDataTable
投稿者 まほ  (学生)
投稿日時
2021/4/16 19:38:32
魔界の仮面弁士様
お返事ありがとうございます。
すみません、Jsonはサンプルに手打ちで記述したものでカンマ位置を間違えていました。
はい、fruit等のキー名やキー数、その中の要素名や要素数もJSON個々に違います。
ご提示頂いたプログラムを試させて頂いたところ各行の一行目から正しく要素がセットされ思い通りの結果になりました。本当にありがとうございます。
二つ目の例はコードがすっきりしているのでこちらを使用させて頂きたいと思います。
ただ両方のコードを見てもどのような事をしているのか理解出来なかったので、よろしければ今後の勉強の為にこういった場合の考え方(組み方)のご解説を頂けると大変助かります。
お返事ありがとうございます。
すみません、Jsonはサンプルに手打ちで記述したものでカンマ位置を間違えていました。
はい、fruit等のキー名やキー数、その中の要素名や要素数もJSON個々に違います。
ご提示頂いたプログラムを試させて頂いたところ各行の一行目から正しく要素がセットされ思い通りの結果になりました。本当にありがとうございます。
二つ目の例はコードがすっきりしているのでこちらを使用させて頂きたいと思います。
ただ両方のコードを見てもどのような事をしているのか理解出来なかったので、よろしければ今後の勉強の為にこういった場合の考え方(組み方)のご解説を頂けると大変助かります。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2021/4/16 20:56:30
まずは最初の例から。
> Dim tbl As New DataTable()
grouplist を列挙して、fruit, aminal, flower 用に、それぞれの別の DataTable を作成しています。
このループ処理により、下記の 3 つのテーブルが生成されます。
『ID』列が追加されていますが、これはテーブルの各行を識別するための「主キー」です。
これは、下記のコードで生成されています。
> tbl.Columns.Add("ID", GetType(Long)).AutoIncrement = True
> tbl.PrimaryKey = New DataColumn() {tbl.Columns(0)}
ID 列の値は自分で代入していっても良いのですが、ここでは AutoIncrement を
用いてます。これを使うと、新しい行が追加されるたびに自動採番してくれます。
> jsonDataTable.Merge(tbl, False, MissingSchemaAction.AddWithKey)
作成されたテーブルは、jsonDataTable にマージしていき、下記の表を作ります。
Merge メソッドに指定してある AddWithKey が肝となっており、これによって
主キーが一致するデータを同一行とみなして結合してくれます。
> jsonDataTable.PrimaryKey = Nothing
> jsonDataTable.Columns.RemoveAt(0)
最終結果に ID 列は不要なので、最後に取り除きます。
削除前には、PrimaryKey としての割り当てを解除しておく必要があります。
> Dim tbl As New DataTable()
grouplist を列挙して、fruit, aminal, flower 用に、それぞれの別の DataTable を作成しています。
このループ処理により、下記の 3 つのテーブルが生成されます。
🍎🍌🍓 🐶😺🐰 🌹💐🌼
┏━┯━━━┓┏━┯━━━┓┏━┯━━━┓
┃ID│fruit ┃┃ID│animal┃┃ID│flower┃
┣━┿━━━┫┣━┿━━━┫┣━┿━━━┫
┃ 0│リンゴ┃┃ 0│イヌ ┃┃ 0│バラ ┃
┃ 1│バナナ┃┃ 1│ネコ ┃┃ 1│ユリ ┃
┃ 2│イチゴ┃┃ 2│ウサギ┃┃ 2│キク ┃
┗━┷━━━┛┗━┷━━━┛┗━┷━━━┛
┏━┯━━━┓┏━┯━━━┓┏━┯━━━┓
┃ID│fruit ┃┃ID│animal┃┃ID│flower┃
┣━┿━━━┫┣━┿━━━┫┣━┿━━━┫
┃ 0│リンゴ┃┃ 0│イヌ ┃┃ 0│バラ ┃
┃ 1│バナナ┃┃ 1│ネコ ┃┃ 1│ユリ ┃
┃ 2│イチゴ┃┃ 2│ウサギ┃┃ 2│キク ┃
┗━┷━━━┛┗━┷━━━┛┗━┷━━━┛
『ID』列が追加されていますが、これはテーブルの各行を識別するための「主キー」です。
これは、下記のコードで生成されています。
> tbl.Columns.Add("ID", GetType(Long)).AutoIncrement = True
> tbl.PrimaryKey = New DataColumn() {tbl.Columns(0)}
ID 列の値は自分で代入していっても良いのですが、ここでは AutoIncrement を
用いてます。これを使うと、新しい行が追加されるたびに自動採番してくれます。
> jsonDataTable.Merge(tbl, False, MissingSchemaAction.AddWithKey)
作成されたテーブルは、jsonDataTable にマージしていき、下記の表を作ります。
Merge メソッドに指定してある AddWithKey が肝となっており、これによって
主キーが一致するデータを同一行とみなして結合してくれます。
┏━┯━━━┯━━━┯━━━┓
┃ID│fruit │animal│flower┃
┣━┿━━━┿━━━┿━━━┫
┃ 0│リンゴ│イヌ │バラ ┃
┃ 1│バナナ│ネコ │ユリ ┃
┃ 2│イチゴ│ウサギ│キク ┃
┗━┷━━━┷━━━┷━━━┛
┃ID│fruit │animal│flower┃
┣━┿━━━┿━━━┿━━━┫
┃ 0│リンゴ│イヌ │バラ ┃
┃ 1│バナナ│ネコ │ユリ ┃
┃ 2│イチゴ│ウサギ│キク ┃
┗━┷━━━┷━━━┷━━━┛
> jsonDataTable.PrimaryKey = Nothing
> jsonDataTable.Columns.RemoveAt(0)
最終結果に ID 列は不要なので、最後に取り除きます。
削除前には、PrimaryKey としての割り当てを解除しておく必要があります。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2021/4/16 21:32:14
2 つ目のコードについても解説。
> Dim jsonList = JsonConvert.DeserializeObject(Of JObject)(TextBox1.Text)("list")
ここでは、(Of JObject) の型パラメーター付きのオーバーロードを用いています。
型パラメーターを使わず、Object で受け取ってから、CType や DirectCast を使っても良いです。
> Dim cols = jsonList("grouplist").Select(Function(g) g.ToString())
LINQ を用いて、grouplist 内の値を列挙しています。
上記の実行結果は、下記のようになります。
> jsonDataTable.Columns.AddRange(cols.Select(Function(c) New DataColumn(c)).ToArray())
ここでは LINQ を用いて、3 つの列を一度に追加していますが、
下記のように、ループ処理で 3 回に分けて追加しても構いません。
> For rowIndex = 0 To Integer.MaxValue - 1
To に渡した Integer.MaxValue は 2147483647 と同義ですので、
それを -1 して、rowIndex が 0~2147483646 の間で遷移していきます。
わざわざ上限値を 1 つ減らしているのは、OverflowException の発生を避けるための処置です。
Next に到達する度に、rowIndex が +1 されるためですね。
もしも上限いっぱいの 0~2147483647 の間で遷移させたい場合には、
「For rowIndex As Long = 0L To Integer.MaxValue」
のように、ループカウンターを Long 型にすることができます。
しかし、この後で呼び出す ElementAtOrDefault メソッドは Integer 型を必要とするため、
今回は As Long とはせず、Integer 型のままにしてあります。
> Dim newRow = jsonDataTable.NewRow()
> For Each col In cols
> newRow(col) = jsonList(col).ElementAtOrDefault(rowIndex)
> Next
ElementAtDefault は、該当する位置の値が無い場合に Nothing を返します。
jsonList(col)(rowIndex) だと、インデックスが範囲外エラーになる可能性があるので、ここでは
jsonList(col).ElementAtOrDefault(rowIndex) をかわりに利用しています。
このループにより、下記のような動作が行われます。
> If newRow.ItemArray.All(AddressOf IsDBNull) Then
> Exit For
> End If
fruit、animal、flower の 3 つすべてが Nothing だった場合は、
もうデータが無いことになるので、行追加のループを脱出させます。
JSON データを直接確認するなら、Nothing 判定のため IsNothing を使うところですが、
DataTable に格納したデータは DBNull に変化するため、IsNull で判定しています。
> Dim jsonList = JsonConvert.DeserializeObject(Of JObject)(TextBox1.Text)("list")
ここでは、(Of JObject) の型パラメーター付きのオーバーロードを用いています。
型パラメーターを使わず、Object で受け取ってから、CType や DirectCast を使っても良いです。
Dim jsonList = DirectCast(JsonConvert.DeserializeObject(TextBox1.Text), JObject)("list")
> Dim cols = jsonList("grouplist").Select(Function(g) g.ToString())
LINQ を用いて、grouplist 内の値を列挙しています。
上記の実行結果は、下記のようになります。
Dim cols() As String = {"fruit", "animal", "flower"}
> jsonDataTable.Columns.AddRange(cols.Select(Function(c) New DataColumn(c)).ToArray())
ここでは LINQ を用いて、3 つの列を一度に追加していますが、
下記のように、ループ処理で 3 回に分けて追加しても構いません。
For Each c As String In cols
jsonDataTable.Columns.Add(New DataColumn(c))
Next
> For rowIndex = 0 To Integer.MaxValue - 1
To に渡した Integer.MaxValue は 2147483647 と同義ですので、
それを -1 して、rowIndex が 0~2147483646 の間で遷移していきます。
わざわざ上限値を 1 つ減らしているのは、OverflowException の発生を避けるための処置です。
Next に到達する度に、rowIndex が +1 されるためですね。
もしも上限いっぱいの 0~2147483647 の間で遷移させたい場合には、
「For rowIndex As Long = 0L To Integer.MaxValue」
のように、ループカウンターを Long 型にすることができます。
しかし、この後で呼び出す ElementAtOrDefault メソッドは Integer 型を必要とするため、
今回は As Long とはせず、Integer 型のままにしてあります。
> Dim newRow = jsonDataTable.NewRow()
> For Each col In cols
> newRow(col) = jsonList(col).ElementAtOrDefault(rowIndex)
> Next
ElementAtDefault は、該当する位置の値が無い場合に Nothing を返します。
jsonList(col)(rowIndex) だと、インデックスが範囲外エラーになる可能性があるので、ここでは
jsonList(col).ElementAtOrDefault(rowIndex) をかわりに利用しています。
このループにより、下記のような動作が行われます。
newRow = jsonDataTable.NewRow() '新しい行を作る
newRow("fruit" ) = jsonList("fruit" ).ElementAtOrDefault(0) 'リンゴ
newRow("animal") = jsonList("animal").ElementAtOrDefault(0) 'イヌ
newRow("flower") = jsonList("flower").ElementAtOrDefault(0) 'バラ
ここまで作ってから、DataTable に一行追加。newRow = jsonDataTable.NewRow() '新しい行を作る
newRow("fruit" ) = jsonList("fruit" ).ElementAtOrDefault(1) 'バナナ
newRow("animal") = jsonList("animal").ElementAtOrDefault(1) 'ネコ
newRow("flower") = jsonList("flower").ElementAtOrDefault(1) 'ユリ
ここまで作ってから、DataTable にまた一行追加。> If newRow.ItemArray.All(AddressOf IsDBNull) Then
> Exit For
> End If
fruit、animal、flower の 3 つすべてが Nothing だった場合は、
もうデータが無いことになるので、行追加のループを脱出させます。
JSON データを直接確認するなら、Nothing 判定のため IsNothing を使うところですが、
DataTable に格納したデータは DBNull に変化するため、IsNull で判定しています。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2021/4/16 21:38:23
ちょっと訂正。
>> Dim cols = jsonList("grouplist").Select(Function(g) g.ToString())
> 上記の実行結果は、下記のようになります。
> Dim cols() As String = {"fruit", "animal", "flower"}
上記の説明は正しくありませんね。
「Dim cols() As String」にするためのコードはこうです。
今回は .ToArray() していなかったので、実際は 「Dim cols As IEnumerable(Of String)」になっています。
>> Dim cols = jsonList("grouplist").Select(Function(g) g.ToString())
> 上記の実行結果は、下記のようになります。
> Dim cols() As String = {"fruit", "animal", "flower"}
上記の説明は正しくありませんね。
「Dim cols() As String」にするためのコードはこうです。
Dim cols = jsonList("grouplist").Select(Function(g) g.ToString()).ToArray()
今回は .ToArray() していなかったので、実際は 「Dim cols As IEnumerable(Of String)」になっています。
投稿者 まほ  (学生)
投稿日時
2021/4/17 19:56:58
ご丁寧なご解説をありがとうございます。
なるほど、一つ目の例はそれぞれのグループを別々のテーブルに格納して最後に一つのDataTableに結合するのですね。
二つ目の例は勉強不足で完全に理解するのにまだ時間が掛かりそうですが、柔軟に組めるよう今後も勉強して参りたいと思います。
なるほど、一つ目の例はそれぞれのグループを別々のテーブルに格納して最後に一つのDataTableに結合するのですね。
二つ目の例は勉強不足で完全に理解するのにまだ時間が掛かりそうですが、柔軟に組めるよう今後も勉強して参りたいと思います。
ファイル化されたJSONデータを読込みDataTableに格納しようとしているのですが思ったように格納できません。
読み込むJSONは以下の形式です。
「list」と「grouplist」のキー名は決まっています。
ただしgrouplistの要素数とそれに対応するグループのキー及び要素数は読込むJSONごとに異なります。
-------------------------------------------------------------------------
例:Json1
{
"list": {
"grouplist": ["fruit", "animal", "flower"],
"fruit": ["リンゴ","バナナ","イチゴ"],
"animal": ["イヌ","ネコ","ウサギ"],
"flower": ["バラ","ユリ","キク"]
}
}
-------------------------------------------------------------------------
例:Json2
{
"list": {
"grouplist": ["fruit","animal","flower","human"],
"fruit": ["リンゴ","バナナ","イチゴ","モモ","ブドウ"],
"animal": ["イヌ","ネコ","ウサギ","リス","ウシ"],
"flower": ["バラ","ユリ","キク","チューリップ","ヒマワリ"]
"human": ["タナカ","サトウ","ヤマダ","カトウ"],
}
}
-------------------------------------------------------------------------
このように記述すると二つ目以降のグループ要素が一つ前のグループ要素の次の行から格納されてずれてしまいます。
| fruit | animal | flower |
|リンゴ | | |
|バナナ| | |
|イチゴ | | |
| |イヌ | |
| |ネコ | |
| |ウサギ | |
| | |バラ |
| | |ユリ |
| | |キク |
読み込むJSONによってグループ数が不確定なので、列ごとにループで要素を格納するしか思いつかずこのような形になってしまいます。
全てのグループの要素を一行目からセットするにはどのように記述すればよいでしょうか?
ご教授をお願いします。