雑記
 

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

2020/3/20

この記事が対象とする製品・バージョン (バージョンの確認方法)

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.概要

VB/C#でJSONを読み書きするには、JSON.NET(Newtonsoft JSON)またはSystem.Text.Jsonを使用するのが一般的です。

JSON.NETは非常に人気のあるライブラリで多機能です。あまりにもよくできていたためマイクロソフトのASP.NET CoreですらこのJSON.NETに依存する状況が発生してしまいました。これだとASP.NET CoreとJSON.NETそれぞれのバージョンが束縛されてしまいますので、マイクロソフトはJSONを扱う機能を小さなライブラリとして作成して、ASP.NET CoreをJSON.NETの依存から脱却させました。それがSystem.Text.Jsonです。このような事情ですのでどちらを使うのが良いということは特にありません。System.Text.Jsonの機能で十分であれば、それでよいですし、いろいろと複雑な解析が必要であれば高機能なJSON.NETを使うと良いでしょう。

この記事ではJSON.NETを扱います。

なお、JSONの仕様については下記の記事で説明しています。

JSONの仕様

 

2.準備

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

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

 

3.基本的な考え方

JSON.NETでは { } で囲まれたオブジェクトは JObject クラスで表現します。

オブジェクトは 名前 : 値 の0個以上のペアから構成されるのがJSONの仕様なので、JSON.NETではこのペアを JPropertyクラスで表現します。

たとえば、下記のJSONを考えて見ます。

{ } で囲まれたオブジェクトは3つあり、これらはJObjectクラスで表現します。

それぞれのオブジェクト内に名前:値のぺアがあります。名前に着目して拾ってみると、"Image", "Width", "Height", "Title", "Thumbnail", "Url", "Height", "Width", "Animated", "IDs" の10個のペアがあることがわかります。これらの10個はJPropertyクラスで表現されます。上の図では10個のうち、2つだけ赤い枠で囲ってJPropertyと書いています。

値にはいろいろな型があるので理解を深めるために、名前と値の型を一覧にして確認してみましょう。

名前 値の型
Image オブジェクト
Width 数値
Height 数値
Title 文字列
Thumbnail オブジェクト
Url 文字列
Height 数値
Width 数値
Animated 論理
IDs 配列

最後の、IDsの値は [ ] で囲まれており、配列です。JSON.NETではJArrayクラスで配列を表現します。

JObject, JProperty, JArrayの各クラスは JToken を基底クラスとします。

 

JSON.NETでJSONを読み込んで処理をするには大きく分けて3つのアプローチがあります。

このJSONを例に簡単にどのようなアプローチかを紹介します。

 

アプローチ1:JSONの定義がわかっている場合1 - VB/C#のクラスにマッピングする

JSONの定義があらかじめわかっている場合はVB/C#で同じ構造のクラスを作成しておくと、JSON.NETの機能でそのクラスに値を割り当ててくれます。クラスを定義するのは少し面倒かもしれませんが、読み込んだ後の処理では完全にVB/C#の世界になるのでJSONを意識することがありません。つまり、上述で説明したようなJObjectやJPropertyなどのことを知らなくてもよいということです。(とはいえ、いろいろ作っているとどうしても簡単にマッピングしただけでは済まない事態も発生するので、やはり、JSON.NETがどういう考え方なのか上述の説明は理解しておいたほうが良いです。)

まず、上述のJSONをVB/C#のクラスで表現して次のような構造を作成します。プロパティ名は完全にJSONと一致させるのが楽ですが、クラス名は自由です。

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をマッピングできます。

マッピングするにはJsonConvertクラスのDesrializeObjectメソッドを使用し、型パラメーターにマッピング先の型を指定します。

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 = Newtonsoft.Json.JsonConvert.DeserializeObject(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 = Newtonsoft.Json.JsonConvert.DeserializeObject<ProductInfo>(jsonText);
}

 

 

この例では、マッピングした後、何もしていませんが、root変数の内容を見ると、ちゃんと値が入っているのわかります。

ここまでできれば後はプログラムで自由に利用できますね。

 

アプローチ2:JSONの定義がわかっている場合2 - 位置を指定してアクセスする

JSONの定義がわかっているならば、クラスを定義しなくても値を読み取ることが可能です。

このアプローチはさらにいくつかの手段にわかれます。代表的なのは動的ランタイムを使用する方法です。

たとえば、次のようにしてUrlプロパティの値を読み込むことができます。

このプログラム中に登場する LoadJsonTextメソッドは上述のプログラム例に登場するものと同じなので割愛しています。

VBの例を実行するにはOption StrictがOffである必要があります。

rootの型がJObjectではなく、Objectであることに注意してください。

VB

Private Sub TestJson()
    Dim jsonText As String = Me.LoadJsonText

    Dim root As Object = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText)
    Dim url As String = root("Image")("Thumbnail")("Url")
End Sub

C#の場合、スマートに書けます。

C#

private void TestJson()
{
    string jsonText = this.LoadJsonText();

    dynamic root = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText);
    string url = root.Image.Thumbnail.Url;
}

VBでも少し細工をすればC#のように自前のプロパティであるかのごとくアクセスすることが可能です。

それにはここで解説されているDynamicAdapterクラスを定義します。

http://umayadia.cloudapp.net/article/.NET/JSON%e3%81%ae%e3%82%b7%e3%83%aa%e3%82%a2%e3%83%a9%e3%82%a4%e3%82%ba%e3%81%a8%e3%83%87%e3%82%b7%e3%83%aa%e3%82%a2%e3%83%a9%e3%82%a4%e3%82%ba

このクラスが存在すれば次のように記述できます。Option StrictはOffである必要があります。

VB

Private Sub TestJson()
    Dim jsonText As String = Me.LoadJsonText

    Dim root As Object = New DynamicAdapter(Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText))
    Dim url As String = root.Image.Thumbnail.Url
End Sub

 

なお、JSON内の位置を指定して値を取り出すにはJSONパスを利用することもできます。これを使うとUrlは次のように取り出せます。

下記のプログラムを実行するには冒頭にVBの場合、 Imports Newtonsoft.Json.Linq が、C#の場合、 using Newtonsoft.Json.Linq; が必要です。

JSONパスについてはこちらに説明があります。

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

VB

Private Sub TestJson()
    Dim jsonText As String = Me.LoadJsonText

    Dim root As JToken = DirectCast(Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText), JToken)
    Dim target As String = root.SelectToken("$.Image.Thumbnail.Url").ToString
End Sub

C#

private void TestJson()
{
    string jsonText = this.LoadJsonText();

    JToken root = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText) as JToken;
    string url = root.SelectToken("$.Image.Thumbnail.Url").ToString();
}

 

アプローチ3:JSONの定義がわかっていない場合

JSONの定義があらかじめわかっていない場合、For Each(foreach)の列挙や、条件分岐、再帰などを使って構造を解析したり、値を検索、取得していくことになります。

このときに登場するのが前に説明したJObjectやJPropertyなどのクラス群です。これらのクラスの多くはNewtonsoft.Json.Linq名前空間で定義されています。

下記のプログラムを実行するには冒頭にVBの場合、 Imports Newtonsoft.Json.Linq が、C#の場合、 using Newtonsoft.Json.Linq; が必要です。

 

このプログラムでは読み込んでJSONの要素がオブジェクトなのか配列なのかその他なのかで分岐、オブジェクトの場合はプロパティの列挙、配列の場合は値の列挙、値の場合は値を表示するという処理を行っています。

上述のJSONでのみテストした簡易的な実装であり、対応できないケースもあるのではないかと思いますが、構造がわからないJSONに対するアプローチを示すのには十分だと思います。

VB

Private Sub TestJson()

    Dim jsonText As String = Me.LoadJsonText
    Dim root As JToken = DirectCast(Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText), JToken)

    Parse(root)

End Sub

Private Sub Parse(source As JToken)

    If (source.Type = JTokenType.Object) Then
        Debug.WriteLine("(オブジェクト)")
        ParseObject(DirectCast(source, JObject))
    ElseIf (source.Type = JTokenType.Array) Then
        Debug.WriteLine("(配列)")
        ParseArray(DirectCast(source, JArray))
    Else
        Debug.WriteLine(source)
    End If

End Sub

Private Sub ParseObject(source As JObject)

    For Each prop In source.Properties
        Debug.Write($"{prop.Name} : ")
        Parse(prop.Value)
    Next

End Sub

Private Sub ParseArray(source As JArray)

    For Each value In source
        Parse(value)
    Next

End Sub

C#

private void TestJson()
{
    string jsonText = this.LoadJsonText();
    JToken root = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText) as JToken;

    Parse(root);
}

private void Parse(JToken source)
{
    if (source.Type == JTokenType.Object)
    {
        System.Diagnostics.Debug.WriteLine("(オブジェクト)");
        ParseObject(source as JObject);
    }
    else if (source.Type == JTokenType.Array)
    {
        System.Diagnostics.Debug.WriteLine("(配列)");
        ParseArray(source as JArray);
    }
    else
    {
        System.Diagnostics.Debug.WriteLine(source);
    }
}

private void ParseObject(JObject source)
{
    foreach(var prop in source.Properties())
    {
        System.Diagnostics.Debug.Write($"{prop.Name} : ");
        Parse(prop.Value);
    }
}

private void ParseArray(JArray source)
{
    foreach(var value in source)
    {
        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

 

 

おまけ

JObjectやJPropertyなどの理解を深める練習用のサンプルを紹介しておきます。

Private Sub TestJson()

    Dim jsonText As String = Me.LoadJsonText

    '一番外側はオブジェクトなので、JObjectが返されます。
    Dim root As JObject = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText)

    Debug.WriteLine($"一番外側のオブジェクトのプロパティ数:{root.Properties.Count}")

    '唯一のプロパティを取得
    Dim prop1 As JProperty = root.Properties(0)
    Debug.WriteLine($"プロパティ名:{prop1.Name}")
    Debug.WriteLine($"値の型:{prop1.Value.Type.ToString}")

    'Imageプロパティの値はオブジェクトであることがわかっているので取得します。
    Dim middle As JObject = prop1.Value

    Debug.WriteLine($"真ん中のオブジェクトのプロパティ数:{middle.Properties.Count}")

    'プロパティを列挙します。
    For Each prop In middle.Properties
        Debug.WriteLine($"プロパティ名:{prop.Name}")
        Debug.WriteLine($"プロパティの値:{prop.Value}")
        Debug.WriteLine($"値の型:{prop.Value.Type.ToString}")
    Next

    '最後のプロパティは配列であることがわかっているのでJArrayで取得します。
    Dim lastProp As JArray = middle.Properties.Last.Value
    '配列の値を列挙します。
    For Each value In lastProp
        Debug.WriteLine($"配列の値:{value}")
    Next


End Sub

 

 

参考

.NET での JSON のシリアル化と逆シリアル化 (マーシャリングとアンマーシャリング)-概要

https://docs.microsoft.com/ja-jp/dotnet/standard/serialization/system-text-json-overview

 

Newtonsoft. Json から system.string に移行する方法

https://docs.microsoft.com/ja-jp/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to

 

Try the new System.Text.Json APIs

https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/