VB.NETでJSONをデコードする

こんにちは。

開発担当のお猿です。

年末年始にVB.NETでJSONを楽にデコードしたいな~と思いましたが、中々良いものがないので作ってみました。

今回はJSON文字列をVB上にハードコーディングする際、ダブルクォーテーションがとっても邪魔なので、ダブルクォーテーションが無くても読み込めるように調整してみました。

※「スペース」「カンマ」「コロン」を文字列として格納したいときは、ダブルクォーテーションを使用して書かないとならないです。

上手いこと活用出来たらよいかと思います。

(ソースの解析に関しては、また気が向いたときに記載したいと思います。)

Imports System.Text

Public Class JsonClass

    Private _baseJson As String             ' デコード対象文字列
    Private _outOfChar As New Collection    ' Jsonからの除外文字
    Private _replaceTxt As New Hashtable    ' Jsonエスケープ文字

    ''' <summary>
    ''' デコードしたJSON
    ''' </summary>
    ''' <returns>String</returns>
    Public ReadOnly Property BaseJson As String
        Get
            Return _baseJson
        End Get
    End Property

    ''' <summary>
    ''' Jsonからの除外文字
    ''' </summary>
    ''' <returns>Collection</returns>
    Public Property OutOfChar As Collection
        Set(value As Collection)
            _outOfChar = value
        End Set
        Get
            Return _outOfChar
        End Get
    End Property

    ''' <summary>
    ''' Jsonエスケープ文字
    ''' </summary>
    ''' <returns>Hashtable</returns>
    Public Property ReplaceTxt As Hashtable
        Set(value As Hashtable)
            _replaceTxt = value
        End Set
        Get
            Return _replaceTxt
        End Get
    End Property

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    Public Sub New()

        ' Jsonからの除外文字
        _outOfChar.Add(vbCrLf)
        _outOfChar.Add(vbCr)
        _outOfChar.Add(vbLf)
        _outOfChar.Add(vbTab)
        _outOfChar.Add(vbVerticalTab)
        _outOfChar.Add(vbFormFeed)
        _outOfChar.Add(vbNewLine)
        _outOfChar.Add(vbNullChar)
        _outOfChar.Add(vbNullString)
        _outOfChar.Add(" ")
        _outOfChar.Add(" ")

        ' Jsonエスケープ文字
        _replaceTxt("\""") = """"
        _replaceTxt("\\") = "\"
        _replaceTxt("\/") = "/"
        _replaceTxt("\n") = vbCr
        _replaceTxt("\r") = vbLf
        _replaceTxt("\t") = vbTab

    End Sub

    ''' <summary>
    ''' Jsonをデコード
    ''' </summary>
    ''' <param name="targetString">対象文字列</param>
    ''' <returns>Object</returns>
    Public Function JsonDecode(ByVal targetString As String) As Object
        Try
            ' デコード対象文字列
            _baseJson = Trim(targetString)

            ' 改行・スペースを削除する
            Dim tChr As String                  ' 調査対象文字
            Dim stFlg As Boolean = False        ' 除外文字数フラグ
            For i As Integer = 0 To _baseJson.Length - 1 Step 1
                ' 調査対象文字
                tChr = _baseJson(i)

                ' 除外文字を除外する
                stFlg = True
                For Each oChr As String In _outOfChar
                    If tChr = oChr Then
                        stFlg = False
                        Exit For
                    End If
                Next
                If stFlg = True Then
                    _baseJson = _baseJson.Substring(i, _baseJson.Length - i)
                    Exit For
                End If
            Next i

            ' Json解析開始
            If Left(_baseJson, 1) = "[" Then
                ' 配列の場合
                Return RecursiveObject(0, "[", 0)

            Else
                ' 何もない場合、オブジェクトとみなす
                Return RecursiveObject(0, "{", 0)

            End If

        Catch ex As Exception
            Throw
        End Try

    End Function

    ''' <summary>
    ''' 再起処理でオブジェクト(配列)毎にデータを取得する
    ''' </summary>
    ''' <param name="stPoint">開始位置</param>
    ''' <param name="chkType">オブジェクトタイプ</param>
    ''' <param name="resPoint">再起戻り値用</param>
    ''' <returns>Object</returns>
    Private Function RecursiveObject(ByVal stPoint As Integer,
                                     ByVal chkType As String,
                                     ByRef resPoint As Integer) As Object
        Try

            Dim i, j, k As Integer              ' ループ用
            Dim tChr As String                  ' 調査対象文字
            Dim isTxtFlg As Boolean = False     ' 文字列中フラグ
            Dim resObj As Object = Nothing      ' 戻り値格納用
            Dim keySt As Integer = stPoint + 1  ' key開始位置
            Dim valSt As Integer = stPoint + 1  ' value開始位置
            Dim keyData As String = ""          ' keyデータ
            Dim objCount As Integer = 0         ' オブジェクトキーなしの番号
            Dim chkCommaFlg As Boolean          ' カンマ時のフラグ

            Dim dataObj As Object = Nothing     ' 今回の処理の型
            If chkType = "{" Then
                dataObj = New Hashtable
            ElseIf chkType = "[" Then
                dataObj = New ArrayList
            End If

            For i = stPoint + 1 To _baseJson.Length - 1 Step 1  ' 2文字目から取得
                ' 調査対象文字
                tChr = _baseJson(i)

                ' 文字がダブルクォーテーションの場合、フラグをスイッチする
                If tChr = """" Then
                    isTxtFlg = Not isTxtFlg
                End If

                ' 文字列フラグが外れている場合
                If isTxtFlg = False Then

                    ' 再起処理チェック
                    If tChr = "{" Then
                        ' 再起処理(オブジェクト)
                        resObj = RecursiveObject(i, "{", j)
                        i = j + 1
                        tChr = _baseJson(i)

                    ElseIf tChr = "[" Then
                        ' 再起処理(配列)
                        resObj = RecursiveObject(i, "[", j)
                        i = j + 1
                        tChr = _baseJson(i)

                    End If

                    ' その他jsonキー文字列
                    If tChr = ":" Then
                        If chkType = "{" Then
                            ' オブジェクトの場合のキーとの切り離し
                            keyData = _baseJson.Substring(keySt, i - keySt)
                            keySt = i + 1
                            valSt = i + 1
                        End If


                    ElseIf tChr = "}" Then
                        If chkType = "{" Then
                            ' 処理が閉じた場合
                            Call InputObjectData(i, CType(dataObj, Hashtable), resObj, keyData, objCount, keySt, valSt)

                            'データを戻す
                            resPoint = i
                            Return dataObj
                        End If

                    ElseIf tChr = "]" Then
                        If chkType = "[" Then
                            ' 処理が閉じた場合
                            Call InputArrayData(i, CType(dataObj, ArrayList), resObj, valSt)

                            'データを戻す
                            resPoint = i
                            Return dataObj
                        End If

                    ElseIf i = _baseJson.Length - 1 Then
                        ' 最後まで来た場合
                        If chkType = "{" Then
                            Call InputObjectData(i, CType(dataObj, Hashtable), resObj, keyData, objCount, keySt, valSt)
                            'データを戻す
                            Return dataObj
                        ElseIf chkType = "[" Then
                            Call InputArrayData(i, CType(dataObj, ArrayList), resObj, valSt)
                            'データを戻す
                            Return dataObj
                        End If

                    ElseIf tChr = "," Then
                        ' 次が閉じの場合、""になるのを防ぐ
                        For k = i + 1 To _baseJson.Length - 1 Step 1
                            ' 次の文字列が除外文字でない場合のみ進む
                            chkCommaFlg = False
                            For Each oChr As String In _outOfChar
                                If _baseJson(k) = oChr Then
                                    chkCommaFlg = True
                                    Exit For
                                End If
                            Next
                            If chkCommaFlg = False Then
                                If _baseJson(k) <> "}" AndAlso _baseJson(k) <> "]" Then
                                    ' 次の文字が}]でなければ登録する
                                    If chkType = "{" Then
                                        ' オブジェクトを登録する
                                        Call InputObjectData(i, CType(dataObj, Hashtable), resObj, keyData, objCount, keySt, valSt)

                                    ElseIf chkType = "[" Then
                                        ' 配列を登録する
                                        Call InputArrayData(i, CType(dataObj, ArrayList), resObj, valSt)
                                    End If
                                End If
                                Exit For
                            End If
                        Next k
                    End If
                End If
            Next i

            Return Nothing
        Catch ex As Exception
            Throw
        End Try
    End Function

    ''' <summary>
    ''' オブジェクトにデータを格納する
    ''' </summary>
    ''' <param name="i">現在の桁文字位置</param>
    ''' <param name="dataObj">最終的なオブジェクト</param>
    ''' <param name="resObj">再起処理からのオブジェクト</param>
    ''' <param name="keyData">キー名</param>
    ''' <param name="objCount">キーが存在しない場合のカウント</param>
    ''' <param name="keySt">キー開始位置</param>
    ''' <param name="valSt">データ開始位置</param>
    Private Sub InputObjectData(ByVal i As Integer,
                                ByRef dataObj As Hashtable,
                                ByRef resObj As Object,
                                ByRef keyData As String,
                                ByRef objCount As Integer,
                                ByRef keySt As Integer,
                                ByRef valSt As Integer)
        Try
            If resObj Is Nothing Then
                ' 再起からの戻りでない場合
                If keyData = "" Then
                    ' キーなしデータを入れる
                    dataObj.Add(objCount, TrimTxtData(_baseJson.Substring(valSt, i - valSt)))
                    objCount += 1
                Else
                    ' キーありデータを入れる
                    dataObj.Add(TrimTxtData(keyData), TrimTxtData(_baseJson.Substring(valSt, i - valSt)))
                End If
            Else
                ' 再起からの戻りの場合
                If keyData = "" Then
                    ' キーなしデータを入れる
                    dataObj.Add(objCount, resObj)
                    objCount += 1
                Else
                    ' キーありデータを入れる
                    dataObj.Add(TrimTxtData(keyData), resObj)
                End If
            End If

            keyData = ""        ' キーデータ初期化
            resObj = Nothing    ' 再起データ初期化

            keySt = i + 1       ' key開始場所初期化
            valSt = i + 1       ' val開始場所初期化
        Catch ex As Exception
            Throw
        End Try
    End Sub

    ''' <summary>
    ''' 配列にデータを格納する
    ''' </summary>
    ''' <param name="i">現在の桁文字位置</param>
    ''' <param name="dataObj">最終的なオブジェクト</param>
    ''' <param name="resObj">再起処理からのオブジェクト</param>
    ''' <param name="valSt">データ開始位置</param>
    Private Sub InputArrayData(ByVal i As Integer,
                               ByRef dataObj As ArrayList,
                               ByRef resObj As Object,
                               ByRef valSt As Integer)
        Try
            If resObj Is Nothing Then
                ' 再起からの戻りでない場合
                dataObj.Add(TrimTxtData(_baseJson.Substring(valSt, i - valSt)))
            Else
                ' 再起からの戻りの場合
                dataObj.Add(resObj)
            End If

            resObj = Nothing    ' 再起データ初期化
            valSt = i + 1       ' val開始場所初期化
        Catch ex As Exception
            Throw
        End Try
    End Sub

    ''' <summary>
    ''' ダブルクォーテーションと先頭・最後の改行を除外する
    ''' </summary>
    ''' <param name="targetTxt">対象文字列</param>
    ''' <returns>string</returns>
    Private Function TrimTxtData(ByVal targetTxt As String) As String
        Try
            ' 対象をTrim
            Dim tmpData As String = Trim(targetTxt)

            ' 不要な文字列を削除
            Dim isTxtFlg As Boolean = False     ' 文字列中フラグ
            Dim tChr As String = ""             ' 対象文字
            Dim moveFlg As Boolean = False      ' 移動フラグ
            Dim res As New StringBuilder        ' 戻り値用

            ' JSONから除外文字列を削除
            For i As Integer = 0 To tmpData.Length - 1 Step 1   ' 1文字ずつループ
                ' 調査対象文字
                tChr = tmpData(i)

                ' 文字がダブルクォーテーションの場合、フラグをスイッチする
                If tChr = """" Then
                    isTxtFlg = Not isTxtFlg
                End If

                If isTxtFlg = False Then
                    ' 文字列フラグが折れている場合
                    ' 除外文字列を除いてデータを格納する
                    moveFlg = False
                    For Each oChr As String In _outOfChar
                        If tChr = oChr Then
                            moveFlg = True
                            Exit For
                        End If
                    Next
                    If moveFlg = False Then
                        If tChr <> "," Then
                            res.Append(tChr)
                        End If
                    End If
                Else
                    ' 文字列フラグが立っている場合
                    res.Append(tChr)
                End If
            Next i

            tmpData = res.ToString

            ' ダブルクォーテーションを削除
            If Left(tmpData, 1) = """" AndAlso Right(tmpData, 1) = """" Then
                tmpData = tmpData.Substring(1, Len(tmpData) - 2)
            End If

            ' Json仕様上のエスケープ文字置換
            If tmpData <> "" Then
                For Each repTxt As DictionaryEntry In _replaceTxt
                    tmpData = Replace(tmpData, CType(repTxt.Key, String), CType(repTxt.Value, String))
                Next
            End If

            Return tmpData
        Catch ex As Exception
            Throw
        End Try

    End Function

End Class

基本的に戻り値は

配列→ArrayList。

オブジェクト→Hashtable

で戻るように作っています。


一応、↑のクラスを使用した読み込みのサンプルとしてダミーのJSONを生成してくれるサイトを置いておきますね。

JSON GENERATOR


基本的なパターンは網羅しているかと思いますが、バグ等が見つかった場合はご連絡いただけると幸いです。