Parsing JSON in Excel VBA

后端 未结 11 903
长发绾君心
长发绾君心 2020-11-22 09:58

I have the same issue as in Excel VBA: Parsed JSON Object Loop but cannot find any solution. My JSON has nested objects so suggested solution like VBJSON and vba-json do not

相关标签:
11条回答
  • Simpler way you can go array.myitem(0) in VB code

    my full answer here parse and stringify (serialize)

    Use the 'this' object in js

    ScriptEngine.AddCode "Object.prototype.myitem=function( i ) { return this[i] } ; "
    

    Then you can go array.myitem(0)

    Private ScriptEngine As ScriptControl
    
    Public Sub InitScriptEngine()
        Set ScriptEngine = New ScriptControl
        ScriptEngine.Language = "JScript"
        ScriptEngine.AddCode "Object.prototype.myitem=function( i ) { return this[i] } ; "
        Set foo = ScriptEngine.Eval("(" + "[ 1234, 2345 ]" + ")") ' JSON array
        Debug.Print foo.myitem(1) ' method case sensitive!
        Set foo = ScriptEngine.Eval("(" + "{ ""key1"":23 , ""key2"":2345 }" + ")") ' JSON key value
        Debug.Print foo.myitem("key1") ' WTF
    
    End Sub
    
    0 讨论(0)
  • 2020-11-22 10:28

    Thanks a lot Codo.

    I've just updated and completed what you have done to :

    • serialize the json (I need it to inject the json in a text-like document)
    • add, remove and update node (who knows)

      Option Explicit
      
      Private ScriptEngine As ScriptControl
      
      Public Sub InitScriptEngine()
          Set ScriptEngine = New ScriptControl
          ScriptEngine.Language = "JScript"
          ScriptEngine.AddCode "function getProperty(jsonObj, propertyName) { return jsonObj[propertyName]; } "
          ScriptEngine.AddCode "function getType(jsonObj, propertyName) {return typeof(jsonObj[propertyName]);}"
          ScriptEngine.AddCode "function getKeys(jsonObj) { var keys = new Array(); for (var i in jsonObj) { keys.push(i); } return keys; } "
          ScriptEngine.AddCode "function addKey(jsonObj, propertyName, value) { jsonObj[propertyName] = value; return jsonObj;}"
          ScriptEngine.AddCode "function removeKey(jsonObj, propertyName) { var json = jsonObj; delete json[propertyName]; return json }"
      End Sub
      Public Function removeJSONProperty(ByVal JsonObject As Object, propertyName As String)
          Set removeJSONProperty = ScriptEngine.Run("removeKey", JsonObject, propertyName)
      End Function
      
      Public Function updateJSONPropertyValue(ByVal JsonObject As Object, propertyName As String, value As String) As Object
          Set updateJSONPropertyValue = ScriptEngine.Run("removeKey", JsonObject, propertyName)
          Set updateJSONPropertyValue = ScriptEngine.Run("addKey", JsonObject, propertyName, value)
      End Function
      
      
      
      Public Function addJSONPropertyValue(ByVal JsonObject As Object, propertyName As String, value As String) As Object
          Set addJSONPropertyValue = ScriptEngine.Run("addKey", JsonObject, propertyName, value)
      End Function
      Public Function DecodeJsonString(ByVal JsonString As String)
      InitScriptEngine
          Set DecodeJsonString = ScriptEngine.Eval("(" + JsonString + ")")
      End Function
      
      Public Function GetProperty(ByVal JsonObject As Object, ByVal propertyName As String) As Variant
          GetProperty = ScriptEngine.Run("getProperty", JsonObject, propertyName)
      End Function
      
      Public Function GetObjectProperty(ByVal JsonObject As Object, ByVal propertyName As String) As Object
          Set GetObjectProperty = ScriptEngine.Run("getProperty", JsonObject, propertyName)
      End Function
      
      Public Function SerializeJSONObject(ByVal JsonObject As Object) As String()
          Dim Length As Integer
          Dim KeysArray() As String
          Dim KeysObject As Object
          Dim Index As Integer
          Dim Key As Variant
          Dim tmpString As String
          Dim tmpJSON As Object
          Dim tmpJSONArray() As Variant
          Dim tmpJSONObject() As Variant
          Dim strJsonObject As String
          Dim tmpNbElement As Long, i As Long
          InitScriptEngine
          Set KeysObject = ScriptEngine.Run("getKeys", JsonObject)
      
          Length = GetProperty(KeysObject, "length")
          ReDim KeysArray(Length - 1)
          Index = 0
          For Each Key In KeysObject
          tmpString = ""
              If ScriptEngine.Run("getType", JsonObject, Key) = "object" Then
          'MsgBox "object " & SerializeJSONObject(GetObjectProperty(JsonObject, Key))(0)
                  Set tmpJSON = GetObjectProperty(JsonObject, Key)
                  strJsonObject = VBA.Replace(ScriptEngine.Run("getKeys", tmpJSON), " ", "")
                  tmpNbElement = Len(strJsonObject) - Len(VBA.Replace(strJsonObject, ",", ""))
      
                  If VBA.IsNumeric(Left(ScriptEngine.Run("getKeys", tmpJSON), 1)) = True Then
      
                      ReDim tmpJSONArray(tmpNbElement)
                      For i = 0 To tmpNbElement
                          tmpJSONArray(i) = GetProperty(tmpJSON, i)
                      Next
                          tmpString = "[" & Join(tmpJSONArray, ",") & "]"
                  Else
                      tmpString = "{" & Join(SerializeJSONObject(tmpJSON), ", ") & "}"
                  End If
      
              Else
                      tmpString = GetProperty(JsonObject, Key)
      
              End If
      
              KeysArray(Index) = Key & ": " & tmpString
              Index = Index + 1
          Next
      
          SerializeJSONObject = KeysArray
      
      End Function
      
      Public Function GetKeys(ByVal JsonObject As Object) As String()
          Dim Length As Integer
          Dim KeysArray() As String
          Dim KeysObject As Object
          Dim Index As Integer
          Dim Key As Variant
      InitScriptEngine
          Set KeysObject = ScriptEngine.Run("getKeys", JsonObject)
          Length = GetProperty(KeysObject, "length")
          ReDim KeysArray(Length - 1)
          Index = 0
          For Each Key In KeysObject
              KeysArray(Index) = Key
              Index = Index + 1
          Next
          GetKeys = KeysArray
      End Function
      
    0 讨论(0)
  • 2020-11-22 10:33

    Another Regex based JSON parser (decode only)

    Private Enum JsonStep
        jsonString
        jsonNumber
        jsonTrue
        jsonFalse
        jsonNull
        jsonOpeningBrace
        jsonClosingBrace
        jsonOpeningBracket
        jsonClosingBracket
        jsonComma
        jsonColon
    End Enum
    
    Private regexp As Object
    
    Private Function JsonStepName(ByVal json_step As JsonStep) As String
        Select Case json_step
            Case jsonString: JsonStepName = "'STRING'"
            Case jsonNumber: JsonStepName = "'NUMBER'"
            Case jsonTrue: JsonStepName = "true"
            Case jsonFalse: JsonStepName = "false"
            Case jsonNull: JsonStepName = "null"
            Case jsonOpeningBrace: JsonStepName = "'{'"
            Case jsonClosingBrace: JsonStepName = "'}'"
            Case jsonOpeningBracket: JsonStepName = "'['"
            Case jsonClosingBracket: JsonStepName = "']'"
            Case jsonComma: JsonStepName = "','"
            Case jsonColon: JsonStepName = "':'"
        End Select
    End Function
    
    Private Function Unescape(ByVal str As String) As String
        Dim match As Object
    
        str = Replace$(str, "\""", """")
        str = Replace$(str, "\\", "\")
        str = Replace$(str, "\/", "/")
        str = Replace$(str, "\b", vbBack)
        str = Replace$(str, "\f", vbFormFeed)
        str = Replace$(str, "\n", vbCrLf)
        str = Replace$(str, "\r", vbCr)
        str = Replace$(str, "\t", vbTab)
        With regexp
            .Global = True
            .IgnoreCase = False
            .MultiLine = False
            .Pattern = "\\u([0-9a-fA-F]{4})"
            For Each match In .Execute(str)
                str = Replace$(str, match.value, ChrW$(Val("&H" + match.SubMatches(0))), match.FirstIndex + 1, 1)
            Next match
        End With
        Unescape = str
    End Function
    
    Private Function ParseStep(ByVal str As String, _
                               ByRef index As Long, _
                               ByRef value As Variant, _
                               ByVal json_step As JsonStep, _
                               ByVal expected As Boolean) As Boolean
        Dim match As Object
    
        With regexp
            .Global = False
            .IgnoreCase = False
            .MultiLine = False
            Select Case json_step
                'Case jsonString: .Pattern = "^\s*""(([^\\""]+|\\[""\\/bfnrt]|\\u[0-9a-fA-F]{4})*)""\s*"
                Case jsonString: .Pattern = "^\s*""([^\\""]+|([^\\""]+|\\[""\\/bfnrt]|\\u[0-9a-fA-F]{4})*)""\s*"
                Case jsonNumber: .Pattern = "^\s*(-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?)\s*"
                Case jsonTrue: .Pattern = "^\s*(true)\s*"
                Case jsonFalse: .Pattern = "^\s*(false)\s*"
                Case jsonNull: .Pattern = "^\s*(null)\s*"
                Case jsonOpeningBrace: .Pattern = "^\s*(\{)\s*"
                Case jsonClosingBrace: .Pattern = "^\s*(\})\s*"
                Case jsonOpeningBracket: .Pattern = "^\s*(\[)\s*"
                Case jsonClosingBracket: .Pattern = "^\s*(\])\s*"
                Case jsonComma: .Pattern = "^\s*(\,)\s*"
                Case jsonColon: .Pattern = "^\s*(:)\s*"
            End Select
            Set match = .Execute(Mid$(str, index))
        End With
        If match.Count > 0 Then
            index = index + match(0).Length
            Select Case json_step
                Case jsonString
                    If match(0).SubMatches(1) = Empty Then
                        value = match(0).SubMatches(0)
                    Else
                        value = Unescape(match(0).SubMatches(0))
                    End If
                Case jsonNumber: value = Val(match(0).SubMatches(0))
                Case jsonTrue: value = True
                Case jsonFalse: value = False
                Case jsonNull: value = Null
                Case Else: value = Empty
            End Select
            ParseStep = True
        ElseIf expected Then
            Err.Raise 10001, "ParseJson", "Expecting " & JsonStepName(json_step) & " at char " & index & "."
        End If
    End Function
    
    Private Function ParseValue(ByRef str As String, _
                                ByRef index As Long, _
                                ByRef value As Variant, _
                                ByVal expected As Boolean) As Boolean
        ParseValue = True
        If ParseStep(str, index, value, jsonString, False) Then Exit Function
        If ParseStep(str, index, value, jsonNumber, False) Then Exit Function
        If ParseObject(str, index, value, False) Then Exit Function
        If ParseArray(str, index, value, False) Then Exit Function
        If ParseStep(str, index, value, jsonTrue, False) Then Exit Function
        If ParseStep(str, index, value, jsonFalse, False) Then Exit Function
        If ParseStep(str, index, value, jsonNull, False) Then Exit Function
        ParseValue = False
        If expected Then
            Err.Raise 10001, "ParseJson", "Expecting " & JsonStepName(jsonString) & ", " & JsonStepName(jsonNumber) & ", " & JsonStepName(jsonTrue) & ", " & JsonStepName(jsonFalse) & ", " & JsonStepName(jsonNull) & ", " & JsonStepName(jsonOpeningBrace) & ", or " & JsonStepName(jsonOpeningBracket) & " at char " & index & "."
        End If
    End Function
    
    Private Function ParseObject(ByRef str As String, _
                                 ByRef index As Long, _
                                 ByRef obj As Variant, _
                                 ByVal expected As Boolean) As Boolean
        Dim key As Variant
        Dim value As Variant
    
        ParseObject = ParseStep(str, index, Empty, jsonOpeningBrace, expected)
        If ParseObject Then
            Set obj = CreateObject("Scripting.Dictionary")
            If ParseStep(str, index, Empty, jsonClosingBrace, False) Then Exit Function
            Do
                If ParseStep(str, index, key, jsonString, True) Then
                    If ParseStep(str, index, Empty, jsonColon, True) Then
                        If ParseValue(str, index, value, True) Then
                            If IsObject(value) Then
                                Set obj.Item(key) = value
                            Else
                                obj.Item(key) = value
                            End If
                        End If
                    End If
                End If
            Loop While ParseStep(str, index, Empty, jsonComma, False)
            ParseObject = ParseStep(str, index, Empty, jsonClosingBrace, True)
        End If
    End Function
    
    Private Function ParseArray(ByRef str As String, _
                                ByRef index As Long, _
                                ByRef arr As Variant, _
                                ByVal expected As Boolean) As Boolean
        Dim key As Variant
        Dim value As Variant
    
        ParseArray = ParseStep(str, index, Empty, jsonOpeningBracket, expected)
        If ParseArray Then
            Set arr = New Collection
            If ParseStep(str, index, Empty, jsonClosingBracket, False) Then Exit Function
            Do
                If ParseValue(str, index, value, True) Then
                    arr.Add value
                End If
            Loop While ParseStep(str, index, Empty, jsonComma, False)
            ParseArray = ParseStep(str, index, Empty, jsonClosingBracket, True)
        End If
    End Function
    
    Public Function ParseJson(ByVal str As String) As Object
        If regexp Is Nothing Then
            Set regexp = CreateObject("VBScript.RegExp")
        End If
        If ParseObject(str, 1, ParseJson, False) Then Exit Function
        If ParseArray(str, 1, ParseJson, False) Then Exit Function
        Err.Raise 10001, "ParseJson", "Expecting " & JsonStepName(jsonOpeningBrace) & " or " & JsonStepName(jsonOpeningBracket) & "."
    End Function
    
    0 讨论(0)
  • 2020-11-22 10:33

    Two small contributions to Codo's answer:

    ' "recursive" version of GetObjectProperty
    Public Function GetObjectProperty(ByVal JsonObject As Object, ByVal propertyName As String) As Object
        Dim names() As String
        Dim i As Integer
    
        names = Split(propertyName, ".")
    
        For i = 0 To UBound(names)
            Set JsonObject = ScriptEngine.Run("getProperty", JsonObject, names(i))
        Next
    
        Set GetObjectProperty = JsonObject
    End Function
    
    ' shortcut to object array
    Public Function GetObjectArrayProperty(ByVal JsonObject As Object, ByVal propertyName As String) As Object()
        Dim a() As Object
        Dim i As Integer
        Dim l As Integer
    
        Set JsonObject = GetObjectProperty(JsonObject, propertyName)
    
        l = GetProperty(JsonObject, "length") - 1
    
        ReDim a(l)
    
        For i = 0 To l
            Set a(i) = GetObjectProperty(JsonObject, CStr(i))
        Next
    
        GetObjectArrayProperty = a
    End Function
    

    So now I can do stuff like:

    Dim JsonObject As Object
    Dim Value() As Object
    Dim i As Integer
    Dim Total As Double
    
    Set JsonObject = DecodeJsonString(CStr(request.responseText))
    
    Value = GetObjectArrayProperty(JsonObject, "d.Data")
    
    For i = 0 To UBound(Value)
        Total = Total + Value(i).Amount
    Next
    
    0 讨论(0)
  • 2020-11-22 10:36

    Microsoft: Because VBScript is a subset of Visual Basic for Applications,...

    The code below is derived from Codo's post should it also be helpful to have in class form, and usable as VBScript:

    class JsonParser
        ' adapted from: http://stackoverflow.com/questions/6627652/parsing-json-in-excel-vba
        private se
        private sub Class_Initialize
            set se = CreateObject("MSScriptControl.ScriptControl") 
            se.Language = "JScript"
            se.AddCode "function getValue(jsonObj, valueName) { return jsonObj[valueName]; } "
            se.AddCode "function enumKeys(jsonObj) { var keys = new Array(); for (var i in jsonObj) { keys.push(i); } return keys; } "
        end sub
        public function Decode(ByVal json)
            set Decode = se.Eval("(" + cstr(json) + ")")
        end function
    
        public function GetValue(ByVal jsonObj, ByVal valueName)
            GetValue = se.Run("getValue", jsonObj, valueName)
        end function
    
        public function GetObject(ByVal jsonObject, ByVal valueName)
            set GetObjet = se.Run("getValue", jsonObject, valueName)
        end function
    
        public function EnumKeys(ByVal jsonObject)
            dim length, keys, obj, idx, key
            set obj = se.Run("enumKeys", jsonObject)
            length = GetValue(obj, "length")
            redim keys(length - 1)
            idx = 0
            for each key in obj
                keys(idx) = key
                idx = idx + 1
            next
            EnumKeys = keys
        end function
    end class
    

    Usage:

    set jp = new JsonParser
    set jo = jp.Decode("{value: true}")
    keys = jp.EnumKeys(jo)
    value = jp.GetValue(jo, "value")
    
    0 讨论(0)
提交回复
热议问题