Platforms to show: All Mac Windows Linux Cross-Platform
Required plugins for this example: MBS Util Plugin
You find this example project in your Plugins Download as a Xojo project file within the examples folder: /Util/JSON/JSONItem clone
This example is the version from Tue, 18th Sep 2023.
Project "JSONItem clone.xojo_binary_project"
Class App Inherits Application
Const kEditClear = "&Delete"
Const kFileQuit = "&Quit"
Const kFileQuitShortcut = ""
End Class
Class Window1 Inherits Window
Const constJSON_Test1 = "[""a"",1,""b"",2, 2.5]"
Const constJSON_Test2 = "{""a"":1, ""b"":2}"
Const constJSON_Test3 = "[\r {\r ""id"":127,\r ""name"":""Meier"",\r ""prename"":""Anna"",\r ""age"":13,\r ""birthdate"":""20071208""\r },\r {\r ""id"":142,\r ""name"":""Meier"",\r ""prename"":""Patrick"",\r ""age"":12,\r ""birthdate"":""20081127""\r }\r ]"
Control TextArea1 Inherits TextArea
ControlInstance TextArea1 Inherits TextArea
End Control
Control btnJSONItem Inherits PushButton
ControlInstance btnJSONItem Inherits PushButton
EventHandler Sub Action()
Dim j As JSONItem
Dim s() As String
s.Append("JSONItem")
s.Append("")
s.Append(constJSON_Test1)
j = New JSONItem(constJSON_Test1)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
s.Append("Value(" + Str(i) + "): " + j.Value(i).StringValue)
next
s.Append("")
s.Append(constJSON_Test2)
j = New JSONItem(constJSON_Test2)
s.Append("IsArray: " + Str(j.IsArray))
s.Append("Count: " + Str(j.Count))
s.Append("a: " + j.Value("a"))
s.Append("b: " + j.Value("b"))
s.Append("HasName(""a""): " + Str(j.HasName("a")))
s.Append("HasName(""A""): " + Str(j.HasName("A")))
s.Append("")
s.Append(constJSON_Test3)
j = New JSONItem(constJSON_Test3)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
Dim k As JSONItem = j.Child(i)
s.Append("Value(" + Str(i) + ").id/prename: " + k.Lookup("id", 0) + "/" + k.Lookup("prename", ""))
next
s.Append("")
s.Append("own new JSONItem with DecimalFormat: -0.0##")
j = New JSONItem
j.DecimalFormat = "-0.0##"
j.Value("1") = 23.67
j.Value("2") = -2.7
j.Value("3") = 12
j.Value("4") = 12345.67891
Dim va() As Variant
va.Append(23.67)
va.Append(-2.7)
va.Append(12)
va.Append(12345.67891)
j.Value("a") = va
s.Append(j.ToString)
s.Append("")
j = New JSONItem
j.Value("Hello") = "World"
j.Value("Integer_1") = 2
j.Value("Double_7.5") = 7.5
j.Value("Double_23.67") = 23.67
j.Value("Integer_Array") = Array(1,3,5,7)
j.Value("Double_Array") = Array(1.5,3.3,987.123456)
s.Append("own New JSONItem, changing DecimalFormat later")
s.Append("Count: " + Str(j.Count))
s.Append("ToString: " + j.ToString)
j.DecimalFormat = "-0.0####"
s.Append("ToString (DecimalFormat: " + j.DecimalFormat + "): " + j.ToString)
j = New JSONItem
j.Compact = false
for i1 As Integer = 1 to 100
Dim j2 As New JSONItem
for i2 As Integer = 1 to 25
if i2 mod 2 = 0 then
j2.Value(Str(i1) + "." + Str(i2)) = "Value " + Str(i2)
else
j2.Value(Str(i2)) = i2
end if
next
j.Value(Str(i1)) = j2
next
Dim d As Double = Microseconds
Dim s2 As String = j.ToString
d = Microseconds - d
s.Append("")
s.Append("2500 JSON objects .ToString: " + Format(d/1000, "####0.0##") + "ms")
s.Append(s2)
TextArea1.Text = Join(s, EndOfLine)
End EventHandler
End Control
Control btnJSONItem_MBS Inherits PushButton
ControlInstance btnJSONItem_MBS Inherits PushButton
EventHandler Sub Action()
Dim j As JSONItem_MBS
Dim s() As String
s.Append("JSONItem_MBS")
s.Append("")
s.Append(constJSON_Test1)
j = New JSONItem_MBS(constJSON_Test1)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
s.Append("Value(" + Str(i) + "): " + j.Value(i).StringValue)
next
s.Append("")
s.Append(constJSON_Test2)
j = New JSONItem_MBS(constJSON_Test2)
s.Append("IsArray: " + Str(j.IsArray))
s.Append("Count: " + Str(j.Count))
s.Append("a: " + j.Value("a"))
s.Append("b: " + j.Value("b"))
s.Append("HasName(""a""): " + Str(j.HasName("a")))
s.Append("HasName(""A""): " + Str(j.HasName("A")))
s.Append("")
s.Append(constJSON_Test3)
j = New JSONItem_MBS(constJSON_Test3)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
Dim k As JSONItem_MBS = j.Child(i)
s.Append("Value(" + Str(i) + ").id/prename: " + k.Lookup("id", 0) + "/" + k.Lookup("prename", ""))
next
s.Append("")
s.Append("own new JSONItem with DecimalFormat: -0.0##")
j = New JSONItem_MBS
j.DecimalFormat = "-0.0##"
j.Value("1") = 23.67
j.Value("2") = -2.7
j.Value("3") = 12
j.Value("4") = 12345.67891
Dim va() As Variant
va.Append(23.67)
va.Append(-2.7)
va.Append(12)
va.Append(12345.67891)
j.Value("a") = va
s.Append(j.ToString)
s.Append("")
j = New JSONItem_MBS
j.Value("Hello") = "World"
j.Value("Integer_1") = 2
j.Value("Double_7.5") = 7.5
j.Value("Double_23.67") = 23.67
j.Value("Integer_Array") = Array(1,3,5,7)
j.Value("Double_Array") = Array(1.5,3.3,987.123456)
s.Append("own New JSONItem, changing DecimalFormat later")
s.Append("Count: " + Str(j.Count))
s.Append("ToString: " + j.ToString)
j.DecimalFormat = "-0.0####"
s.Append("ToString (DecimalFormat: " + j.DecimalFormat + "): " + j.ToString)
j = New JSONItem_MBS
j.Compact = false
for i1 As Integer = 1 to 100
Dim j2 As New JSONItem_MBS
for i2 As Integer = 1 to 25
if i2 mod 2 = 0 then
j2.Value(Str(i1) + "." + Str(i2)) = "Value " + Str(i2)
else
j2.Value(Str(i2)) = i2
end if
next
j.Value(Str(i1)) = j2
next
Dim d As Double = Microseconds
Dim s2 As String = j.ToString
d = Microseconds - d
s.Append("")
s.Append("2500 JSON objects .ToString: " + Format(d/1000, "####0.0##") + "ms")
s.Append(s2)
TextArea1.Text = Join(s, EndOfLine)
End EventHandler
End Control
Control btnJSONItem_MTC Inherits PushButton
ControlInstance btnJSONItem_MTC Inherits PushButton
EventHandler Sub Action()
Dim j As JSONItem_MTC
Dim s() As String
s.Append("JSONItem_MTC: https://github.com/ktekinay/JSONItem_MTC")
s.Append("")
s.Append(constJSON_Test1)
j = New JSONItem_MTC(constJSON_Test1)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
s.Append("Value(" + Str(i) + "): " + j.Value(i).StringValue)
next
s.Append("")
s.Append(constJSON_Test2)
j = New JSONItem_MTC(constJSON_Test2)
s.Append("IsArray: " + Str(j.IsArray))
s.Append("Count: " + Str(j.Count))
s.Append("a: " + j.Value("a"))
s.Append("b: " + j.Value("b"))
s.Append("HasName(""a""): " + Str(j.HasName("a")))
s.Append("HasName(""A""): " + Str(j.HasName("A")))
s.Append("")
s.Append(constJSON_Test3)
j = New JSONItem_MTC(constJSON_Test3)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
Dim k As JSONItem_MTC = j.Child(i)
s.Append("Value(" + Str(i) + ").id/prename: " + k.Lookup("id", 0) + "/" + k.Lookup("prename", ""))
next
s.Append("")
s.Append("own new JSONItem with DecimalFormat: -0.0##")
j = New JSONItem_MTC
j.DecimalFormat = "-0.0##"
j.Value("1") = 23.67
j.Value("2") = -2.7
j.Value("3") = 12
j.Value("4") = 12345.67891
Dim va() As Variant
va.Append(23.67)
va.Append(-2.7)
va.Append(12)
va.Append(12345.67891)
j.Value("a") = va
s.Append(j.ToString)
s.Append("")
j = New JSONItem_MTC
j.Value("Hello") = "World"
j.Value("Integer_1") = 2
j.Value("Double_7.5") = 7.5
j.Value("Double_23.67") = 23.67
j.Value("Integer_Array") = Array(1,3,5,7)
j.Value("Double_Array") = Array(1.5,3.3,987.123456)
s.Append("own New JSONItem, changing DecimalFormat later")
s.Append("Count: " + Str(j.Count))
s.Append("ToString: " + j.ToString)
j.DecimalFormat = "-0.0####"
s.Append("ToString (DecimalFormat: " + j.DecimalFormat + "): " + j.ToString)
j = New JSONItem_MTC
j.Compact = false
for i1 As Integer = 1 to 100
Dim j2 As New JSONItem_MTC
for i2 As Integer = 1 to 25
if i2 mod 2 = 0 then
j2.Value(Str(i1) + "." + Str(i2)) = "Value " + Str(i2)
else
j2.Value(Str(i2)) = i2
end if
next
j.Value(Str(i1)) = j2
next
Dim d As Double = Microseconds
Dim s2 As String = j.ToString
d = Microseconds - d
s.Append("")
s.Append("2500 JSON objects .ToString: " + Format(d/1000, "####0.0##") + "ms")
s.Append(s2)
TextArea1.Text = Join(s, EndOfLine)
End EventHandler
End Control
Control btnJSONItem_MBS1 Inherits PushButton
ControlInstance btnJSONItem_MBS1 Inherits PushButton
EventHandler Sub Action()
Dim j As JSONMBS
Dim s() As String
s.Append("JSONMBS")
s.Append("")
s.Append(constJSON_Test1)
j = New JSONMBS(constJSON_Test1)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
s.Append("Value(" + Str(i) + "): " + j.Value(i).StringValue)
next
s.Append("")
s.Append(constJSON_Test2)
j = New JSONMBS(constJSON_Test2)
s.Append("IsArray: " + Str(j.IsArray))
s.Append("Count: " + Str(j.Count))
s.Append("a: " + j.Value("a"))
s.Append("b: " + j.Value("b"))
s.Append("HasName(""a""): " + Str(j.HasName("a")))
s.Append("HasName(""A""): " + Str(j.HasName("A")))
s.Append("")
s.Append(constJSON_Test3)
j = New JSONMBS(constJSON_Test3)
s.Append("IsArray: " + Str(j.IsArray))
for i As Integer = 0 to j.Count-1
Dim k As JSONMBS = j.Child(i)
s.Append("Value(" + Str(i) + ").id/prename: " + k.Lookup("id", 0) + "/" + k.Lookup("prename", ""))
next
s.Append("")
s.Append("own new JSONItem with DecimalFormat: -0.0##")
j = New JSONMBS
'j.DecimalFormat = "-0.0##"
j.Value("1") = 23.67
j.Value("2") = -2.7
j.Value("3") = 12
j.Value("4") = 12345.67891
Dim va() As Variant
va.Append(23.67)
va.Append(-2.7)
va.Append(12)
va.Append(12345.67891)
j.Value("a") = va
s.Append(j.ToString)
s.Append("")
j = New JSONMBS
j.Value("Hello") = "World"
j.Value("Integer_1") = 2
j.Value("Double_7.5") = 7.5
j.Value("Double_23.67") = 23.67
j.Value("Integer_Array") = Array(1,3,5,7)
j.Value("Double_Array") = Array(1.5,3.3,987.123456)
s.Append("own New JSONItem, changing DecimalFormat later")
s.Append("Count: " + Str(j.Count))
s.Append("ToString: " + j.ToString)
'j.DecimalFormat = "-0.0####"
s.Append("ToString: " + j.ToString)
j = New JSONMBS
'j.Compact = false
for i1 As Integer = 1 to 100
Dim j2 As New JSONMBS
for i2 As Integer = 1 to 25
if i2 mod 2 = 0 then
j2.Value(Str(i1) + "." + Str(i2)) = "Value " + Str(i2)
else
j2.Value(Str(i2)) = i2
end if
next
j.Value(Str(i1)) = j2
next
Dim d As Double = Microseconds
Dim s2 As String = j.ToString
d = Microseconds - d
s.Append("")
s.Append("2500 JSON objects .ToString: " + Format(d/1000, "####0.0##") + "ms")
s.Append(s2)
TextArea1.Text = Join(s, EndOfLine)
End EventHandler
End Control
End Class
MenuBar MainMenuBar
MenuItem FileMenu = "&File"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Edit"
MenuItem EditUndo = "&Undo"
MenuItem EditSeparator1 = "-"
MenuItem EditCut = "Cu&t"
MenuItem EditCopy = "&Copy"
MenuItem EditPaste = "&Paste"
MenuItem EditClear = "#App.kEditClear"
MenuItem EditSeparator2 = "-"
MenuItem EditSelectAll = "Select &All"
End MenuBar
Class JSONItem_MBS
ComputedProperty Count As Integer
Sub Get()
Return mJSON.ArraySize
End Get
End ComputedProperty
ComputedProperty IsArray As Boolean
Sub Get()
Return (mJSON.Type = JSONMBS.kTypeArray)
End Get
End ComputedProperty
ComputedProperty ToString As String
Sub Get()
Return mJSON.toString(Not Me.Compact)
End Get
End ComputedProperty
Sub Append(v as Variant)
// for arrays we use array node instead of object node
// so we need to switch the internal JSONMBS object
If (Not Me.IsArray) Then
mJSON = JSONMBS.NewArrayNode
End If
Dim p As JSONMBS = ToJSON(v)
mJSON.AddItemToArray(p)
End Sub
Function Child(index As Integer) As JSONItem_MBS
Return Me.Value(index)
End Function
Sub Constructor()
// start with a new object
mJSON = JSONMBS.NewObjectNode
End Sub
Protected Sub Constructor(c As JSONMBS)
// wrap a JSONMBS
Self.mJSON = c
End Sub
Sub Constructor(data As String)
// parse and raise exception on error
// raise exception also if data could be parsed partially
mJSON = New JSONMBS(data)
'If (mJSON.Handle = 0) or (mJSON.ParseError <> "") Then
'Raise New JSONException("JSONMBS ParseError: " + mJSON.ParseError, mJSON.Handle)
'End If
End Sub
Private Function FromJSON(pJSON as JSONMBS) As Variant
If (pJSON = Nil) Then Return Nil
Select Case pJSON.Type
Case JSONMBS.kTypeArray, JSONMBS.kTypeObject
Dim r As New JSONItem_MBS(pJSON)
r.DecimalFormat = Me.DecimalFormat
Return r
Case JSONMBS.kTypeNull
Return Nil
Case JSONMBS.kTypeBoolean
Return pJSON.ValueBoolean
Case JSONMBS.kTypeDouble
Return pJSON.ValueDouble
Case JSONMBS.kTypeInt64
Return pJSON.ValueInt64
Case JSONMBS.kTypeUInt64
Return pJSON.ValueUInt64
Case JSONMBS.kTypeString
If pJSON.IsInt64 Then Return pJSON.ValueInt64
If pJSON.IsNumber Then Return pJSON.ValueDouble
Return pJSON.ValueString
Case JSONMBS.kTypeSingle
Return pJSON.ValueDouble
End Select
// Hmm...
Break
End Function
Function HasName(psName As String) As Boolean
Dim c As JSONMBS = mJSON.ChildNode
If (c = Nil) Then Return False
If (StrComp(c.Name, psName, REALbasic.StrCompCaseSensitive) = 0) Then Return True
While (c <> mJSON.LastChildNode)
c = c.NextNode
If (StrComp(c.Name, psName, REALbasic.StrCompCaseSensitive) = 0) Then Return True
Wend
Return False
End Function
Function Lookup(key as Variant, defaultValue as Variant) As Variant
Select Case mJSON.Type
Case JSONMBS.kTypeArray
Dim n As Integer = key.IntegerValue
Dim c As JSONMBS = mJSON.ArrayItem(n)
If (c = Nil) Then
Return defaultValue
Else
Return FromJSON(c)
End If
Case JSONMBS.kTypeObject
Dim l As String = key.StringValue
Dim c As JSONMBS = mJSON.Child(l)
If (c = Nil) Then
Return defaultValue
Else
Return FromJSON(c)
End If
Else
// Hmm...
Break
End Select
Return defaultValue
End Function
Function Name(index as Integer) As String
Dim c As JSONMBS = mJSON.ChildNode
For i As Integer = 1 To index
If (c = Nil) Then Return ""
c = c.NextNode
Next
If (c = Nil) Then Return ""
Return c.Name
End Function
Function Names() As String()
Dim s() As String
Dim c As JSONMBS = mJSON.ChildNode
If (c = Nil) Then Return s
s.Append(c.Name)
While (c <> mJSON.LastChildNode)
c = c.NextNode
If (c = Nil) Then Exit 'Loop
s.Append(c.Name)
Wend
Return s
End Function
Private Function ToJSON(v as Variant) As JSONMBS
// get a JSONItem for Variant
If (v = Nil) Then Return JSONMBS.NewNullNode
If (v IsA JSONItem_MBS) Then Return JSONItem_MBS(v).mJSON
If (v IsA JSONMBS) Then Return v
Select Case v.Type
Case Variant.TypeString
Return JSONMBS.NewStringNode(v.StringValue)
Case Variant.TypeBoolean
Return JSONMBS.NewBoolNode(v.BooleanValue)
Case Variant.TypeDouble, Variant.TypeSingle
Return JSONMBS.NewNumberNode(Str(v.DoubleValue, Me.DecimalFormat))
Case Variant.TypeInt64, Variant.TypeInt32
Return JSONMBS.NewInt64Node(v.Int64Value)
Else
If v.IsArray Then
Select Case v.ArrayElementType
Case Variant.TypeDouble
Dim a As JSONMBS = JSONMBS.NewArrayNode
Dim arr() As Double = v
For i As Integer = 0 To arr.Ubound
a.AddItemToArray(ToJSON(arr(i)))
Next
Return a
Case Variant.TypeSingle
Dim a As JSONMBS = JSONMBS.NewArrayNode
Dim arr() As Single = v
For i As Integer = 0 To arr.Ubound
a.AddItemToArray(ToJSON(arr(i)))
Next
Return a
Case Variant.TypeObject
Dim a As JSONMBS = JSONMBS.NewArrayNode
Dim arr() As Object = v
For i As Integer = 0 To arr.Ubound
a.AddItemToArray(ToJSON(arr(i)))
Next
Return a
Else
//Use Convert to handle Array values
Return JSONMBS.Convert(v)
End Select
End If
End Select
// Hmm... that kind of Variant is not (yet) implemented
Break
End Function
Function Value(key as Variant) As Variant
Select Case mJSON.Type
Case JSONMBS.kTypeArray
Dim n As Integer = key
Dim c As JSONMBS = mJSON.ArrayItem(n)
If (c = Nil) Then
Dim k As New KeyNotFoundException
k.Message = "Item "+Str(n)+" not found."
Raise k
Else
Return FromJSON(c)
End If
Case JSONMBS.kTypeObject
Dim l As String = key
Dim c As JSONMBS = mJSON.Child(l)
If (c = Nil) Then
Dim k As New KeyNotFoundException
k.Message = "Key "+l+" not found."
Raise k
Else
Return FromJSON(c)
End If
End Select
// Hmm...
Break
End Function
Sub Value(key as Variant, assigns value as Variant)
Dim KeyString As String = key.StringValue
// remove if needed.
// You can leave this away, if you never edit JSON, but just parse+build
mJSON.DeleteItem(KeyString)
// now add new value
mJSON.AddItemToObject(KeyString, ToJSON(Value))
End Sub
Note "KnownIssues"
DecimalFormat
-------------
- is only respected when adding Double values
-> you have to set the DecimalFormat before adding values
- no effect if you are changing the DecimalFormat later (e.g. before .ToString)
Property Compact As Boolean = True
Property DecimalFormat As String = "-0.0##############"
Property Private mJSON As JSONMBS
End Class
Class JSONItem_MTC
ComputedProperty Count As Integer
Sub Get()
if IsArray then
return ArrayValues.Ubound + 1
elseif ObjectValues <> nil then
return ObjectValues.Count
else
return 0
end if
End Get
End ComputedProperty
ComputedProperty IsArray As Boolean
Sub Get()
return HasSetType = kHasSetArray
End Get
End ComputedProperty
ComputedProperty Private IsObject As Boolean
Sub Get()
return HasSetType = kHasSetObject
End Get
End ComputedProperty
ComputedProperty ToNativeValue As Variant
Sub Get()
if IsObject then
dim d as new Dictionary
dim rawKeys() as variant = ObjectValues.Keys
dim values() as variant = ObjectValues.Values
for i as integer = 0 to rawKeys.Ubound
dim name as string = KeyToName( rawKeys( i ) )
dim value as variant = values( i )
if value isa JSONItem_MTC then
value = JSONItem_MTC( value ).ToNativeValue
end if
d.Value( name ) = value
next
return d
elseif IsArray then
dim arr() as variant
redim arr( ArrayValues.Ubound )
for i as integer = 0 to ArrayValues.Ubound
dim value as variant = ArrayValues( i )
if value isa JSONItem_MTC then
value = JSONItem_MTC( value ).ToNativeValue
end if
arr( i ) = value
next
return arr
else
return nil
end if
End Get
End ComputedProperty
ComputedProperty ToString As String
Sub Get()
return self.Serialize( self )
End Get
End ComputedProperty
Const Version = "4.2"
Const kBackSlash = 92
Const kCloseCurlyBrace = 125
Const kCloseSquareBracket = 93
Const kColon = 58
Const kComma = 44
Const kDefaultDecimalFormat = "-0.0##############"
Const kDot = 46
Const kForwardSlash = 47
Const kHasSetArray = 1
Const kHasSetNone = 0
Const kHasSetObject = 2
Const kHyphen = 45
Const kInMBSize = 2048
Const kOpenCurlyBrace = 123
Const kOpenSquareBracket = 91
Const kOutMBSize = 2097152
Const kPlus = 43
Const kQuote = 34
Enum EncodeType
None = 0
JavaScriptCompatible = 1
All = 2
End Enum
Private Sub AdvancePastWhiteSpace(inMB As MemoryBlock, ByRef pos As Integer)
dim inPtr as Ptr = inMB
dim lastPos as integer = inMB.Size - 1
do
if pos > lastPos then
raise new JSONException( "A parsing error occurred", 2, pos )
return
end if
dim thisByte as integer = inPtr.Byte( pos )
select case thisByte
case 9, 10, 13, 32
pos = pos + 1
else
return
end select
loop
End Sub
Sub Append(value As Variant)
if not EnsureArray() then
return
end if
Validate( value )
HasSetType = kHasSetArray
ArrayValues.Append value
End Sub
Private Function AppendOutMB(outMBs() As MemoryBlock, ByRef outIndex As Integer) As MemoryBlock
dim outMB as MemoryBlock
outMB = new MemoryBlock( kOutMBSize )
outMBs.Append outMB
outIndex = 0
return outMB
End Function
Function Child(index As Integer) As JSONItem_MTC
dim r as JSONItem_MTC = self.Value( index )
return r
End Function
Sub Child(index As Integer, Assigns obj As JSONItem_MTC)
self.Value( index ) = obj
End Sub
Function Child(name As String) As JSONItem_MTC
dim r as JSONItem_MTC = self.Value( name )
return r
End Function
Sub Child(name As String, Assigns obj As JSONItem_MTC)
self.Value( name ) = obj
End Sub
Sub Clear()
ObjectValues = new Dictionary
redim ArrayValues( -1 )
End Sub
Sub Constructor()
LoadCS = new CriticalSection
ObjectValues = new Dictionary
DecimalFormat = kDefaultDecimalFormat
End Sub
Sub Constructor(JSONString As String, isStrict As Boolean = False)
self.Constructor()
self.Strict = isStrict
self.Load JSONString
End Sub
Private Function DecodeString(inMB As MemoryBlock, ByRef pos As Integer, current As JSONItem_MTC, outMB As MemoryBlock) As String
dim lastPos as integer = inMB.Size - 1
if pos > lastPos then
raise new JSONException( "Missing """, 7, pos + 1 )
return ""
end if
dim inPtr as Ptr = inMB
if inPtr.Byte( pos ) <> kQuote then
raise new JSONException( "Missing """, 7, pos + 1 )
return ""
end if
if outMB.Size < inMB.Size then
outMB.Size = inMB.Size
end if
dim outPtr as Ptr = outMB
dim outIndex as integer
pos = pos + 1
dim flushStart as integer = pos
dim flushEnd as integer = -1
dim hasBackSlash as boolean
dim surrogatePairFirstHalf as integer
dim surrogatePairLastHalf as integer
dim expectingSurrogate as boolean
dim insertCodepoint as boolean
do
if pos > lastPos then
dim msg as string = if( current.IsArray, "Missing , or ]", "Missing , or }" )
raise new JSONException( msg, 6, pos )
return ""
end if
if expectingSurrogate and ( inPtr.Byte( pos ) <> kBackSlash or ( pos + 1 ) > lastPos or inPtr.Byte( pos + 1 ) <> 117 ) then
raise new JSONException( "Improperly formed JSON string", 0 )
return ""
end if
dim thisByte as integer = inPtr.Byte( pos )
select case thisByte
case kBackSlash
hasBackSlash = true
if flushEnd <> -1 then
dim flushLen as integer = flushEnd - flushStart + 1
outMB.StringValue( outIndex, flushLen ) = inMB.StringValue( flushStart, flushLen )
outIndex = outIndex + flushLen
end if
pos = pos + 1
if pos > lastPos then
continue do
end if
thisByte = inPtr.Byte( pos )
if Strict and thisByte < 32 then
continue do // Deal on the next pass
end if
select case thisByte
case 114 // r
outPtr.Byte( outIndex ) = 13
outIndex = outIndex + 1
pos = pos + 1
case 116 // t
outPtr.Byte( outIndex ) = 9
outIndex = outIndex + 1
pos = pos + 1
case 110 // n
outPtr.Byte( outIndex ) = 10
outIndex = outIndex + 1
pos = pos + 1
case 98 // b
outPtr.Byte( outIndex ) = 8
outIndex = outIndex + 1
pos = pos + 1
case 102 // f
outPtr.Byte( outIndex ) = 12
outIndex = outIndex + 1
pos = pos + 1
case 117 // u
if ( pos + 5 ) > lastPos then
raise new JSONException( "Improperly formed JSON string", 0 )
return ""
end if
dim codepoint as integer
for byteAdder as integer = 1 to 4
dim hexDigit as integer = inPtr.Byte( pos + byteAdder )
select case hexDigit
case 48 to 57 // 0 - 9
codepoint = ( codepoint * 16 ) + ( hexDigit - 48 )
case 65 to 70 // A - F
codepoint = ( codepoint * 16 ) + ( hexDigit - 55 )
case 97 to 102 // a - f
codepoint = ( codepoint * 16 ) + ( hexDigit - 87 )
else
raise new JSONException( "Illegal value", 10, pos )
return ""
end select
next
//
// See if this is part of a surrogate pair
//
if expectingSurrogate then
if codepoint < &hDC00 or codepoint > &hDFFF then
raise new JSONException( "Invalid codepoint", 10, pos )
return ""
end if
//
// surrogatePairFirstHalf holds the first 10 bits, this codepoint holds the last 10
//
surrogatePairLastHalf = codepoint
codepoint = Bitwise.ShiftLeft( surrogatePairFirstHalf - &hD800, 10, 20 ) + ( surrogatePairLastHalf - &hDC00 ) + &h10000
insertCodepoint = true
expectingSurrogate = false
elseif codepoint < 128 then
outPtr.Byte( outIndex ) = codepoint
outIndex = outIndex + 1
elseif codepoint < &hD800 or codepoint > &hDFFF then
insertCodepoint = True
elseif codepoint < &hDC00 then // Surrogate pair
surrogatePairFirstHalf = codepoint
expectingSurrogate = true
else // It's some codepoint that shouldn't be here
raise new JSONException( "Invalid codepoint", 10, pos )
return ""
end if
if insertCodepoint then
if codepoint > &b1111111111111111 then // Four bytes
outPtr.Byte( outIndex ) = &b11110000 or Bitwise.ShiftRight( codepoint, 18, 21 )
outPtr.Byte( outIndex + 1 ) = &b10000000 or ( Bitwise.ShiftRight( codepoint, 12, 21 ) and &b111111 )
outPtr.Byte( outIndex + 2 ) = &b10000000 or ( Bitwise.ShiftRight( codepoint, 6, 21 ) and &b111111 )
outPtr.Byte( outIndex + 3 ) = &b10000000 or ( codepoint and &b111111 )
outIndex = outIndex + 4
elseif codepoint > &b11111111111 then // Three bytes
outPtr.Byte( outIndex ) = &b11100000 or Bitwise.ShiftRight( codepoint, 12, 16 )
outPtr.Byte( outIndex + 1 ) = &b10000000 or ( BitWise.ShiftRight( codepoint, 6, 16 ) and &b111111 )
outPtr.Byte( outIndex + 2 ) = &b10000000 or ( codepoint and &b111111 )
outIndex = outIndex + 3
else // two bytes
outPtr.Byte( outIndex ) = &b11000000 or Bitwise.ShiftRight( codepoint, 6, 11 )
outPtr.Byte( outIndex + 1 ) = &b10000000 or ( codepoint and &b111111 )
outIndex = outIndex + 2
end if
insertCodepoint = false
end if
pos = pos + 5
else // Some random escaped character
if Strict then
raise new JSONException( "Illegal Character", 9, pos + 1 )
return ""
end if
outPtr.Byte( outIndex ) = inPtr.Byte( pos )
outIndex = outIndex + 1
pos = pos + 1
end select
flushStart = pos
flushEnd = -1
case kQuote // End
pos = pos + 1
exit
else
if self.Strict and thisByte < 32 then
raise new JSONException( "Illegal Character", 9, pos + 1 )
return ""
end if
flushEnd = pos
pos = pos + 1
end select
loop
if hasBackSlash and flushEnd <> -1 then
dim flushLen as integer = flushEnd - flushStart + 1
outMB.StringValue( outIndex, flushLen ) = inMB.StringValue( flushStart, flushLen )
outIndex = outIndex + flushLen
end if
dim r as string
if hasBackSlash then
r = outMB.StringValue( 0, outIndex )
elseif flushEnd <> -1 then
r = inMB.StringValue( flushStart, flushEnd - flushStart + 1 )
end if
r = r.DefineEncoding( Encodings.UTF8 )
return r
End Function
Private Function DecodeValue(inMB As MemoryBlock, ByRef pos As Integer, current As JSONItem_MTC, outMB As MemoryBlock) As Variant
static trueValue as Int32
if trueValue = 0 then
dim mb as MemoryBlock = "true"
mb.LittleEndian = inMB.LittleEndian
trueValue = mb.Int32Value( 0 )
end if
static nullValue as Int32
if nullValue = 0 then
dim mb as MemoryBlock = "null"
mb.LittleEndian = inMB.LittleEndian
nullValue = mb.Int32Value( 0 )
end if
static falsValue as Int32
if falsValue = 0 then
dim mb as MemoryBlock = "fals"
mb.LittleEndian = inMB.LittleEndian
falsValue = mb.Int32Value( 0 )
end if
dim lastPos as integer = inMB.Size - 1
if pos > lastPos then
raise new JSONException( "A parsing error occurred", 2, pos + 1 )
return nil
end if
dim inPtr as Ptr = inMB
dim thisByte as integer = inPtr.Byte( pos )
if thisByte = kQuote then
return DecodeString( inMB, pos, current, outMB )
elseif thisByte = kOpenCurlyBrace then
dim child as new JSONItem_MTC
child.HasSetType = kHasSetObject
pos = pos + 1
return child
elseif thisByte = kOpenSquareBracket then
dim child as new JSONItem_MTC
child.HasSetType = kHasSetArray
pos = pos + 1
return child
end if
// Look for the next ender
dim startPos as integer = pos
dim endPos as integer = pos + 1
do
if endPos > lastPos then
dim msg as string = if( current.IsArray, "Missing , or ]", "Missing , or }" )
raise new JSONException( msg, 6, endPos + 1 )
return nil
end if
thisByte = inPtr.Byte( endPos )
if thisByte < 33 then
exit do
elseif thisByte = kComma then
exit do
elseif thisByte = kCloseCurlyBrace or thisByte = kCloseSquareBracket then
exit do
end if
endPos = endPos + 1
loop
pos = endPos
endPos = endPos - 1
dim valueLen as integer = endPos - startPos + 1
dim value as variant
dim keepChecking as boolean = valueLen <> 0
dim firstByte as integer = inPtr.Byte( startPos )
//
// See if it's true, false, or null first
//
if keepChecking and valueLen = 5 then // false?
if firstByte = 102 and inPtr.Byte( startPos + 4 ) = 101 and inMB.Int32Value( startPos ) = falsValue then
value = false
keepChecking = false
end if
end if
if keepChecking and valueLen = 4 then // true or null?
if firstByte = 116 and inMB.Int32Value( startPos ) = trueValue then
value = true
keepChecking = false
end if
if keepChecking and firstByte = 110 and inMB.Int32Value( startPos ) = nullValue then
value = nil
keepChecking = false
end if
end if
//
// Is it an number?
//
if keepChecking and valueLen = 1 and firstByte >= 48 and firstByte <= 57 then // Single digit
value = firstByte - 48
keepChecking = false
end if
if keepChecking then
//
// Only send it to ParseNumber if it's not Strict, or if the firstByte meets strict criteria
//
if not Strict or ( firstByte = kHyphen or ( firstByte >= 48 and firstByte <= 57 ) ) then
if ParseNumber( inMB, startPos, endPos, value ) then
keepChecking = false
end if
end if
end if
//
// Hail Mary
//
if keepChecking and not Strict then
keepChecking = false
dim valueString as string = inMB.StringValue( startPos, valueLen ).DefineEncoding( Encodings.UTF8 )
if valueString = "true" then
value = true
elseif valueString = "false" then
value = false
elseif valueString = "null" then
value = nil
elseif IsNumeric( valueString ) then
value = val( valueString )
else
keepChecking = true
end if
end if
if keepChecking then // Never found it
raise new JSONException( "Illegal Value", 10, startPos )
end if
return value
End Function
Private Sub EncodeCodepoint(value As Integer, outPtr As Ptr, pos As Integer)
// WARNING: Assumption is that the destination has enough room
static hexByteArr() as integer = Array( 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 ) // 0-9, A- F
outPtr.Byte( pos + 3 ) = hexByteArr( value and &h0F )
outPtr.Byte( pos + 2 ) = hexByteArr( Bitwise.ShiftRight( value, 4, 32 ) and &h0F )
outPtr.Byte( pos + 1 ) = hexByteArr( Bitwise.ShiftRight( value, 8, 32 ) and &h0F )
outPtr.Byte( pos ) = hexByteArr( Bitwise.ShiftRight( value, 12, 32 ) and &h0F )
End Sub
Private Sub EncodeString(s As String, settings As JSONItem_MTC, outMBs() As MemoryBlock, ByRef outIndex As Integer, inMB As MemoryBlock)
// Encodes a string for output during ToString.
// Honors the EncodeUnicode setting, but some characters must ALWAYS be encoded for
// Javascript compatibility. These are
//
// chr 127
// &h00ad
// &h0600 - &h0604
// &h070f
// &h17b4 - &h17b5
// &h200c - &h200f
// &h2028 - &h202f
// &h2060 - &h206f
// &hfeff
// &hfff0 - &hffff
static alwaysEncode() as integer = Array( _
127, &hAD, _
&h600, &h601, &h602, &h603, &h604, _
&h70F, _
&h17B4, &h17B5, _
&h200C, &h200D, &h200E, &h200F, _
&h2028, &h2029, &h202A, &h202B, &h202C, &h202D, &h202E, &h202F, _
&h2060, &h2061, &h2062, &h2063, &h2064, &h2065, &h2066, &h2067, &h2068, &h2069, _
&h206A, &h206B, &h206C, &h206D, &h206E, &h206F, _
&hFEFF, _
&hFFF0, &hFFF1, &hFFF2, &hFFF3, &hFFF4, &hFFF5, &hFFF6, &hFFF7, &hfff8, &hFFF9, _
&hFFFA, &hFFFB, &hFFFC, &hFFFD, &hFFFE, &hFFFF _
)
const kByteBuffer = 13 // Most an encoding can use plus the trailing quote
dim outMB as MemoryBlock = outMBs( outMBs.Ubound )
dim outSize as integer = outMB.Size
if outIndex > ( outSize - kByteBuffer ) then
outMB = AppendOutMB( outMBs, outIndex )
end if
dim outPtr as Ptr = outMB
outPtr.Byte( outIndex ) = kQuote
outIndex = outIndex + 1
if s = "" then
outPtr.Byte( outIndex ) = kQuote
outIndex = outIndex + 1
return
end if
s = s.ConvertEncoding( Encodings.UTF8 )
dim inSize as integer = s.LenB
if inMB.Size < inSize then
inMB.Size = inSize
end if
inMB.StringValue( 0, inSize ) = s
dim inPtr as Ptr = inMB
dim lastIndex as integer = inSize - 1
dim inIndex as integer = -1
dim encodeUnicode as EncodeType = settings.EncodeUnicode
dim escapeSlashes as boolean = settings.EscapeSlashes
do
inIndex = inIndex + 1
if inIndex > lastIndex then
exit do
end if
if outIndex > ( outSize - kByteBuffer ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
dim thisByte as byte = inPtr.Byte( inIndex )
if thisByte = 34 or thisByte = kBackSlash then // quote or backslash
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = thisByte
outIndex = outIndex + 2
elseif thisByte = 47 then // slash
if escapeSlashes then
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = thisByte
outIndex = outIndex + 2
else
outPtr.Byte( outIndex ) = thisByte
outIndex = outIndex + 1
end if
elseif thisByte = 8 then // backspace
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 98 // b
outIndex = outIndex + 2
elseif thisByte = 9 then // tab
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 116 // t
outIndex = outIndex + 2
elseif thisByte = 10 then // linefeed
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 110 // n
outIndex = outIndex + 2
elseif thisByte = 12 then // formfeed
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 102 // f
outIndex = outIndex + 2
elseif thisByte = 13 then // return
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 114 // r
outIndex = outIndex + 2
elseif thisByte < 32 then // control character
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 117 // u
dim insertValue as string = Right( "000" + hex( thisByte ), 4 )
outMB.StringValue( outIndex + 2, 4 ) = insertValue
outIndex = outIndex + 6
elseif thisByte > 127 and thisByte < 192 then // Continuation byte that wasn't encoded, so just add it
outPtr.Byte( outIndex ) = thisByte
outIndex = outIndex + 1
elseif encodeUnicode <> EncodeType.None and ( thisByte = 127 or thisByte >= 192 ) then // Leading byte
dim codepoint as integer
dim byteLength as integer
if thisByte >= 240 then
byteLength = 4
codepoint = Bitwise.ShiftLeft( thisByte and &b00000111, 18, 21 )
codepoint = codepoint + ( Bitwise.ShiftLeft( inPtr.Byte( inIndex + 1 ) and &b00111111, 12, 21 ) )
codepoint = codepoint + ( Bitwise.ShiftLeft( inPtr.Byte( inIndex + 2 ) and &b00111111 , 6, 21 ) )
codepoint = codepoint + ( inPtr.Byte( inIndex + 3 ) and &b00111111 )
elseif thisByte >= 224 then
byteLength = 3
codepoint = Bitwise.ShiftLeft( thisByte and &b00001111, 12, 18 )
codepoint = codepoint + ( Bitwise.ShiftLeft( inPtr.Byte( inIndex + 1 ) and &b00111111, 6, 18 ) )
codepoint = codepoint + ( inPtr.Byte( inIndex + 2 ) and &b00111111 )
elseif thisByte >= 192 then
byteLength = 2
codepoint = Bitwise.ShiftLeft( thisByte and &b00011111, 6, 18 )
codepoint = codepoint + ( inPtr.Byte( inIndex + 1 ) and &b00111111 )
else
byteLength = 1
codepoint = thisByte
end if
//
// See if we have to use a surrogate pair
//
if codepoint > &hFFFF and encodeUnicode = EncodeType.All then
codepoint = codepoint - &h10000
dim surrogateFirst as integer = &hD800 + Bitwise.ShiftRight( codepoint, 10, 21 )
dim surrogateLast as integer = &hDC00 + ( codepoint and &b1111111111 )
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 117 // u
EncodeCodepoint( surrogateFirst, outPtr, outIndex + 2 )
outPtr.Byte( outIndex + 6 ) = kBackSlash
outPtr.Byte( outIndex + 7 ) = 117 // u
EncodeCodepoint( surrogateLast, outPtr, outIndex + 8 )
outIndex = outIndex + 12
elseif codepoint <= &hFFFF and ( encodeUnicode = EncodeType.All or alwaysEncode.IndexOf( codepoint ) <> -1 ) then
outPtr.Byte( outIndex ) = kBackSlash
outPtr.Byte( outIndex + 1 ) = 117 // u
EncodeCodepoint( codepoint, outPtr, outIndex + 2 )
outIndex = outIndex + 6
else
outPtr.Byte( outIndex ) = thisByte
outIndex = outIndex + 1
byteLength = 1 // Doesn't matter what it really is, only need to advance one byte
end if
inIndex = inIndex + byteLength - 1
else // thisByte > 31 and thisByte < 127
outPtr.Byte( outIndex ) = thisByte
outIndex = outIndex + 1
end if
loop
outPtr.Byte( outIndex ) = kQuote
outIndex = outIndex + 1
End Sub
Private Sub EncodeValue(value As Variant, settings As JSONItem_MTC, level As Integer, outMBs() As MemoryBlock, ByRef outIndex As Integer, inMB As MemoryBlock)
if value IsA JSONItem_MTC then
JSONItem_MTC( value ).Serialize( outMBs, outIndex, settings, level, inMB )
elseif value IsA Dictionary then
dim child as JSONItem_MTC = Dictionary( value )
child.Serialize( outMBs, outIndex, settings, level, inMB )
elseif value.Type = Variant.TypeString or value.Type = Variant.TypeCString or _
value.Type = Variant.TypePString then
EncodeString( value.StringValue, settings, outMBs, outIndex, inMB )
elseif value.Type = Variant.TypeText then
dim t as Text = value.TextValue
dim s as string = t
EncodeString( s, settings, outMBs, outIndex, inMB )
else
dim insert as string
if value.Type = Variant.TypeDouble or value.Type = Variant.TypeSingle or _
value.Type = Variant.TypeCurrency then
dim s as string = value.StringValue
if s.InStr( "inf" ) <> 0 then
if settings.Strict then
raise new JSONException( "Illegal Value 'inf'", 10 )
else
insert = "inf"
end if
elseif s.InStr( "nan" ) <> 0 then
if settings.Strict then
raise new JSONException( "Illegal Value 'nan'", 10 )
else
insert = "nan"
end if
else
insert = Format( value, settings.DecimalFormat )
end if
elseif value.Type = Variant.TypeNil then
insert = "null"
else
insert = value.StringValue.Lowercase
end if
dim insertLen as integer = insert.LenB
dim outMB as MemoryBlock = outMBs( outMBs.Ubound )
dim outSize as integer = outMB.Size
if outIndex > ( outSize - insertLen ) then
outMB = AppendOutMB( outMBs, outIndex )
end if
outMB.StringValue( outIndex, insertLen ) = insert
outIndex = outIndex + insertLen
end if
End Sub
Private Function EnsureArray() As Boolean
if HasSetType <> kHasSetObject then
return true
else
raise new JSONException( "This JSONItem_MTC is an object", 13 )
return false
end if
End Function
Private Function EnsureObject() As Boolean
if HasSetType <> kHasSetArray then
return true
else
raise new JSONException( "This JSONItem_MTC is an array", 13 )
return false
end if
End Function
Function HasName(name As String) As Boolean
if not EnsureObject() then
return false
end if
HasSetType = kHasSetObject
dim key as string = NameToKey( name )
return ObjectValues.HasKey( key )
End Function
Sub Insert(index As Integer, value As Variant)
if not EnsureArray() then
return
end if
HasSetType = kHasSetArray
ArrayValues.Insert( index, value )
End Sub
Function IsLoading() As Boolean
return mIsLoading
End Function
Private Function JSONStringToUTF8(s As String) As String
// Ensures that the given string is converted to UTF8.
//
// A string obtained from an unknown source might be nil or might
// be marked as the wrong encoding, so we won't trust it
// and will check ourselves.
//
// Since the string must start with characters in the ASCII range
// (whitespace, {, or [), we only need the first four bytes.
static null as string = ChrB( 0 )
static doubleNull as string = null + null
static tripleNull as string = null + null + null
dim firstFour as string = s.LeftB( 4 )
if firstFour.LenB < 2 or firstFour.LenB = 3 then
return s.DefineEncoding( Encodings.UTF8 )
end if
//
// Check for BOM
//
if true then // Scope
static bomArr() as string = Array( _
ChrB( &hFF ) + ChrB( &hFE ) + doubleNull, _
doubleNull + ChrB( &hFE ) + ChrB( &hFF ), _
ChrB( &hEF ) + ChrB( &hBB ) + ChrB( &hBF ), _
ChrB( &hFF ) + ChrB( &hFE ), _
ChrB( &hFE ) + ChrB( &hFF ) _
)
for i as integer = 0 to bomArr.Ubound
dim bom as string = bomArr( i )
dim bomLen as integer = bom.LenB
if firstFour.LeftB( bomLen ) = bom then
s = s.MidB( bomLen + 1 )
firstFour = s.LeftB( 4 )
exit
end if
next i
end if
// Check for UTF8
if firstFour.InStrB( null ) = 0 then
return s.DefineEncoding( Encodings.UTF8 )
end if
// UTF32?
if firstFour.LenB = 4 then
if firstFour.LeftB( 3 ) = tripleNull then
s = s.DefineEncoding( Encodings.UTF32BE )
return s.ConvertEncoding( Encodings.UTF8 )
end if
if firstFour.RightB( 3 ) = tripleNull then
s = s.DefineEncoding( Encodings.UTF32LE )
return s.ConvertEncoding( Encodings.UTF8 )
end if
end if
// UTF16?
if firstFour.LeftB( 1 ) = null then
s = s.DefineEncoding( Encodings.UTF16BE )
return s.ConvertEncoding( Encodings.UTF8 )
end if
if firstFour.RightB( 1 ) = null then
s = s.DefineEncoding( Encodings.UTF16LE )
return s.ConvertEncoding( Encodings.UTF8 )
end if
// We don't know what the heck it is
return s
End Function
Private Function KeyToName(key As String) As String
dim hex as string = key.NthField( "-", key.CountFields( "-" ) )
dim name as string = key.LeftB( key.LenB - hex.LenB - 1 )
return name
End Function
Sub Load(JSONString As String)
const kNeverStarted = -1000
dim originalSetType as integer = kNeverStarted
dim originalValue as variant
if not LoadCS.TryEnter then
raise new JSONException( "This object is currently loading", 0 )
return
end if
JSONString = JSONStringToUTF8( JSONString ).RTrim
if JSONString = "" then
// Do nothing
else
mIsLoading = true
//
// Save original values
//
originalSetType = self.HasSetType
if originalSetType = kHasSetArray then
dim values() as variant
redim values( ArrayValues.Ubound )
for i as integer = 0 to ArrayValues.Ubound
values( i ) = ArrayValues( i )
next i
originalValue = values
elseif originalSetType = kHasSetObject then
dim d as new Dictionary
dim keys() as Variant = ObjectValues.Keys
for i as integer = 0 to keys.Ubound
dim k as variant = keys( i )
d.Value( k ) = ObjectValues.Value( k )
next i
originalValue = d
end if
dim inMB as MemoryBlock = JSONString
dim pos as integer
dim inPtr as Ptr = inMB
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
dim proceed as boolean = true
if inPtr.Byte( pos ) = kOpenSquareBracket then
if IsObject then
// Do nothing
proceed = false
else
HasSetType = kHasSetArray
end if
elseif inPtr.Byte( pos ) = kOpenCurlyBrace then
if IsArray then
// Do nothing
proceed = false
else
HasSetType = kHasSetObject
end if
elseif inPtr.Byte( pos ) <> kOpenCurlyBrace and inPtr.Byte( pos ) <> kOpenSquareBracket then
raise new JSONException( "Parse Error: Expecting '{' or '['", 1 )
return
end if
if proceed then
dim outMB as new MemoryBlock( 1024 )
pos = pos + 1
dim stack() as JSONItem_MTC
dim current as JSONItem_MTC = self
dim lastIndex as integer = inMB.Size - 1
do
if current.IsArray then
if pos <= lastIndex and inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
if inPtr.Byte( pos ) = kCloseSquareBracket then // Empty array
if stack.Ubound <> -1 then
current = stack.Pop
else
current = nil
end if
pos = pos + 1
else
LoadArray( inMB, pos, current, stack, outMB )
if pos <= lastIndex and inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
end if
else
if pos <= lastIndex and inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
if inPtr.Byte( pos ) = kCloseCurlyBrace then // Empty object
if stack.Ubound <> -1 then
current = stack.Pop
else
current = nil
end if
pos = pos + 1
else
LoadObject( inMB, pos, current, stack, outMB )
if pos <= lastIndex and inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
end if
end if
if current is nil then
if pos <= lastIndex then
raise new JSONException( "A parsing error occurred", 2, pos )
end if
exit do
elseif pos > lastIndex then
raise new JSONException( "A parsing error occurred", 2, pos )
exit do
else
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
if inPtr.Byte( pos ) = kComma then
pos = pos + 1
end if
end if
loop
end if // proceed
end if // JSONString = ""
mIsLoading = false
LoadCS.Leave
Exception err as RuntimeException
//
// Restore original values
//
if originalSetType <> kNeverStarted then
self.Clear
if originalSetType = kHasSetArray then
ArrayValues = originalValue
elseif originalSetType = kHasSetObject then
ObjectValues = originalValue
end if
HasSetType = originalSetType
end if
mIsLoading = False
LoadCS.Leave
raise err
End Sub
Private Sub LoadArray(inMB As MemoryBlock, ByRef pos As Integer, ByRef current As JSONItem_MTC, stack() As JSONItem_MTC, outMB As MemoryBlock)
dim inPtr As Ptr = inMB
dim commaFound as boolean
dim value as variant
do
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
if inPtr.Byte( pos ) = kCloseSquareBracket then
if commaFound then
raise new JSONException( "Illegal Value", 10, pos )
exit do
end if
pos = pos + 1
if stack.Ubound <> -1 then
current = stack.Pop
else
current = nil
end if
exit do
end if
value = DecodeValue( inMB, pos, current, outMB )
current.ArrayValues.Append value
if value IsA JSONItem_MTC then
stack.Append current
current = value
exit do
end if
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
commaFound = false
if inPtr.Byte( pos ) = kComma then
pos = pos + 1
commaFound = true
elseif inPtr.Byte( pos ) <> kCloseSquareBracket then
raise new JSONException( "A parsing error occurred", 2, pos + 1 )
exit do
end if
loop
End Sub
Private Sub LoadObject(inMB As MemoryBlock, ByRef pos As Integer, ByRef current As JSONItem_MTC, stack() As JSONItem_MTC, outMB As MemoryBlock)
dim inPtr As Ptr = inMB
dim name as string
dim key as string
dim value as variant
do
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
if inPtr.Byte( pos ) = kCloseCurlyBrace then
pos = pos + 1
if stack.Ubound <> -1 then
current = stack.Pop
else
current = nil
end if
exit do
end if
name = DecodeString( inMB, pos, current, outMB )
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
if inPtr.Byte( pos ) <> kColon then
// Something is wrong
raise new JSONException( "Missing :", 5, pos + 1 )
exit do
end if
pos = pos + 1
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
value = DecodeValue( inMB, pos, current, outMB )
key = NameToKey( name )
current.ObjectValues.Value( key ) = value
if value IsA JSONItem_MTC then
stack.Append current
current = value
exit do
end if
if inPtr.Byte( pos ) < 33 then
self.AdvancePastWhiteSpace( inMB, pos )
end if
if inPtr.Byte( pos ) = kComma then
pos = pos + 1
end if
loop
End Sub
Function Lookup(name As String, defaultValue As Variant) As Variant
if not EnsureObject() then
return nil
end if
dim key as string = NameToKey( name )
return ObjectValues.Lookup( key, defaultValue )
End Function
Function Name(index As Integer) As String
if not EnsureObject() then
return ""
end if
dim key as string = ObjectValues.Key( index ).StringValue
dim name as string = KeyToName( key )
return name
End Function
Private Function NameToKey(name As String) As String
name = name.ConvertEncoding( Encodings.UTF8 )
dim key as string = name + "-" + EncodeHex( name )
return key
End Function
Function Names() As String()
if not EnsureObject() then
return nil
end if
dim d as Dictionary = ObjectValues
dim keys() as variant = d.Keys
dim r() as string
redim r( keys.Ubound )
for i as integer = 0 to keys.Ubound
dim name as string = keys( i )
name = KeyToName( name )
r( i ) = name
next i
return r
End Function
Sub Operator_Convert(arr() As Boolean)
self.Constructor()
for each v as boolean in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As Currency)
self.Constructor()
for each v as currency in arr
self.Append v
next
End Sub
Sub Operator_Convert(d As Dictionary)
self.Constructor()
dim keys() as variant = d.Keys
for each name as string in keys
self.Value( name ) = d.Value( name )
next
End Sub
Sub Operator_Convert(arr() As Double)
self.Constructor()
for each v as double in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As Int32)
self.Constructor()
for each v as Int32 in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As Int64)
self.Constructor()
for each v as Int64 in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As Object)
self.Constructor()
for each v as object in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As Single)
self.Constructor()
for each v as single in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As String)
self.Constructor()
for each v as string in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As Text)
self.Constructor()
for each v as text in arr
self.Append v
next
End Sub
Sub Operator_Convert(arr() As Variant)
self.Constructor()
for each v as variant in arr
self.Append v
next
End Sub
Function Operator_Subscript(index As Integer) As Variant
return self.Value( index )
End Function
Sub Operator_Subscript(index As Integer, Assigns value As Variant)
self.Value( index ) = value
End Sub
Shared Function ParseJSON(s As String) As Variant
dim j as new JSONItem_MTC( s )
return j.ToNativeValue
End Function
Private Function ParseNumber(mb As MemoryBlock, startPos As Integer, endPos As Integer, ByRef result As Variant) As Boolean
if endPos >= mb.Size then
endPos = mb.Size - 1
end if
if endPos <= startPos then
return false
end if
dim p as Ptr = mb
dim dotFound as boolean
dim inExponent as boolean
dim r as boolean = true
dim pos as integer = startPos
dim thisByte as integer = p.Byte( pos )
dim isNegative as boolean
if thisByte = kHyphen then
isNegative = true
pos = pos + 1
elseif thisByte = kPlus then
pos = pos + 1
end if
dim total as Int64
do
thisByte = p.Byte( pos )
select case thisByte
case kDot
if dotFound or inExponent then
r = false
exit do
else
dotFound = true
pos = pos + 1
end if
case 48 to 57
if not dotFound and not inExponent then
total = ( total * 10 ) + ( thisByte - 48 )
end if
pos = pos + 1
case 101, 69 // e, E
if inExponent then
r = false
exit do
elseif Strict and dotFound and p.Byte( pos - 1 ) = kDot then
r = false
exit do
else
inExponent = true
pos = pos + 1
if pos > endPos then
r = false
exit do
else
thisByte = p.Byte( pos )
if thisByte = 45 or thisByte = 43 then
pos = pos + 1
if pos > endPos then
r = false
exit do
end if
end if
end if
end if
else
r = false
exit do
end select
loop until pos > endPos
if r and Strict and dotFound and ( p.Byte( endPos ) < 48 or p.Byte( endPos ) > 57 ) then
r = false
end if
if r then
if not dotFound and not inExponent then // Integer
if isNegative then
total = 0 - total
end if
result = total
else
dim s as string = mb.StringValue( startPos, endPos - startPos + 1 )
result = Val( s )
end if
end if
return r
End Function
Sub Remove(index As Integer)
if not EnsureArray() then
return
end if
ArrayValues.Remove( index )
End Sub
Sub Remove(name As String)
if not EnsureObject() then
return
end if
dim key as string = NameToKey( name )
ObjectValues.Remove( key )
End Sub
Function Serialize(data As JSONItem_MTC) As String
//
// Most JSON will be rather small, so we'll start with a small initial block
const kInitialMBSize = 10 * 1024
dim outMB as new MemoryBlock( kInitialMBSize )
dim outMBs() as MemoryBlock
outMBs.Append outMB
dim outIndex as integer
dim inMB as new MemoryBlock( kInMBSize )
data.Serialize( outMBs, outIndex, self, 0, inMB)
//
// We know exactly how long the last MemoryBlock's data is
// in outIndex
//
dim joiner() as string
redim joiner( outMBs.Ubound)
for i as integer = 0 to outMBs.Ubound
#if DebugBuild
if i = 1 then
i = i // A place to break
end if
#endif
outMB = outMBs( i )
outMBs( i ) = nil
dim s as string
if i = outMBs.Ubound then
s = outMB.StringValue( 0, outIndex )
else
//
// Scan for the final null
//
dim lastBytePos as integer = outMB.Size - 1
dim p as Ptr = outMB
for bytePos as integer = lastBytePos downto 0
if p.Byte( bytePos ) <> 0 then
s = outMB.StringValue( 0, bytePos + 1 )
exit for bytePos
end if
next bytePos
end if
outMB = nil
if s = "" then
raise new JSONException( "Couldn't find non-null bytes in a MemoryBlock", 0 )
end if
s = s.DefineEncoding( Encodings.UTF8 )
joiner( i ) = s
next i
dim r as string = join( joiner, "" )
return r
End Function
Protected Sub Serialize(outMBs() As MemoryBlock, ByRef outIndex As Integer, settings As JSONItem_MTC, level As Integer, inMB As MemoryBlock)
dim outMB as MemoryBlock = outMBs( outMBs.Ubound )
level = level + 1
dim notCompact as boolean = not( settings.Compact ) and settings.IndentSpacing > 0
static lotsOfSpaces as string = " "
dim indenter as string
if notCompact then
dim targetLen as integer = level * settings.IndentSpacing
while lotsOfSpaces.LenB < targetLen
lotsOfSpaces = lotsOfSpaces + lotsOfSpaces
wend
indenter = lotsOfSpaces.LeftB( targetLen )
end if
//
// Always keep at least a 1024 byte buffer at the end of the outMB
//
dim outSize as integer = outMB.Size
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
end if
dim outPtr as Ptr = outMB
if IsArray then
outPtr.Byte( outIndex ) = kOpenSquareBracket
outIndex = outIndex + 1
if ArrayValues.Ubound <> -1 then
if notCompact then
#if TargetWin32
outPtr.Byte( outIndex ) = 13
outIndex = outIndex + 1
#endif
outPtr.Byte( outIndex ) = 10
outIndex = outIndex + 1
end if
for i as integer = 0 to ArrayValues.Ubound
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
if notCompact then
outMB.StringValue( outIndex, indenter.LenB ) = indenter
outIndex = outIndex + indenter.LenB
end if
dim value as variant = ArrayValues( i )
EncodeValue( value, settings, level, outMBs, outIndex, inMB )
outMB = outMBs( outMBs.Ubound )
outSize = outMB.Size
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
end if
outPtr = outMB
if i < ArrayValues.Ubound then
outPtr.Byte( outIndex ) = kComma
outIndex = outIndex + 1
end if
if notCompact then
#if TargetWin32
outPtr.Byte( outIndex ) = 13
outIndex = outIndex + 1
#endif
outPtr.Byte( outIndex ) = 10
outIndex = outIndex + 1
end if
next i
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
if notCompact then
if outIndex > ( outSize - indenter.LenB ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
outMB.StringValue( outIndex, indenter.LenB - settings.IndentSpacing ) = indenter.LeftB( indenter.LenB - indentSpacing )
outIndex = outIndex + ( indenter.LenB - settings.IndentSpacing )
end if
end if
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
outPtr.Byte( outIndex ) = kCloseSquareBracket
outIndex = outIndex + 1
else
dim d as Dictionary = ObjectValues
outPtr.Byte( outIndex ) = kOpenCurlyBrace
outIndex = outIndex + 1
if d.Count <> 0 then
if notCompact then
#if TargetWin32
outPtr.Byte( outIndex ) = 13
outIndex = outIndex + 1
#endif
outPtr.Byte( outIndex ) = 10
outIndex = outIndex + 1
end if
dim keys() as variant = d.Keys
for i as integer = 0 to keys.Ubound
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
if notCompact then
outMB.StringValue( outIndex, indenter.LenB ) = indenter
outIndex = outIndex + indenter.LenB
end if
dim key as variant = keys( i )
dim name as string = KeyToName( key )
EncodeString( name, settings, outMBs, outIndex, inMB )
outMB = outMBs( outMBs.Ubound )
outSize = outMB.Size
if outIndex > ( outSize - 1 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
end if
outPtr = outMB
outPtr.Byte( outIndex ) = kColon
outIndex = outIndex + 1
dim value as variant = d.Value( key )
EncodeValue( value, settings, level, outMBs, outIndex, inMB )
outMB = outMBs( outMBs.Ubound )
outSize = outMB.Size
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
end if
outPtr = outMB
if i < keys.Ubound then
outPtr.Byte( outIndex ) = kComma
outIndex = outIndex + 1
end if
if notCompact then
#if TargetWin32
outPtr.Byte( outIndex ) = 13
outIndex = outIndex + 1
#endif
outPtr.Byte( outIndex ) = 10
outIndex = outIndex + 1
end if
next
if notCompact then
if outIndex > ( outSize - indenter.LenB ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
outMB.StringValue( outIndex, indenter.LenB - settings.IndentSpacing ) = indenter.LeftB( indenter.LenB - indentSpacing )
outIndex = outIndex + ( indenter.LenB - settings.IndentSpacing )
end if
end if
if outIndex > ( outSize - 1024 ) then
outMB = AppendOutMB( outMBs, outIndex )
outSize = outMB.Size
outPtr = outMB
end if
outPtr.Byte( outIndex ) = kCloseCurlyBrace
outIndex = outIndex + 1
end if
End Sub
Private Sub Validate(ByRef value As Variant)
//
// If value holds a Dictionary or Array,
// this will do the conversion and allow the new
// object to raise an exception if needed
//
dim isGood as boolean = true // Assume it is
if value.IsArray then
dim jsonArr as JSONItem_MTC
select case value.ArrayElementType
case Variant.TypeBoolean
dim arr() as boolean = value
jsonArr = arr
case Variant.TypeDouble
dim arr() as double = value
jsonArr = arr
case Variant.TypeInt32
dim arr() as Int32 = value
jsonArr = arr
case Variant.TypeInt64
dim arr() as Int64 = value
jsonArr = arr
case Variant.TypeSingle
dim arr() as single = value
jsonArr = arr
case Variant.TypeString
dim arr() as string = value
jsonArr = arr
case Variant.TypeText
dim arr() as text = value
jsonArr = arr
case Variant.TypeObject
dim arr() as object = value
jsonArr = arr
case else
isGood = false
end select
if isGood then
value = jsonArr
end if
else
select case value.Type
case Variant.TypeNil, Variant.TypeBoolean
case Variant.TypeString, Variant.TypeCString, Variant.TypePString, Variant.TypeText
case Variant.TypeDouble, Variant.TypeSingle, Variant.TypeCurrency
case Variant.TypeInt32, Variant.TypeInt64, Variant.TypeInteger
case Variant.TypeObject
select case value
case IsA JSONItem_MTC
case IsA Dictionary
dim dictJSON as JSONItem_MTC = Dictionary( value )
value = dictJSON
else
raise new JSONException( "Unrecognized Object", 11 )
end select
else
isGood = false
end
end if
if not isGood then
raise new JSONException( "Illegal Value", 10 )
end
End Sub
Function Value(index As Integer) As Variant
if not EnsureArray() then
return nil
end if
return ArrayValues( index )
End Function
Sub Value(index As Integer, Assigns value As Variant)
if not EnsureArray() then
return
end if
Validate( value )
HasSetType = kHasSetArray
if ArrayValues.Ubound < index then
ArrayValues.Append value
else
ArrayValues( index ) = value
end if
End Sub
Function Value(name As String) As Variant
if not EnsureObject() then
return nil
end if
dim key as string = NameToKey( name )
return ObjectValues.Value( key )
End Function
Sub Value(name As String, Assigns value As Variant)
if not EnsureObject then
return
end if
Validate( value )
HasSetType = kHasSetObject
dim key as string = NameToKey( name )
ObjectValues.Value( key ) = value
End Sub
Note "About"
JSONItem_MTC
This is a drop-in replacements for the native JSONItem. It emulates all its features but should
perform certain functions faster.
To use it, search for "JSONItem" within your project and replace it with "JSONItem_MTC". Then
drag the JSONItem_MTC class into your project.
Differences:
- You can add any object to JSONItem and it will throw an exception when you try to use ToString.
This class will throw that exception when you try to add the bad object.
- This class has an extra property, EncodeUnicode. By default, it is False to emulate the native class.
If set to True, it will encode all characters whose codepoints are greater than 127.
- Some error messages will be different within this class than the native class.
- As of Xojo 2014r21, ToString is significantly faster in this class than the native version.
See the Legal note for licensing information. If you do make useful modifications, please let me
know so I can include them in future versions.
Kem Tekinay
ktekinay@mactechnologies.com
http://www.mactechnologies.com
Original project at:
https://github.com/ktekinay/JSONItem_MTC
Note "Acknowledgements"
With thanks to Jeremy Cowgar for his suggestions and adding the unit testing framework.
With thanks for Paul Lefebvre of Xojo, Inc., for creating the unit testing framework in the first place.
Note "Legal"
This class was created by Kem Tekinay, MacTechnologies Consulting (ktekinay@mactechnologies.com).
It is copyright ©2015 by Kem Tekinay, all rights reserved.
This project is distributed AS-IS and no warranty of fitness for any particular purpose is expressed or implied.
The author disavows any responsibility for bad design, poor execution, or any other faults.
You may freely use or modify this project or any part within. You may distribute a modified version as long
as this notice or any other legal notice is left undisturbed and all modifications are clearly documented and
accredited. The author does not actively support this class, although comments and recommendations are
welcome.
Note "Specs (RFC)"
This class follows the specs in RFC 7159 found, among other places, here:
http://tools.ietf.org/html/rfc7159
Property Protected ArrayValues() As Variant
Property Compact As Boolean = True
Property DecimalFormat As String
Property EncodeUnicode As EncodeType = EncodeType.JavaScriptCompatible
Property EscapeSlashes As Boolean = True
Property Protected HasSetType As Integer
Property IndentSpacing As Integer = 2
Property Private LoadCS As CriticalSection
Property Protected ObjectValues As Dictionary
Property Strict As Boolean
Property Private mIsLoading As Boolean
End Class
End Project
The items on this page are in the following plugins: MBS Util Plugin.