Results 1 to 9 of 9

Thread: JSON Parser

  1. #1
    Junior Member
    Join Date
    Jul 11
    Posts
    24

    JSON Parser

    JSON coder/decoder.
    You must to patch Visual Basic 6 with Service Pack 6 before compiling with this TLB!

    The ToJSON function can parse data with all kinds of errors, but parsing the JSON comments is not implemented.
    The JSON comments are not part of the standard and slow down the parsing.

    Formatted output:
    Code:
    [
      {},
      [
       {},
       []
      ],
      1,
      [
       4,
       5.1,
       {
        "a":1,
        "b":2
       }
      ],
      "3\u000B"
    ]
    C-like empty items parsing (last empty is ignored):
    Code:
    []            []
    [,]           [null]
    [,,]          [null,null]
    [null]        [null]
    [null,null]   [null,null]
    [,1]          [null,1]
    [,,2]         [null,null,2]
    [1,]          [1]
    [2,,]         [2,null]
    Skip whitespace function released as:
    VB Code:
    1. Private Const WhiteSpace = " " & vbTab & vbCrLf
    2. Private Function SkipWhiteSpace() As Integer
    3.   i = i + (CharsCountPtrStr(i, WhiteSpace) * 2)
    4.   SkipWhiteSpace = MemInt(i)
    5. End Function

    Declared through Type Library functions:
    Code:
    CharsCountPtrStr = shlwapi.StrSpnW
    MemInt = msvbvm60.GetMem2
    FindCharsPtrStr = shlwapi.StrPBrkW
    AllocString = oleaut32.SysAllocStringLen
    Also in the project you can find:
    Type library with sources (‎Usable VB and Windows API declarations are added during the programming from December 1, 2011)
    Function to detect is file exists
    Functions for reading and writing the whole binary and text files
    Functions for reading and writing the file times
    Function to detect is current thread active (IsAppActive)
    Function to get window class

    Rate and comment please!
    Attached Files Attached Files
    Last edited by Filyus; Jul 7th, 2012 at 05:49 AM.

  2. #2
    Frenzied Member Bonnie West's Avatar
    Join Date
    Jun 12
    Location
    InIDE
    Posts
    1,602

    Re: JSON Parser

    Excellent Type Library! Thanks!

  3. #3
    Junior Member
    Join Date
    Jul 11
    Posts
    24

    Re: JSON Parser

    Updated: Fixed null keys and object keys processing in the LoadDefault, ToJSON and ToJSONFormatted functions.

    Bonnie West, thank you for the comment!

  4. #4
    Junior Member
    Join Date
    May 11
    Posts
    18

    Re: JSON Parser

    Here is a much simpler JSON parser, less than 270 lines of code.
    VB Code:
    1. Option Explicit
    2.  
    3. Public Function JsonParse(sText As String, lPos As Long, vResult As Variant, Optional Error As String) As Boolean
    4.     Dim vToken          As Variant
    5.     Dim sName           As String
    6.     Dim vValue          As Variant
    7.     Dim lIdx            As Long
    8.    
    9.     On Error GoTo EH
    10.     Error = vbNullString
    11.     vToken = pvJsonGetToken(sText, lPos)
    12.     If VarType(vToken) = vbString Then
    13.         Select Case Left$(vToken, 1)
    14.         Case "{"
    15.             Set vResult = CreateObject("Scripting.Dictionary")
    16.             vResult.CompareMode = 1 ' TextCompare
    17.             Do
    18.                 vToken = pvJsonGetToken(sText, lPos)
    19.                 If VarType(vToken) <> vbString Then
    20.                     GoTo InvalidToken
    21.                 ElseIf vToken = "}" Then
    22.                     Exit Do
    23.                 ElseIf Left$(vToken, 1) <> """" Or Len(vToken) < 2 Then
    24.                     Error = "Expected name at position " & lPos - 1
    25.                     GoTo QH
    26.                 End If
    27.                 sName = Mid$(vToken, 2, Len(vToken) - 2)
    28.                 vToken = pvJsonGetToken(sText, lPos)
    29.                 If VarType(vToken) <> vbString Then
    30.                     GoTo InvalidToken
    31.                 ElseIf vToken <> ":" Then
    32.                     Error = "Expected ':' at position " & lPos - 1
    33.                     GoTo QH
    34.                 End If
    35.                 If Not JsonParse(sText, lPos, vValue, Error) Then
    36.                     GoTo QH
    37.                 End If
    38.                 vResult.Add sName, vValue
    39.                 vToken = pvJsonGetToken(sText, lPos)
    40.                 If VarType(vToken) <> vbString Then
    41.                     GoTo InvalidToken
    42.                 End If
    43.                 Select Case vToken
    44.                 Case "}"
    45.                     Exit Do
    46.                 Case ","
    47.                 Case Else
    48.                     GoTo InvalidToken
    49.                 End Select
    50.             Loop
    51.         Case "["
    52.             Set vResult = CreateObject("Scripting.Dictionary")
    53.             vResult.CompareMode = 0 ' BinaryCompare
    54.             '--- peek next token and check for empty array
    55.             lIdx = lPos
    56.             vToken = pvJsonGetToken(sText, lPos)
    57.             If VarType(vToken) = vbString Then
    58.                 If vToken = "]" Then
    59.                     '--- success
    60.                     JsonParse = True
    61.                     GoTo QH
    62.                 End If
    63.             End If
    64.             lPos = lIdx
    65.             '--- non-empty array
    66.             For lIdx = 0 To &H7FFFFFFF
    67.                 If Not JsonParse(sText, lPos, vValue, Error) Then
    68.                     GoTo QH
    69.                 End If
    70.                 vResult.Add lIdx, vValue
    71.                 vToken = pvJsonGetToken(sText, lPos)
    72.                 If VarType(vToken) <> vbString Then
    73.                     GoTo InvalidToken
    74.                 End If
    75.                 Select Case vToken
    76.                 Case "]"
    77.                     Exit For
    78.                 Case ","
    79.                 Case Else
    80.                     GoTo InvalidToken
    81.                 End Select
    82.             Next
    83.         Case """"
    84.             If Len(vToken) < 2 Then
    85.                 GoTo InvalidToken
    86.             End If
    87.             vResult = Mid$(vToken, 2, Len(vToken) - 2)
    88.         Case Else
    89.             GoTo InvalidToken
    90.         End Select
    91.     Else
    92.         vResult = vToken
    93.     End If
    94.     '--- success
    95.     JsonParse = True
    96. QH:
    97.     Exit Function
    98. InvalidToken:
    99.     Error = "Invalid token " & Switch(VarType(vToken) = vbEmpty, "Empty", VarType(vToken) = vbNull, "Null", _
    100.         VarType(vToken) = vbString, "'" & vToken & "'", True, vToken & "") & " at position " & lPos - 1
    101.     Exit Function
    102. EH:
    103.     Debug.Print Error
    104.     Resume Next
    105. End Function
    106.  
    107. Private Function pvJsonGetToken(sText As String, lPos As Long) As Variant
    108.     Dim sChar           As String
    109.    
    110.     On Error GoTo EH
    111.     '--- skip white-space
    112.     Do
    113.         sChar = Mid$(sText, lPos, 1)
    114.         lPos = lPos + 1
    115.         Select Case sChar
    116.         Case " ", vbTab, vbCr, vbLf
    117.         Case Else
    118.             Exit Do
    119.         End Select
    120.     Loop
    121.     Select Case LCase$(sChar)
    122.     Case vbNullString
    123.         '--- return empty
    124.         GoTo QH
    125.     Case "t"
    126.         If "rue" = LCase$(Mid$(sText, lPos, 3)) Then
    127.             lPos = lPos + 3
    128.             pvJsonGetToken = True
    129.             GoTo QH
    130.         End If
    131.     Case "f"
    132.         If "alse" = LCase$(Mid$(sText, lPos, 4)) Then
    133.             lPos = lPos + 4
    134.             pvJsonGetToken = False
    135.             GoTo QH
    136.         End If
    137.     Case "n"
    138.         If "ull" = LCase$(Mid$(sText, lPos, 3)) Then
    139.             lPos = lPos + 3
    140.             pvJsonGetToken = Null
    141.             GoTo QH
    142.         End If
    143.     Case """"
    144.         pvJsonGetToken = sChar
    145.         Do
    146.             sChar = Mid$(sText, lPos, 1)
    147.             lPos = lPos + 1
    148.             Select Case sChar
    149.             Case "\"
    150.                 sChar = Mid$(sText, lPos, 1)
    151.                 lPos = lPos + 1
    152.                 Select Case sChar
    153.                 Case "b"
    154.                     pvJsonGetToken = pvJsonGetToken & Chr$(8)
    155.                 Case "f"
    156.                     pvJsonGetToken = pvJsonGetToken & Chr$(12)
    157.                 Case "n"
    158.                     pvJsonGetToken = pvJsonGetToken & vbLf
    159.                 Case "r"
    160.                     pvJsonGetToken = pvJsonGetToken & vbCr
    161.                 Case "t"
    162.                     pvJsonGetToken = pvJsonGetToken & vbTab
    163.                 Case "u"
    164.                     pvJsonGetToken = pvJsonGetToken & ChrW$(CLng("&H" & Mid$(sText, lPos, 4)))
    165.                     lPos = lPos + 4
    166.                 Case Else ' "\", "'", """"
    167.                     pvJsonGetToken = pvJsonGetToken & sChar
    168.                 End Select
    169.             Case """", vbNullString
    170.                 pvJsonGetToken = pvJsonGetToken & sChar
    171.                 Exit Do
    172.             Case Else
    173.                 pvJsonGetToken = pvJsonGetToken & sChar
    174.             End Select
    175.         Loop
    176.         GoTo QH
    177.     Case Else
    178.         If sChar Like "[0-9.+-]" Then '
    179.             pvJsonGetToken = sChar
    180.             Do
    181.                 sChar = Mid$(sText, lPos, 1)
    182.                 If sChar Like "[0-9eExXa-fA-F.+-]" Then
    183.                     lPos = lPos + 1
    184.                     pvJsonGetToken = pvJsonGetToken & sChar
    185.                 Else
    186.                     Exit Do
    187.                 End If
    188.             Loop
    189.             If LCase$(Left$(pvJsonGetToken, 2)) = "0x" Then
    190.                 pvJsonGetToken = "&H" & Mid$(pvJsonGetToken, 3)
    191.             End If
    192.             On Error Resume Next
    193.             pvJsonGetToken = CDec(pvJsonGetToken)
    194.             pvJsonGetToken = CDbl(pvJsonGetToken)
    195.             On Error GoTo EH
    196.             GoTo QH
    197.         End If
    198.     End Select
    199.     pvJsonGetToken = sChar
    200. QH:
    201.     Exit Function
    202. EH:
    203.     Debug.Print Error
    204.     Resume Next
    205. End Function
    206.  
    207. Public Function JsonDump(vJson As Variant, Optional ByVal lLevel As Long) As String
    208.     Const STR_CODES     As String = "\u0000|\u0001|\u0002|\u0003|\u0004|\u0005|\u0006|\u0007|\b|\t|\n|\u000B|\f|\r|\u000E|\u000F|\u0010|\u0011|\u0012|\u0013|\u0014|\u0015|\u0016|\u0017|\u0018|\u0019|\u001A|\u001B|\u001C|\u001D|\u001E|\u001F"
    209.     Const INDENT        As Long = 4
    210.     Static vTranscode   As Variant
    211.     Dim vKeys           As Variant
    212.     Dim vItems          As Variant
    213.     Dim lIdx            As Long
    214.     Dim lSize           As Long
    215.     Dim sCompound       As String
    216.     Dim lAsc            As Long
    217.    
    218.     On Error GoTo EH
    219.     Select Case VarType(vJson)
    220.     Case vbObject
    221.         sCompound = IIf(vJson.CompareMode = 0, "[]", "{}")
    222.         If vJson.Count = 0 Then
    223.             JsonDump = sCompound
    224.         Else
    225.             vKeys = vJson.Keys
    226.             vItems = vJson.Items
    227.             For lIdx = 0 To vJson.Count - 1
    228.                 vItems(lIdx) = JsonDump(vItems(lIdx), lLevel + 1)
    229.                 If VarType(vKeys(lIdx)) = vbString Then
    230.                     vItems(lIdx) = JsonDump(vKeys(lIdx)) & ": " & vItems(lIdx)
    231.                 End If
    232.                 lSize = lSize + Len(vItems(lIdx))
    233.             Next
    234.             If lSize > 100 Then
    235.                 JsonDump = Left$(sCompound, 1) & vbCrLf & _
    236.                     Space((lLevel + 1) * INDENT) & Join(vItems, "," & vbCrLf & Space((lLevel + 1) * INDENT)) & vbCrLf & _
    237.                     Space(lLevel * INDENT) & Right$(sCompound, 1)
    238.             Else
    239.                 JsonDump = Left$(sCompound, 1) & " " & Join(vItems, ", ") & " " & Right$(sCompound, 1)
    240.             End If
    241.         End If
    242.     Case vbNull
    243.         JsonDump = "Null"
    244.     Case vbEmpty
    245.         JsonDump = "Empty"
    246.     Case vbString
    247.         '--- one-time initialization of transcoding array
    248.         If IsEmpty(vTranscode) Then
    249.             vTranscode = Split(STR_CODES, "|")
    250.         End If
    251.         For lIdx = 1 To Len(vJson)
    252.             lAsc = AscW(Mid$(vJson, lIdx, 1))
    253.             If lAsc = 92 Or lAsc = 34 Then '--- \ and "
    254.                 JsonDump = JsonDump & "\" & Chr$(lAsc)
    255.             ElseIf lAsc >= 32 And lAsc < 256 Then
    256.                 JsonDump = JsonDump & Chr$(lAsc)
    257.             ElseIf lAsc >= 0 And lAsc < 32 Then
    258.                 JsonDump = JsonDump & vTranscode(lAsc)
    259.             ElseIf Asc(Mid$(vJson, lIdx, 1)) <> 63 Then '--- ?
    260.                 JsonDump = JsonDump & Chr$(Asc(Mid$(vJson, lIdx, 1)))
    261.             Else
    262.                 JsonDump = JsonDump & "\u" & Right$("0000" & Hex(lAsc), 4)
    263.             End If
    264.         Next
    265.         JsonDump = """" & JsonDump & """"
    266.     Case Else
    267.         JsonDump = vJson & ""
    268.     End Select
    269.     Exit Function
    270. EH:
    271.     Debug.Print Error
    272.     Resume Next
    273. End Function

    Function JsonParse parses JSON objects and JSON arrays to Scripting.Dictionary objects. Object properties can be accessed with oResult!myproperty, array elements with oResult!myarray(0).

    Function JsonDump in reverse converts Scripting.Dictionary objects to valid JSON string.

    cheers,
    </wqw>
    Last edited by wqweto; Jul 11th, 2012 at 03:15 AM.

  5. #5
    Junior Member
    Join Date
    Jul 11
    Posts
    24

    Re: JSON Parser

    wqweto, your code is not only much more slowly, but even bigger than mine.

    Source code length (bytes):
    Code:
    JsonParse 9437
    FromJSON 9156 - without additional functions and comments
    FromJSON 18005 - with additional functions and comments
    Speed test. And this is on the Intel i5-2500K!
    ms, bytes.

    In the IDE:
    Code:
    String Length:  62576 
     0  FromJSON
     78  JsonParse
    
    String Length: 71134 
     16  FromJSON
     78  JsonParse
    
    String Length: 93189 
     0  FromJSON
     94  JsonParse
    
    String Length:  102243 
     15  FromJSON
     94  JsonParse
    
    String Length: 168061 
     16  FromJSON
     280  JsonParse
    Compiled:
    Code:
    String Length: 19725
     0  FromJSON
     31  JsonParse
    
    String Length: 72010
     16  FromJSON
     125  JsonParse
    
    String Length: 287445
     16  FromJSON
     484  JsonParse
    
    String Length: 344385
     16  FromJSON
     546  JsonParse
    Last edited by Filyus; Jul 5th, 2012 at 09:38 AM.

  6. #6
    Junior Member
    Join Date
    May 11
    Posts
    18

    Re: JSON Parser

    Yes, your code is clearly very well tuned. I'm not using StrPBrkW or any API calls at all, nor are any additional project references needed.

    My primary goal was to "make it simple" -- a single (or two) self-containing functions anyone can paste in his project. Simple to use too -- something like parsedObject!printers(0)!capabilities!width to traverse nested arrays/objects. I'll see what can be tweaked performance wise without bloating the code.

    Btw, why are you comparing JsonParse with ToJSON? JsonParse is equivalent to FromJSON, JsonDump is equivalent to ToJSON. Why are you including JsonDump in LOC measurement? It's a simple 65 lines of code dumping function that is not related to parsing.

    Sorry, for hijacking your theme. The typelib looks very good, lots of clever hacks (can't fathom how CallProc5 does not corrupt the stack though).

    cheers,
    </wqw>
    Last edited by wqweto; Jul 5th, 2012 at 08:59 AM.

  7. #7
    Junior Member
    Join Date
    Jul 11
    Posts
    24

    Re: JSON Parser

    Quote Originally Posted by wqweto View Post
    Code:
            Case "{"
                Set vResult = CreateObject("Scripting.Dictionary")
                vResult.CompareMode = 1 ' TextCompare
    In the JSON-RPC documentation:
    Conforming implementations MUST treat procedure and parameter names as being case-sensitive such the names bar and BAR would be seen as two distinct entities.
    So I think there must be BinaryCompare.

    Quote Originally Posted by wqweto View Post
    Yes, your code is clearly very well tuned. I'm not using StrPBrkW or any API calls at all, nor are any additional project references needed.

    Btw, why are you comparing JsonParse with ToJSON? JsonParse is equivalent to FromJSON, JsonDump is equivalent to ToJSON. Why are you including JsonDump in LOC measurement? It's a simple 65 lines of code dumping function that is not related to parsing.

    Sorry, for hijacking your theme. The typelib looks very good, lots of clever hacks (can't fathom how CallProc5 does not corrupt the stack though).
    I'm confused with the functions names. I compared the FromJSON and JsonParse functions.

    CallProc*(ProcAddres,*) works only with replaced function msvbvm60.rtcCallByName by this:
    Code:
       pop eax  ;pop return_address
       pop ecx  ;pop proc_address
       push eax ;push return_address
       jmp ecx ;jmp proc_address
    Quote Originally Posted by wqweto View Post
    My primary goal was to "make it simple" -- a single (or two) self-containing functions anyone can paste in his project. Simple to use too -- something like parsedObject!printers(0)!capabilities!width to traverse nested arrays/objects. I'll see what can be tweaked performance wise without bloating the code.
    This is awesome construction with default string params! I didn't know about it ten years of my programming in the VB 6.0...
    My code can work with "parsedObject!printers(0)!capabilities!width" like expressions too, and I WILL NOT change my code because adding item to the collection is much faster than it is for the dictionary:
    Code:
      Set c = New Collection
      Set d = New Dictionary
      t = Tick
      For i = 1 To 100000
        c.Add i '16(100000) 47(500000) 109(1000000)
        'd.Add i, i '140(100000) 5242(500000) 20794(1000000)
      Next i
      Debug.Print Tick - t
      t = Tick
      'For i = 1 To 100000
      'For Each v In d '16(500000) 32(1000000)
      For Each v In c '15(500000) 32(1000000)
        'n = c.Item(i) '12761(100000)
        'n = d(i) '125(100000)
      Next
      Debug.Print Tick - t
    Code:
      Set c = New Collection
      Set d = New Dictionary
      For i = 1 To 20
        'c.Add i
        d.Add i, i
      Next i
      t = Tick
      For i = 1 To 5000000
        'n = c(5) '109(1'000'000) 546(5'000'000)
        n = d(5) '78(1'000'000) 406(5'000'000)
      Next i
      Debug.Print Tick - t
    Last edited by Filyus; Jul 5th, 2012 at 12:04 PM.

  8. #8
    Junior Member
    Join Date
    May 11
    Posts
    18

    Re: JSON Parser

    Yes, Dictionary is slower that Collection *and* uses much more memory.

    For instance this large json file can not be parsed with dictionaries because parsed object uses more that 2GB or RAM.

    Same file with collections uses 287MB of RAM.

    I did some research on my sample and the performance penalty turned out to be with late-bound calls on dictionaries (adding entries). Early-bound version is about 20% slower (not 20 times) than yours.

    Here is the modified version
    VB Code:
    1. Option Explicit
    2.  
    3. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    4.  
    5. Private Type JsonContext
    6.     Text()              As Integer
    7.     Pos                 As Long
    8.     Error               As String
    9.     LastChar            As Integer
    10. End Type
    11.  
    12. Public Function JsonParse(sText As String, vResult As Variant, Optional Error As String) As Boolean
    13.     Dim uCtx            As JsonContext
    14.     Dim oResult         As Object
    15.    
    16.     With uCtx
    17.         ReDim .Text(0 To Len(sText)) As Integer
    18.         Call CopyMemory(.Text(0), ByVal StrPtr(sText), LenB(sText))
    19.         JsonParse = pvJsonParse(uCtx, vResult, oResult)
    20.         If Not oResult Is Nothing Then
    21.             Set vResult = oResult
    22.         End If
    23.         Error = .Error
    24.     End With
    25. End Function
    26.  
    27. Private Function pvJsonMissing(Optional vMissing As Variant) As Variant
    28.     pvJsonMissing = vMissing
    29. End Function
    30.  
    31. Private Function pvJsonParse(uCtx As JsonContext, vResult As Variant, oResult As Dictionary) As Boolean
    32.     '--- note: when using collections change type of parameter oResult to Collection
    33.     #Const USE_COLLECTION = False
    34.     Dim lIdx            As Long
    35.     Dim vKey            As Variant
    36.     Dim vValue          As Variant
    37.     Dim oValue          As Object
    38.     Dim sText           As String
    39.    
    40.     vValue = pvJsonMissing
    41.     With uCtx
    42.         Select Case pvJsonGetChar(uCtx)
    43.         Case 34 ' "
    44.             vResult = pvJsonGetString(uCtx)
    45.         Case 91 ' [
    46.             #If USE_COLLECTION Then
    47.                 Set oResult = New Collection
    48.             #Else
    49.                 Set oResult = CreateObject("Scripting.Dictionary")
    50.             #End If
    51.             Do
    52.                 Select Case pvJsonGetChar(uCtx)
    53.                 Case 0, 44, 93 ' , ]
    54.                     If Not oValue Is Nothing Then
    55.                         #If USE_COLLECTION Then
    56.                             oResult.Add oValue
    57.                         #Else
    58.                             oResult.Add lIdx, oValue
    59.                         #End If
    60.                     ElseIf Not IsMissing(vValue) Then
    61.                         #If USE_COLLECTION Then
    62.                             oResult.Add vValue
    63.                         #Else
    64.                             oResult.Add lIdx, vValue
    65.                         #End If
    66.                     End If
    67.                     If .LastChar <> 44 Then ' ,
    68.                         Exit Do
    69.                     End If
    70.                     lIdx = lIdx + 1
    71.                     vValue = pvJsonMissing
    72.                     Set oValue = Nothing
    73.                 Case Else
    74.                     .Pos = .Pos - 1
    75.                     If Not pvJsonParse(uCtx, vValue, oValue) Then
    76.                         GoTo QH
    77.                     End If
    78.                 End Select
    79.             Loop
    80.         Case 123 ' {
    81.             #If USE_COLLECTION Then
    82.                 Set oResult = New Collection
    83.             #Else
    84.                 Set oResult = CreateObject("Scripting.Dictionary")
    85.                 oResult.CompareMode = 1 ' TextCompare
    86.             #End If
    87.             Do
    88.                 Select Case pvJsonGetChar(uCtx)
    89.                 Case 34 ' "
    90.                     vKey = pvJsonGetString(uCtx)
    91.                 Case 58 ' :
    92.                     If Not oValue Is Nothing Then
    93.                         .Error = "Value already specified at position " & .Pos
    94.                         GoTo QH
    95.                     ElseIf Not IsMissing(vValue) Then
    96.                         vKey = vValue
    97.                         vValue = pvJsonMissing
    98.                     End If
    99.                     lIdx = .Pos
    100.                     If Not pvJsonParse(uCtx, vValue, oValue) Then
    101.                         .Pos = lIdx
    102.                         vValue = Empty
    103.                         Set oValue = Nothing
    104.                     End If
    105.                 Case 0, 44, 125 ' , }
    106.                     If IsMissing(vValue) And oValue Is Nothing Then
    107.                         If IsEmpty(vKey) Then
    108.                             GoTo NoProp
    109.                         End If
    110.                         vValue = vKey
    111.                         vKey = vbNullString
    112.                     End If
    113.                     If IsEmpty(vKey) Then
    114.                         vKey = vbNullString
    115.                     ElseIf IsNull(vKey) Then
    116.                         vKey = "null"
    117.                     End If
    118.                     If Not oValue Is Nothing Then
    119.                         #If USE_COLLECTION Then
    120.                             oResult.Add oValue, vKey & ""
    121.                         #Else
    122.                             oResult.Add vKey & "", oValue
    123.                         #End If
    124.                     Else
    125.                         #If USE_COLLECTION Then
    126.                             oResult.Add vValue, vKey & ""
    127.                         #Else
    128.                             oResult.Add vKey & "", vValue
    129.                         #End If
    130.                     End If
    131. NoProp:
    132.                     If .LastChar <> 44 Then ' ,
    133.                         Exit Do
    134.                     End If
    135.                     vKey = Empty
    136.                     vValue = pvJsonMissing
    137.                     Set oValue = Nothing
    138.                 Case Else
    139.                     .Pos = .Pos - 1
    140.                     If Not pvJsonParse(uCtx, vValue, oValue) Then
    141.                         GoTo QH
    142.                     End If
    143.                 End Select
    144.             Loop
    145.         Case 116, 84  ' "t", "T"
    146.             If Not ((.Text(.Pos + 0) Or &H20) = 114 And (.Text(.Pos + 1) Or &H20) = 117 And (.Text(.Pos + 2) Or &H20) = 101) Then
    147.                 GoTo UnexpectedSymbol
    148.             End If
    149.             .Pos = .Pos + 3
    150.             vResult = True
    151.         Case 102, 70 ' "f", "F"
    152.             If Not ((.Text(.Pos + 0) Or &H20) = 97 And (.Text(.Pos + 1) Or &H20) = 108 And (.Text(.Pos + 2) Or &H20) = 115 And (.Text(.Pos + 3) Or &H20) = 101) Then
    153.                 GoTo UnexpectedSymbol
    154.             End If
    155.             .Pos = .Pos + 4
    156.             vResult = False
    157.         Case 110, 78 ' "n", "N"
    158.             If Not ((.Text(.Pos + 0) Or &H20) = 117 And (.Text(.Pos + 1) Or &H20) = 108 And (.Text(.Pos + 2) Or &H20) = 108) Then
    159.                 GoTo UnexpectedSymbol
    160.             End If
    161.             .Pos = .Pos + 3
    162.             vResult = Null
    163.         Case 48 To 57, 43, 45, 46 ' 0-9 + - .
    164.             For lIdx = 0 To 1000
    165.                 Select Case .Text(.Pos + lIdx)
    166.                 Case 48 To 57, 43, 45, 46, 101, 69, 120, 88, 97 To 102, 65 To 70 ' 0-9 + - . e E x X a-f A-F
    167.                 Case Else
    168.                     Exit For
    169.                 End Select
    170.             Next
    171.             sText = Space(lIdx + 1)
    172.             Call CopyMemory(ByVal StrPtr(sText), .Text(.Pos - 1), LenB(sText))
    173.             If LCase$(Left$(sText, 2)) = "0x" Then
    174.                 sText = "&H" & Mid$(sText, 3)
    175.             End If
    176.             On Error GoTo EH
    177.             vResult = CDbl(sText)
    178.             On Error GoTo 0
    179.             .Pos = .Pos + lIdx
    180.         Case 0
    181.             '--- return empty
    182.         Case Else
    183.             GoTo UnexpectedSymbol
    184.         End Select
    185.         pvJsonParse = True
    186. QH:
    187.         Exit Function
    188. UnexpectedSymbol:
    189.         .Error = "Unexpected symbol '" & ChrW$(.LastChar) & "' at position " & .Pos
    190.         Exit Function
    191. EH:
    192.         .Error = Error$ & " at position " & .Pos
    193.     End With
    194. End Function
    195.  
    196. Private Function pvJsonGetChar(uCtx As JsonContext) As Integer
    197.     With uCtx
    198.         If .Pos <= UBound(.Text) Then
    199. SkipWhitespace:
    200.             .LastChar = .Text(.Pos)
    201.             .Pos = .Pos + 1
    202.             Select Case .LastChar
    203.             Case 32, 9, 10, 13 ' " ", vbTab, vbCr, vbLf
    204.                 GoTo SkipWhitespace
    205.             Case 0
    206.                 Exit Function
    207.             Case Else
    208.                 pvJsonGetChar = .LastChar
    209.             End Select
    210.         End If
    211.     End With
    212. End Function
    213.  
    214. Private Function pvJsonGetString(uCtx As JsonContext) As String
    215.     Dim lIdx            As Long
    216.     Dim nChar           As Integer
    217.     Dim sText           As String
    218.    
    219.     With uCtx
    220.         For lIdx = 0 To &H7FFFFFFF
    221.             nChar = .Text(.Pos + lIdx)
    222.             Select Case nChar
    223.             Case 0, 34, 92 ' " \
    224.                 sText = Space(lIdx)
    225.                 Call CopyMemory(ByVal StrPtr(sText), .Text(.Pos), LenB(sText))
    226.                 pvJsonGetString = pvJsonGetString & sText
    227.                 If nChar <> 92 Then ' \
    228.                     .Pos = .Pos + lIdx + 1
    229.                     Exit For
    230.                 End If
    231.                 lIdx = lIdx + 1
    232.                 nChar = .Text(.Pos + lIdx)
    233.                 Select Case nChar
    234.                 Case 0
    235.                     Exit For
    236.                 Case 98  ' b
    237.                     pvJsonGetString = pvJsonGetString & Chr$(8)
    238.                 Case 102 ' f
    239.                     pvJsonGetString = pvJsonGetString & Chr$(12)
    240.                 Case 110 ' n
    241.                     pvJsonGetString = pvJsonGetString & vbLf
    242.                 Case 114 ' r
    243.                     pvJsonGetString = pvJsonGetString & vbCr
    244.                 Case 116 ' t
    245.                     pvJsonGetString = pvJsonGetString & vbTab
    246.                 Case 117 ' u
    247.                     pvJsonGetString = pvJsonGetString & ChrW$(CLng("&H" & ChrW$(.Text(.Pos + lIdx + 1)) & ChrW$(.Text(.Pos + lIdx + 2)) & ChrW$(.Text(.Pos + lIdx + 3)) & ChrW$(.Text(.Pos + lIdx + 4))))
    248.                     lIdx = lIdx + 4
    249.                 Case 120 ' x
    250.                     pvJsonGetString = pvJsonGetString & ChrW$(CLng("&H" & ChrW$(.Text(.Pos + lIdx + 1)) & ChrW$(.Text(.Pos + lIdx + 2))))
    251.                     lIdx = lIdx + 2
    252.                 Case Else
    253.                     pvJsonGetString = pvJsonGetString & ChrW$(nChar)
    254.                 End Select
    255.                 .Pos = .Pos + lIdx + 1
    256.                 lIdx = -1
    257.             End Select
    258.         Next
    259.     End With
    260. End Function
    261.  
    262. Public Function JsonDump(vJson As Variant, Optional ByVal lLevel As Long) As String
    263.     Const STR_CODES     As String = "\u0000|\u0001|\u0002|\u0003|\u0004|\u0005|\u0006|\u0007|\b|\t|\n|\u000B|\f|\r|\u000E|\u000F|\u0010|\u0011|\u0012|\u0013|\u0014|\u0015|\u0016|\u0017|\u0018|\u0019|\u001A|\u001B|\u001C|\u001D|\u001E|\u001F"
    264.     Const INDENT        As Long = 4
    265.     Static vTranscode   As Variant
    266.     Dim vKeys           As Variant
    267.     Dim vItems          As Variant
    268.     Dim lIdx            As Long
    269.     Dim lSize           As Long
    270.     Dim sCompound       As String
    271.     Dim lAsc            As Long
    272.    
    273.     On Error GoTo EH
    274.     Select Case VarType(vJson)
    275.     Case vbObject
    276.         sCompound = IIf(vJson.CompareMode = 0, "[]", "{}")
    277.         If vJson.Count = 0 Then
    278.             JsonDump = sCompound
    279.         Else
    280.             vKeys = vJson.Keys
    281.             vItems = vJson.Items
    282.             For lIdx = 0 To vJson.Count - 1
    283.                 vItems(lIdx) = JsonDump(vItems(lIdx), lLevel + 1)
    284.                 If VarType(vKeys(lIdx)) = vbString Then
    285.                     vItems(lIdx) = JsonDump(vKeys(lIdx)) & ": " & vItems(lIdx)
    286.                 End If
    287.                 lSize = lSize + Len(vItems(lIdx))
    288.             Next
    289.             If lSize > 100 Then
    290.                 JsonDump = Left$(sCompound, 1) & vbCrLf & _
    291.                     Space((lLevel + 1) * INDENT) & Join(vItems, "," & vbCrLf & Space((lLevel + 1) * INDENT)) & vbCrLf & _
    292.                     Space(lLevel * INDENT) & Right$(sCompound, 1)
    293.             Else
    294.                 JsonDump = Left$(sCompound, 1) & " " & Join(vItems, ", ") & " " & Right$(sCompound, 1)
    295.             End If
    296.         End If
    297.     Case vbNull
    298.         JsonDump = "Null"
    299.     Case vbEmpty
    300.         JsonDump = "Empty"
    301.     Case vbString
    302.         '--- one-time initialization of transcoding array
    303.         If IsEmpty(vTranscode) Then
    304.             vTranscode = Split(STR_CODES, "|")
    305.         End If
    306.         For lIdx = 1 To Len(vJson)
    307.             lAsc = AscW(Mid$(vJson, lIdx, 1))
    308.             If lAsc = 92 Or lAsc = 34 Then '--- \ and "
    309.                 JsonDump = JsonDump & "\" & Chr$(lAsc)
    310.             ElseIf lAsc >= 32 And lAsc < 256 Then
    311.                 JsonDump = JsonDump & Chr$(lAsc)
    312.             ElseIf lAsc >= 0 And lAsc < 32 Then
    313.                 JsonDump = JsonDump & vTranscode(lAsc)
    314.             ElseIf Asc(Mid$(vJson, lIdx, 1)) <> 63 Then '--- ?
    315.                 JsonDump = JsonDump & Chr$(Asc(Mid$(vJson, lIdx, 1)))
    316.             Else
    317.                 JsonDump = JsonDump & "\u" & Right$("0000" & Hex(lAsc), 4)
    318.             End If
    319.         Next
    320.         JsonDump = """" & JsonDump & """"
    321.     Case Else
    322.         JsonDump = vJson & ""
    323.     End Select
    324.     Exit Function
    325. EH:
    326.     Debug.Print Error
    327.     Resume Next
    328. End Function
    The only API function used is CopyMemory. Obviously compiled version is much faster with all those arrays iterations.

    cheers,
    </wqw>
    Last edited by wqweto; Jul 11th, 2012 at 03:15 AM.

  9. #9
    Junior Member
    Join Date
    May 11
    Posts
    18

    Re: JSON Parser

    Here are the results of conformity tests:
    Code:
    "": Empty
    "1": 1
    "true": True
    ""hello"": "hello"
    "not a value": Error=Unexpected symbol 'n' at position 1
    "[]": []
    "[1]": [ 1 ]
    "[1.1]": [ 1.1 ]
    "[-1E+4]": [ -10000 ]
    "[100.0e-2]": [ 1 ]
    "[.5]": [ 0.5 ]
    "[5.]": [ 5 ]
    "[.]": Error=Type mismatch at position 2
    "[5..5]": Error=Type mismatch at position 2
    "[10e]": Error=Type mismatch at position 2
    "[e10]": Error=Unexpected symbol 'e' at position 2
    "[010e2]": [ 1000 ]
    "[010.2]": [ 10.2 ]
    "[010]": [ 10 ]
    "[0xFF]": [ 255 ]
    "[0xff]": [ 255 ]
    "[true]": [ True ]
    "[TRUE]": [ True ]
    "[null]": [ Null ]
    "[NULL]": [ Null ]
    "[""]": [ "" ]
    "["a"]": [ "a" ]
    " [ "a" ] ": [ "a" ]
    "[1,]": [ 1 ]
    "[,]": []
    "[,1]": [ 1 ]
    "[1,,1]": [ 1, 1 ]
    "['a']": Error=Unexpected symbol ''' at position 2
    "["a]": [ "a]" ]
    "[a"]": Error=Unexpected symbol 'a' at position 2
    "['a"]": Error=Unexpected symbol ''' at position 2
    "["a']": [ "a']" ]
    "[']": Error=Unexpected symbol ''' at position 2
    "['']": Error=Unexpected symbol ''' at position 2
    "[''']": Error=Unexpected symbol ''' at position 2
    "["]": [ "]" ]
    "["""]": [ "]" ]
    "["'"]": [ "'" ]
    "['"']": Error=Unexpected symbol ''' at position 2
    "["\'"]": [ "'" ]
    "["\\'"]": [ "\\'" ]
    "["\""]": [ "\"" ]
    "["\"]": [ "\"]" ]
    "["\\"]": [ "\\" ]
    "["\\\"]": [ "\\\"]" ]
    " ["a"]": [ "a" ]
    "[ "a"]": [ "a" ]
    "["a "]": [ "a " ]
    "["a" ]": [ "a" ]
    "["a"] ": [ "a" ]
    "["\u0041\u00DC"]": [ "AЬ" ]
    "["\b\t\f\v\r\n"]": [ "\b\t\fv\r\n" ]
    "["\b\t\f\r\n"]": [ "\b\t\f\r\n" ]
    "["\x41\xDC"]": [ "AЬ" ]
    "[ ] [ ]": []
    "["a" "b"]": [ "b" ]
    "[1 2]": [ 2 ]
    "[{}]": [ {} ]
    "[ { } ]": [ {} ]
    "[{1}]": [ { "": 1 } ]
    "[{1:1}]": [ { "1": 1 } ]
    "[{:1}]": [ { "": 1 } ]
    "[{"1":}]": [ { "1": Empty } ]
    "[{"1":1}]": [ { "1": 1 } ]
    "[{"":1}]": [ { "": 1 } ]
    "[{"a":}]": [ { "a": Empty } ]
    "[{"a":1}]": [ { "a": 1 } ]
    "[{"true":1}]": [ { "true": 1 } ]
    "[{true:1}]": [ { "True": 1 } ]
    "[{null:1}]": [ { "null": 1 } ]
    "[{a "a":1}]": Error=Unexpected symbol 'a' at position 3
    "[{"a":1"a"}]": [ { "a": 1 } ]
    "[{a b:"a"}]": Error=Unexpected symbol 'a' at position 3
    "[{a b:a b}]": Error=Unexpected symbol 'a' at position 3
    "[{a 1:a 1}]": Error=Unexpected symbol 'a' at position 3
    "[{a:b:c}]": Error=Unexpected symbol 'a' at position 3
    Comments are not implemented in my version too.

    cheers,
    </wqw>

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •