Write large collection object (parsed from json) to excel range Write large collection object (parsed from json) to excel range json json

Write large collection object (parsed from json) to excel range


Consider the below example, there is pure VBA JSON parser. It's quite fast, but not so flexible, so it's suitable for parsing of simple json array of objects containing table-like data only.

Option ExplicitSub Test()        Dim strJsonString As String    Dim arrResult() As Variant        ' download    strJsonString = DownloadJson("https://www.gw2shinies.com/api/json/item/tp")        ' process    arrResult = ConvertJsonToArray(strJsonString)        ' output    Output Sheets(1), arrResult    End SubFunction DownloadJson(strUrl As String) As String        With CreateObject("MSXML2.XMLHTTP")        .Open "GET", strUrl        .Send        If .Status <> 200 Then            Debug.Print .Status            Exit Function        End If        DownloadJson = .responseText    End With    End FunctionFunction ConvertJsonToArray(strJsonString As String) As Variant        Dim strCnt As String    Dim strMarkerQuot As String    Dim arrUnicode() As String    Dim arrQuots() As String    Dim arrRows() As String    Dim arrProps() As String    Dim arrTokens() As String    Dim arrHeader() As String    Dim arrColumns() As Variant    Dim arrColumn() As Variant    Dim arrTable() As Variant    Dim j As Long    Dim i As Long    Dim lngMaxRowIdx As Long    Dim lngMaxColIdx As Long    Dim lngPrevIdx As Long    Dim lngFoundIdx As Long    Dim arrProperty() As String    Dim strPropName As String    Dim strPropValue As String        strCnt = Split(strJsonString, "[{")(1)    strCnt = Split(strCnt, "}]")(0)        strMarkerQuot = Mid(CreateObject("Scriptlet.TypeLib").GUID, 2, 36)    strCnt = Replace(strCnt, "\\", "\")    strCnt = Replace(strCnt, "\""", strMarkerQuot)    strCnt = Replace(strCnt, "\/", "/")    strCnt = Replace(strCnt, "\b", Chr(8))    strCnt = Replace(strCnt, "\f", Chr(12))    strCnt = Replace(strCnt, "\n", vbLf)    strCnt = Replace(strCnt, "\r", vbCr)    strCnt = Replace(strCnt, "\t", vbTab)        arrUnicode = Split(strCnt, "\u")    For i = 1 To UBound(arrUnicode)        arrUnicode(i) = ChrW(CLng("&H" & Left(arrUnicode(i), 4))) & Mid(arrUnicode(i), 5)    Next    strCnt = Join(arrUnicode, "")        arrQuots = Split(strCnt, """")    ReDim arrTokens(UBound(arrQuots) \ 2)    For i = 1 To UBound(arrQuots) Step 2        arrTokens(i \ 2) = Replace(arrQuots(i), strMarkerQuot, """")        arrQuots(i) = "%" & i \ 2    Next        strCnt = Join(arrQuots, "")    strCnt = Replace(strCnt, " ", "")        arrRows = Split(strCnt, "},{")    lngMaxRowIdx = UBound(arrRows)    For j = 0 To lngMaxRowIdx        lngPrevIdx = -1        arrProps = Split(arrRows(j), ",")        For i = 0 To UBound(arrProps)            arrProperty = Split(arrProps(i), ":")            strPropName = arrProperty(0)            If Left(strPropName, 1) = "%" Then strPropName = arrTokens(Mid(strPropName, 2))            lngFoundIdx = GetArrayItemIndex(arrHeader, strPropName)            If lngFoundIdx = -1 Then                ReDim arrColumn(lngMaxRowIdx)                If lngPrevIdx = -1 Then                    ArrayAddItem arrHeader, strPropName                    lngPrevIdx = UBound(arrHeader)                    ArrayAddItem arrColumns, arrColumn                Else                    lngPrevIdx = lngPrevIdx + 1                    ArrayInsertItem arrHeader, lngPrevIdx, strPropName                    ArrayInsertItem arrColumns, lngPrevIdx, arrColumn                End If            Else                lngPrevIdx = lngFoundIdx            End If            strPropValue = arrProperty(1)            If Left(strPropValue, 1) = "%" Then strPropValue = arrTokens(Mid(strPropValue, 2))            arrColumns(lngPrevIdx)(j) = strPropValue        Next    Next    lngMaxColIdx = UBound(arrHeader)    ReDim arrTable(lngMaxRowIdx + 1, lngMaxColIdx)    For i = 0 To lngMaxColIdx        arrTable(0, i) = arrHeader(i)    Next    For j = 0 To lngMaxRowIdx        For i = 0 To lngMaxColIdx            arrTable(j + 1, i) = arrColumns(i)(j)        Next    Next        ConvertJsonToArray = arrTable    End FunctionSub Output(objSheet As Worksheet, arrCells() As Variant)        With objSheet        .Select        .Range(.Cells(1, 1), Cells(UBound(arrCells, 1) + 1, UBound(arrCells, 2) + 1)).Value = arrCells        .Columns.AutoFit    End With    With ActiveWindow        .SplitColumn = 0        .SplitRow = 1        .FreezePanes = True    End With    End SubFunction GetArrayItemIndex(arrElements, varTest)    For GetArrayItemIndex = 0 To SafeUBound(arrElements)        If arrElements(GetArrayItemIndex) = varTest Then Exit Function    Next    GetArrayItemIndex = -1End FunctionSub ArrayAddItem(arrElements, varElement)    ReDim Preserve arrElements(SafeUBound(arrElements) + 1)    arrElements(UBound(arrElements)) = varElementEnd SubSub ArrayInsertItem(arrElements, lngIndex, varElement)    Dim i As Long    ReDim Preserve arrElements(SafeUBound(arrElements) + 1)    For i = UBound(arrElements) To lngIndex + 1 Step -1        arrElements(i) = arrElements(i - 1)    Next    arrElements(i) = varElementEnd SubFunction SafeUBound(arrTest)    On Error Resume Next    SafeUBound = -1    SafeUBound = UBound(arrTest)End Function

It takes about 5 secs for downolad (approx. 7 MB), 10 secs for processing and 1.5 for output for me. The resulting worksheet contains 23694 rows including table header:

worksheet

Update

Fast jsJsonParser may help to process large amount of data. Check this Douglas Crockford json2.js implementation for VBA


Have you tried calling the web service via the vba-web toolkit (from the same people who made vba-json)? It automatically wraps the JSON result into a data object.

I then created a Function that converts a simple table-like JSON into a 2D array, which I then paste it into a Range.

First, here's the function you can add to your code:

' Converts a simple JSON dictionary into an arrayFunction ConvertSimpleJsonToArray(data As Variant, ParamArray columnDefinitionsArray() As Variant) As Variant    Dim NumRows, NumColumns As Long    NumRows = data.Count    NumColumns = UBound(columnDefinitionsArray) - LBound(columnDefinitionsArray) + 1    Dim ResultArray() As Variant    ReDim ResultArray(0 To NumRows, 0 To (NumColumns - 1)) 'Rows need an extra header row but columns do not    Dim x, y As Integer    'Column headers    For y = LBound(columnDefinitionsArray) To UBound(columnDefinitionsArray)        ResultArray(LBound(ResultArray), y) = columnDefinitionsArray(y)    Next    'Data rows    For x = 1 To NumRows        For y = LBound(columnDefinitionsArray) To UBound(columnDefinitionsArray)            ResultArray(x, y) = data(x)(columnDefinitionsArray(y))        Next    Next    ConvertSimpleJsonToArray = ResultArrayEnd Function

Here's how I tried calling your API and populating just 4 columns into Excel:

Sub Auto_Open()    Dim FocusClient As New WebClient    FocusClient.BaseUrl = "https://www.gw2shinies.com/api"    ' Use GetJSON helper to execute simple request and work with response    Dim Resource As String    Dim Response As WebResponse    'Create a Request and get Response    Resource = "json/item/tp"    Set Response = FocusClient.GetJson(Resource)    If Response.StatusCode = WebStatusCode.Ok Then        Dim ResultArray() As Variant        ResultArray = ConvertSimpleJsonToArray(Response.data, "item_id", "name", "type", "subtype")        Dim NumRows, NumColumns As Long        NumRows = UBound(ResultArray) - LBound(ResultArray) + 1        NumColumns = UBound(ResultArray, 2) - LBound(ResultArray, 2) + 1        ActiveSheet.Range("a1").Resize(NumRows, NumColumns).Value = ResultArray    Else        Debug.Print "Error: " & Response.Content    End IfEnd Sub

Yes it does take a few seconds to run, but that's more likely to the 26000 rows you have. Even loading the raw JSON in Chrome took a few seconds and this has JSON parsing and loading into array on top of it. You can benchmark the code by Debug.Print timestamps after each code block.

I hope that helps!


It is faster to write all of the values at once then to do it cell by cell. Also you may have secondary event firing so disabling events may help with performance. If performance is still poor with the below code the problem is with the performance of JsonConverter.

Dim ItemCount As IntegerDim items() As VariantFunction httpresp(URL As String) As String    Dim x As Object: Set x = CreateObject("MSXML2.XMLHTTP")    x.Open "GET", URL, False    x.send    httpresp = x.responseTextEnd FunctionPrivate Sub btnLoad_Click()    Application.Calculation = xlCalculationManual    Application.ScreenUpdating = False    Application.EnableEvents = False    Dim URL As String: URL = "https://www.gw2shinies.com/api/json/item/tp"    Dim DecJSON As Object: Set DecJSON = JsonConverter.ParseJson(httpresp(URL))    ItemCount = DecJSON.Count    ReDim items(1 To ItemCount, 1 To 1)    Range("A2:S25000").Clear                'clear range    Dim test As Variant    For i = 1 To ItemCount        items(i, 1) = DecJSON(i)("item_id")        'Cells(i + 1, 1).Value = DecJSON(i)("item_id")    Next i    Range(Range("A2"), Range("A2").Offset(ItemCount)).Value = items    Application.ScreenUpdating = True    Application.Calculation = xlCalculationAutomatic    Application.EnableEvents = TrueEnd Sub