雑記 |
Visual Basic 中学校 > 雑記 >
2020/4/4
この記事が対象とする製品・バージョン
Visual Studio 2019 | ◎ | 対象です。 | |
Visual Studio 2017 | ◎ | 対象です。 | |
Visual Studio 2015 | ◎ | 対象です。 | |
Visual Studio 2013 | × | 対象外です。 | |
Visual Studio 2012 | × | 対象外です。 | |
Visual Studio 2010 | × | 対象外です。 | |
Visual Studio 2008 | × | 対象外です。 | |
Visual Studio 2005 | × | 対象外です。 | |
Visual Studio 2003 | × | 対象外です。 | |
Visual Studio (2002) | × | 対象外です。 |
目次
この記事では System.Text.Json を扱います。JSON.NETについては下記の記事で説明しています。
VB/C#でJSONを読み書きするには、JSON.NET(Newtonsoft JSON)またはSystem.Text.Jsonを使用するのが一般的です。
JSON.NETは多機能で使われる頻度が高く2020年3月現在ではデファクトスタンダードです。System.Text.Jsonは新しくMicrosoftが開発したライブラリで機能は少なめですがパフォーマンスが優れており、Webアプリケーションのスループットを向上させることができるようです。Webアプリケーションのように同時に多数の処理を動かすわけではない場合は、どちらを使っても問題なく高速です。
なお、 JSONの仕様については下記の記事で説明しています。
System.Text.Jsonは、.NET Core 2.0以上 または .NET Framework 4.6.1 以上で使用できます。それより古い環境では使用できません。その場合はJSON.NETを使用してください。
System.Text.Jsonは、プロジェクトの種類によってははじめから使えるようになっていますが、そうでない場合NuGetからインストールする必要があります。
NuGetからインストールする場合 System.Text.Json で検索します。作成者は Microsoft です。
この記事で紹介する例はプログラムの冒頭にVBの場合、 Imports System.Text.Json が、C#の場合、 using System.Text.Json; が記述されていることが前提です。
System.Test.JsonではJSONの要素はすべて JsonElement クラスで表され、ValueKindプロパティでそれがオブジェクトなのか配列なのか数値なのか文字列なのかなどの区別を表します。
JSONでよく登場する 名前 : 値 のペア(たとえば、下記の例では "Width": 800 など)はJsonPropertyクラスで表現されます。JsonPropertyの名前(例 Width)はJSONの仕様上必ず文字列です。値(例 800)はJsonElementです。
下記のJSONを例に考えて見ます。
この例にはJsonElementがたくさん登場しますが、図ではいくつか抜粋して四角で囲ってみました。カッコ内はそのJsonElementのValueKindプロパティの値を示しています。
{ } で囲まれたオブジェクトは3つあり、これらを表すJsonElementはValueKindが JsonValueKind.Object になります。
それぞれのオブジェクト内に名前:値のぺアがあります。名前に着目して拾ってみると、"Image", "Width", "Height", "Title", "Thumbnail", "Url", "Height", "Width", "Animated", "IDs" の10個のペアがあることがわかります。これらの10個の値もそれぞれがJsonElementで表現されます。
名前の方は親オブジェクトを使って列挙させることができます。その例は後で紹介します。
値にはいろいろな型があるので理解を深めるために、名前とValueKindプロパティを一覧にして確認してみましょう。
名前 | 値を表すJsonElementのValueKindプロパティ |
Image | JsonValueKind.Object |
Width | JsonValueKind.Number |
Height | JsonValueKind.Number |
Title | JsonValueKind.String |
Thumbnail | JsonValueKind.Object |
Url | JsonValueKind.String |
Height | JsonValueKind.Number |
Width | JsonValueKind.Number |
Animated | JsonValueKind.False |
IDs | JsonValueKind.Array |
最後の、IDsの値は [ ] で囲まれており、配列です。ValueKindプロパティは JsonValueKind.Array になります。
true, falseは論理型という枠ではなく、それぞれが JsonValueKind.True と JsonValueKind.False になります。このほかにこのJSONの例には登場しませんが、JsonValueKind.Null もあります。
System.Text.JsonでJSONを読み込んで処理をするには大きく分けて3つのアプローチがあります。
このJSONを例に簡単にどのようなアプローチかを紹介します。
JSONの定義があらかじめわかっている場合はVB/C#で同じ構造のクラスを作成しておくと、System.Text.Jsonの機能でそのクラスに値を割り当ててくれます。クラスを定義するのは少し面倒かもしれませんが、読み込んだ後の処理では完全にVB/C#の世界になるのでJSONを意識することがありません。つまり、上述で説明したようなJsonElementやValueKindなどのことを知らなくてもよいということです。(とはいえ、いろいろ作っているとどうしても簡単にマッピングしただけでは済まない事態も発生するので、やはり、System.Text.Jsonがどういう考え方なのか上述の説明は理解しておいたほうが良いです。)
まず、上述のJSONをVB/C#のクラスで表現して次のような構造を作成します。プロパティ名は完全にJSONと一致させるのが楽ですが、クラス名は自由です。
Visual Studioの編集メニューから[形式を選択して貼り付け] - [JSONをクラスとして貼り付ける]を選択すると、JSONをVB/C#のクラスに変換して貼り付けてくれるのでこの手のクラスを定義する際に便利です。ただし、思ったように変換してくれない部分を手で修正する必要がある場合もよくあります。
VB
Public
Class ProductInfo Public Property Image As ProductImage End Class Public Class ProductImage Public Property Width As Integer Public Property Height As Integer Public Property Title As String Public Property Thumbnail As ProductImageThumbnail Public Property Animated As Boolean Public Property IDs As Integer() End Class Public Class ProductImageThumbnail Public Property Url As String Public Property Height As Integer Public Property Width As Integer End Class |
C#
public
class ProductInfo { public ProductImage Image { get; set; } } public class ProductImage { public int Width { get; set; } public int Height { get; set; } public string Title { get; set; } public ProductImageThumbnail Thumbnail { get; set; } public bool Animated { get; set; } public int[] IDs { get; set; } } public class ProductImageThumbnail { public string Url { get; set; } public int Height { get; set; } public int Width { get; set; } } |
後は簡単で、次のようにして、このクラスとJSONをマッピングできます。
マッピングするにはJsonSerializerクラスのDesrializeメソッドを使用し、型パラメーターにマッピング先の型を指定します。
VB
Private
Function LoadJsonText() As String Dim json As New System.Text.StringBuilder json.AppendLine("{") json.AppendLine(" ""Image"": ") json.AppendLine(" {") json.AppendLine(" ""Width"": 800,") json.AppendLine(" ""Height"": 600,") json.AppendLine(" ""Title"": ""View from 15th Floor"",") json.AppendLine(" ""Thumbnail"": ") json.AppendLine(" {") json.AppendLine(" ""Url"": ""http://www.example.com/image/481989943"",") json.AppendLine(" ""Height"": 125,") json.AppendLine(" ""Width"": 100") json.AppendLine(" },") json.AppendLine(" ""Animated"" : false,") json.AppendLine(" ""IDs"": [116, 943, 234, 38793]") json.AppendLine(" }") json.AppendLine("}") Return json.ToString End Function Private Sub TestJson() Dim jsonText As String = Me.LoadJsonText Dim root As ProductInfo = JsonSerializer.Deserialize(Of ProductInfo)(jsonText) End Sub |
C#
private
string LoadJsonText() { StringBuilder json = new StringBuilder(); json.AppendLine("{"); json.AppendLine(" \"Image\": "); json.AppendLine(" {"); json.AppendLine(" \"Width\": 800,"); json.AppendLine(" \"Height\": 600,"); json.AppendLine(" \"Title\": \"View from 15th Floor\","); json.AppendLine(" \"Thumbnail\": "); json.AppendLine(" {"); json.AppendLine(" \"Url\": \"http://www.example.com/image/481989943\","); json.AppendLine(" \"Height\": 125,"); json.AppendLine(" \"Width\": 100"); json.AppendLine(" },"); json.AppendLine(" \"Animated\" : false,"); json.AppendLine(" \"IDs\": [116, 943, 234, 38793]"); json.AppendLine(" }"); json.AppendLine("}"); return json.ToString(); } private void TestJson() { string jsonText = this.LoadJsonText(); ProductInfo root = JsonSerializer.Deserialize<ProductInfo>(jsonText); } |
この例では、マッピングした後、何もしていませんが、root変数の内容を見ると、ちゃんと値が入っているのわかります。
ここまでできれば後はプログラムで自由に利用できますね。
JSONの定義がわかっているならば、クラスを定義しなくても値を読み取ることが可能です。
たとえば、次のようにしてUrlプロパティの値を読み込むことができます。
例にしているJSONの構造では、 UrlはImageが表すオブジェクト内で、Thumbailが表すオブジェクト内の Urlが表すオブジェクトの値である文字列であることがわかっているので、このプログラムではそれを取り出しています。
オブジェクトのプロパティの値はこの例のようにJsonElementクラスのGetPropertyメソッドを使用します。GetPropertyメソッドの戻り値はまたJsonElementクラスです。ここから値を取り出すにはGetString, GetBooleanなどのメソッドを使用します。
このプログラム中に登場する LoadJsonTextメソッドは上述のプログラム例に登場するものと同じなので割愛しています。
VB
Private
Sub
TestJson() Dim jsonText As String = Me.LoadJsonText Using document As JsonDocument = JsonDocument.Parse(jsonText) Dim root As JsonElement = document.RootElement Dim url As String = root.GetProperty("Image").GetProperty("Thumbnail").GetProperty("Url").GetString End Using End Sub |
C#
private
void TestJson() { string jsonText = this.LoadJsonText(); using (JsonDocument document = JsonDocument.Parse(jsonText)) { JsonElement root = document.RootElement; string url = root.GetProperty("Image").GetProperty("Thumbnail").GetProperty("Url").GetString(); } } |
ところで、JSON.NETの場合、C#ではdynamicを使用して、自前のプロパティであるかのように(たとえば、root.Image.Thumbnail.Urlように)Jsonの要素にアクセスすすることができますが、System.Text.Jsonにはそのような機能はなく、この例のようなアクセス方法になります。
また、JsonPath(JSONパス)を使用した要素へのアクセスもできません。
JSONパスによるクエリ機能を追加する提案がされていますが、どうなるか2020年3月28日現在は未決です。
https://github.com/dotnet/runtime/issues/31068
JSONの定義があらかじめわかっていない場合、For Each(foreach)の列挙や、条件分岐、再帰などを使って構造を解析したり、値を検索、取得していくことになります。
このときに登場するのが前に説明したJsonElementクラスやValueKindプロパティです。
このプログラムでは読み込んでJSONの要素がオブジェクトなのか配列なのかその他なのかで分岐、オブジェクトの場合はプロパティの列挙、配列の場合は値の列挙、値の場合は値を表示するという処理を行っています。
上述のJSONでのみテストした簡易的な実装であり、対応できないケースもあるのではないかと思いますが、構造がわからないJSONに対するアプローチを示すのには十分だと思います。
VB
Private
Sub TestJson() Dim jsonText As String = Me.LoadJsonText Using document As JsonDocument = JsonDocument.Parse(jsonText) Dim root As JsonElement = document.RootElement Parse(root) End Using End Sub Private Sub Parse(source As JsonElement) If source.ValueKind = JsonValueKind.Object Then Debug.WriteLine("(オブジェクト)") ParseObject(source) ElseIf source.ValueKind = JsonValueKind.Array Then Debug.WriteLine("(配列)") ParseArray(source) Else Debug.WriteLine(source) End If End Sub Private Sub ParseObject(source As JsonElement) For Each prop As JsonProperty In source.EnumerateObject Debug.Write($"{prop.Name} : ") Parse(prop.Value) Next End Sub Private Sub ParseArray(source As JsonElement) For Each value As JsonElement In source.EnumerateArray Parse(value) Next End Sub |
C#
private
void TestJson() { string jsonText = this.LoadJsonText(); using (JsonDocument document = JsonDocument.Parse(jsonText)) { JsonElement root = document.RootElement; Parse(root); } } private void Parse(JsonElement source) { if (source.ValueKind == JsonValueKind.Object) { System.Diagnostics.Debug.WriteLine("(オブジェクト)"); ParseObject(source); } else if (source.ValueKind == JsonValueKind.Array) { System.Diagnostics.Debug.WriteLine("(配列)"); ParseArray(source); } else { System.Diagnostics.Debug.WriteLine(source); } } private void ParseObject(JsonElement source) { foreach (var prop in source.EnumerateObject()) { System.Diagnostics.Debug.Write($"{prop.Name} : "); Parse(prop.Value); } } private void ParseArray(JsonElement source) { foreach (var value in source.EnumerateArray()) { Parse(value); } } |
このプログラムを実行すると出力ウィンドウには次のように出力されます。
(オブジェクト) Image : (オブジェクト) Width : 800 Height : 600 Title : View from 15th Floor Thumbnail : (オブジェクト) Url : http://www.example.com/image/481989943 Height : 125 Width : 100 Animated : False IDs : (配列) 116 943 234 38793 |
JsonElementやJsonProperty、ValueKindなどの理解を深める練習用のサンプルを紹介しておきます。
Private
Sub TestJson() Dim jsonText As String = Me.LoadJsonText Using document As JsonDocument = JsonDocument.Parse(jsonText) '一番外側の要素を取得 Dim root As JsonElement = document.RootElement '一番側の要素はオブジェクトであることがわかっているのでプロパティ数を取得してみます。 Debug.WriteLine($"一番外側のオブジェクトのプロパティ数:{root.EnumerateObject.Count}") '唯一のプロパティを取得。1つ目の要素があるとわかっているのでインデックス0で取得します。 Dim prop1 As JsonProperty = root.EnumerateObject.First Debug.WriteLine($"プロパティ名:{prop1.Name}") Debug.WriteLine($"値の型:{prop1.Value.ValueKind.ToString}") 'Imageプロパティの値を取得します。 Dim middle As JsonElement = prop1.Value Debug.WriteLine($"真ん中のオブジェクトのプロパティ数:{middle.EnumerateObject.Count}") 'プロパティを列挙します。オブジェクトであることがわかっているのでEnumerateObjectを使用します。 For Each prop As JsonProperty In middle.EnumerateObject Debug.WriteLine($"プロパティ名:{prop.Name}") Debug.WriteLine($"プロパティの値:{prop.Value}") Debug.WriteLine($"値の型:{prop.Value.ValueKind.ToString}") Next '3番目のプロパティ(Title)を取得します。(2つ飛ばしたとことろにある1つを取得) Dim titleProp As JsonProperty = middle.EnumerateObject.Skip(2).Take(1).First Debug.WriteLine($"{titleProp.Name} : {titleProp.Value.GetString}") '最後のプロパティを取得します。 Dim lastProp As JsonElement = middle.EnumerateObject.Last.Value '配列の値を列挙します。配列であることがわかっているのでEnumerateArrayを使用します。 For Each value In lastProp.EnumerateArray Debug.WriteLine($"配列の値:{value}") Next End Using End Sub |
参考
.NET での JSON のシリアル化と逆シリアル化 (マーシャリングとアンマーシャリング)-概要
https://docs.microsoft.com/ja-jp/dotnet/standard/serialization/system-text-json-overview
Newtonsoft. Json から system.string に移行する方法
Try the new System.Text.Json APIs
https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/