[bmx] tiny JSON reader/writer by Warpy [ 1+ years ago ]

Started by BlitzBot, June 29, 2017, 00:28:42

Previous topic - Next topic

BlitzBot

Title : tiny JSON reader/writer
Author : Warpy
Posted : 1+ years ago

Description : from <a href="http://www.json.org/" target="_blank">http://www.json.org</a> :
JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.
This is a tiny JSON library I wrote in about an hour and a half. Amazingly, it worked first time!


Code :
Code (blitzmax) Select
Type jsondecoder
Field txt$
Field i
Field curchr$
Field things:TList

Method New()
things=New TList
End Method

Method getnext(tokens$[],onlywhitespace=1)
oldi=i
While i<Len(txt)
c$=Chr(txt[i])
i:+1
For token$=EachIn tokens
If c=token
curchr=c
Return 1
EndIf
Next
If onlywhitespace And (Not (c=" " Or c="~t" Or c="~n" Or c="~r"))
i:-1
Return 0
EndIf
Wend
i=oldi
Return 0
End Method

Function Create:jsondecoder(txt$)
j:jsondecoder=New jsondecoder
j.txt=txt
j.i=i
Return j
End Function

Method parse()
While getnext(["{","["])
Select curchr
Case "{" 'new object
o:jsonobject=parseobject()
If Not o
Print "error - couldn't parse object"
EndIf
things.addlast o
Case "[" 'new array
a:jsonarray=parsearray()
If Not a
Print "error - couldn't parse array"
EndIf
things.addlast a
End Select
Wend
End Method

Method parseobject:jsonobject()
o:jsonobject=New jsonobject
While getnext(["~q","}"])
Select curchr
Case "~q"
p:jsonpair=parsepair()
If Not p
Print "error reading pair"
EndIf
o.pairs.addlast p
If Not getnext([",","}"])
Print "error after reading pair - expected either , or }"
EndIf
If curchr="}"
Return o
EndIf
Case "}"
Return o
End Select
Wend

Print "error reading Object - expected a } at least!"
End Method

Method parsepair:jsonpair()
p:jsonpair=New jsonpair
p.name=parsestring()
If Not getnext([":"])
Print "error reading pair - expected a :"
EndIf
v:jsonvalue=parsevalue()
If Not v
Print "error reading pair - couldn't read a value"
EndIf
p.value=v
Return p
End Method

Method parsearray:jsonarray()
a:jsonarray=New jsonarray
While getnext(["~q","-","0","1","2","3","4","5","6","7","8","9","{","[","t","f","n","]"])
Select curchr
Case "~q","-","0","1","2","3","4","5","6","7","8","9","{","[","t","f","n"
i:-1
v:jsonvalue=parsevalue()
a.values.addlast v
If Not getnext([",","]"])
Print "error - expecting , or ]"
EndIf
If curchr="]"
Return a
EndIf

Case "]"
Return a
End Select
Wend
Print "error - expecting a value or ]"
End Method

Method parsestring$()
oldi=i
s$=""

While getnext(["~q",""],0)
s:+txt[oldi..i-1]
Select curchr
Case "~q"
Return s
Case ""
Select Chr(txt[i])
Case "~q"
s:+"~q"
Case ""
s:+""
Case "/"
s:+"/"
Case "b"
s:+Chr(8)
Case "f"
s:+Chr(12)
Case "n"
s:+"~n"
Case "r"
s:+"~r"
Case "t"
s:+"~t"
Case "u"
s:+parseunicode()
End Select
i:+1
End Select
oldi=i
Wend
End Method

Method parseunicode$()
n:Short=0
For t=1 To 4
n:*16
c=txt[i+t]
If c>48 And c<57
n:+c-48
ElseIf c>=65 And c<=70
n:+c-55
ElseIf c>=97 And c<=102
n:+c-87
EndIf
Next
i:+4
Return Chr(n)
End Method

Method parsevalue:jsonvalue()
If Not getnext(["~q","-","0","1","2","3","4","5","6","7","8","9","{","[","t","f","n"])
Print "error - expecting the beginning of a value"
EndIf
Select curchr
Case "~q"
s$=parsestring()
Return jsonstringvalue.Create(s,0)
Case "-","0","1","2","3","4","5","6","7","8","9"
n:Double=parsenumber()
Return jsonnumbervalue.Create(n)
Case "{"
o:jsonobject=parseobject()
Return o
Case "["
a:jsonarray=parsearray()
Return a
Case "t"
i:+3
Return jsonliteralvalue.Create(1)
Case "f"
i:+4
Return jsonliteralvalue.Create(0)
Case "n"
i:+2
Return jsonliteralvalue.Create(-1)
End Select
End Method

Method parsenumber:Double()
i:-1
sign=1
n:Double=0
Select Chr(txt[i])
Case "-"
i:+2
Return parsenumber()*(-1)
Case "0"
i:+1
If getnext(["."])
n=parsefraction()
EndIf
Case "1","2","3","4","5","6","7","8","9"
n=parseinteger()
If getnext(["."])
n:+parsefraction()
EndIf
End Select

If Chr(txt[i])="e" Or Chr(txt[i])="E"
i:+1
Select Chr(txt[i])
Case "+"
sign=1
Case "-"
sign=-1
Default
Print "error - not a + or - when reading exponent in number"
End Select
e=parseinteger()
n:*10^(sign*e)
EndIf
Print "parsed number "+String(n)
Return n
End Method

Method parsefraction:Double()
digits=0
n:Double=0
While txt[i]>=48 And txt[i]<=57 And i<Len(txt)
n:*10
n:+txt[i]-48
i:+1
digits:+1
Wend
n:/(10^digits)
If i=Len(txt)
Print "error - reached EOF while reading number"
EndIf
Print "parsed fraction "+String(n)
Return n
End Method

Method parseinteger:Double()
n:Double=0
While txt[i]>=48 And txt[i]<=57 And i<Len(txt)
n:*10
n:+txt[i]-48
i:+1
Wend
If i=Len(txt)
Print "error - reached EOF while reading number"
EndIf
Print "parsed integer "+String(n)
Return n
End Method

End Type

Type jsonvalue

Method repr$(tabs$="")
Return tabs
End Method
End Type

Type jsonobject Extends jsonvalue
Field pairs:TList

Method New()
pairs=New TList
End Method

Method addnewpair(txt$,value:jsonvalue)
pairs.addlast jsonpair.Create(txt,value)
End Method

Method repr$(tabs$="")
t$="{"
ntabs$=tabs+"~t"
op:jsonpair=Null
For p:jsonpair=EachIn pairs
If op Then t:+","
t:+"~n"+ntabs+p.repr(ntabs)
op=p
Next
t:+"~n"+tabs+"}"
Return t
End Method

Method getvalue:jsonvalue(name$)
For p:jsonpair=EachIn pairs
If p.name=name
Return p.value
EndIf
Next
End Method

Method getstringvalue$(name$)
v:jsonstringvalue=jsonstringvalue(getvalue(name))
If v
Return v.txt
EndIf
End Method

Method getnumbervalue:Double(name$)
v:jsonnumbervalue=jsonnumbervalue(getvalue(name))
If v
Return v.number
EndIf
End Method

Method getliteralvalue(name$)
v:jsonliteralvalue=jsonliteralvalue(getvalue(name))
If v
Return v.value
EndIf
End Method

Method getarrayvalue:jsonarray(name$)
v:jsonarray=jsonarray(getvalue(name))
Return v
End Method

Method getobjectvalue:jsonobject(name$)
v:jsonobject=jsonobject(getvalue(name))
Return v
End Method


End Type

Type jsonpair
Field name$,value:jsonvalue

Function Create:jsonpair(name$,value:jsonvalue)
p:jsonpair=New jsonpair
p.name=name
p.value=value
Return p
End Function

Method repr$(tabs$="")
t$="~q"+name+"~q : "
For i=1 To (Len(t)+7)/8
tabs:+"~t"
Next
middo$=""
For i=1 To (8-(Len(t) Mod 8))
middo:+" "
Next
Return t+middo+value.repr(tabs)
End Method
End Type

Type jsonarray Extends jsonvalue
Field values:TList

Method New()
values=New TList
End Method

Method repr$(tabs$="")
t$="["
ntabs$=tabs+"~t"
ov:jsonvalue=Null
For v:jsonvalue=EachIn values
If ov Then t:+","
t:+"~n"+ntabs+v.repr(ntabs)
ov=v
Next
t:+"~n"+tabs+"]"
Return t
End Method
End Type


Type jsonstringvalue Extends jsonvalue
Field txt$

Function Create:jsonstringvalue(txt$,pretty=1)
jsv:jsonstringvalue=New jsonstringvalue

If pretty
otxt$=""
i=0
For i=0 To Len(txt)-1
Select Chr(txt[i])
Case "~q"
otxt:+"~q"
Case ""
otxt:+"\"
Case "/"
otxt:+"/"
Case Chr(8)
otxt:+""
Case Chr(12)
otxt:+"f"
Case "~n"
otxt:+"
"
Case "~r"
otxt:+""
Case "~t"
otxt:+" "
Default
otxt:+Chr(txt[i])
End Select
Next
jsv.txt=otxt
Else
jsv.txt=txt
EndIf
Return jsv
End Function

Method repr$(tabs$="")
Return "~q"+txt+"~q"
End Method
End Type

Type jsonnumbervalue Extends jsonvalue
Field number:Double

Function Create:jsonnumbervalue(n:Double)
jnv:jsonnumbervalue=New jsonnumbervalue
jnv.number=n
Return jnv
End Function

Method repr$(tabs$="")
Return String(number)
End Method
End Type

Type jsonliteralvalue Extends jsonvalue
Field value
'1 - true
'0 - false
'-1 - nil

Function Create:jsonliteralvalue(value)
jlv:jsonliteralvalue=New jsonliteralvalue
jlv.value=value
Return jlv
End Function

Method repr$(tabs$="")
Select value
Case 1
Return "true"
Case 0
Return "false"
Case -1
Return "nil"
End Select
End Method
End Type



'EXAMPLE
f:TStream=ReadFile("json.txt")
txt$=""
While Not Eof(f)
txt:+f.ReadLine()
Wend

j:jsondecoder=jsondecoder.Create(txt)
j.parse()


For v:jsonvalue=EachIn j.things
Print v.repr()
Next


Comments :


plash(Posted 1+ years ago)

 Woah.. now we have three!


Warpy(Posted 1+ years ago)

 Yep!


Bobysait(Posted 1+ years ago)

 It's a bmx file, not bb.Whatever, nice job


TomToad(Posted 2 months ago)

 Thanks for the code, almost exactly what I needed.  Not SuperStrict compatible, so here is your code modified to be SuperStrict.
'; ID: 2300
'; Author: Warpy
'; Date: 2008-08-21 15:59:54
'; Title: tiny JSON reader/writer
'; Description: decodes / encodes JSON data - see www.json.org

Type jsondecoder
Field txt:String
Field i:Int
Field curchr:String
Field things:TList

Method New()
things=New TList
End Method

Method getnext:Int(tokens:String[],onlywhitespace:Int=1)
Local oldi:Int=i
While i<Len(txt)
Local c:String=Chr(txt[i])
i:+1
For Local token:String=EachIn tokens
If c=token
curchr=c
Return 1
EndIf
Next
If onlywhitespace And (Not (c=" " Or c="~t" Or c="~n" Or c="~r"))
i:-1
Return 0
EndIf
Wend
i=oldi
Return 0
End Method

Function Create:jsondecoder(txt:String)
Local j:jsondecoder=New jsondecoder
j.txt=txt
j.i=0
Return j
End Function

Method parse()
While getnext(["{","["])
Select curchr
Case "{" 'new object
Local o:jsonobject=parseobject()
If Not o
Print "error - couldn't parse object"
EndIf
things.addlast o
Case "[" 'new array
Local a:jsonarray=parsearray()
If Not a
Print "error - couldn't parse array"
EndIf
things.addlast a
End Select
Wend
End Method

Method parseobject:jsonobject()
Local o:jsonobject=New jsonobject
While getnext(["~q","}"])
Select curchr
Case "~q"
Local p:jsonpair=parsepair()
If Not p
Print "error reading pair"
EndIf
o.pairs.addlast p
If Not getnext([",","}"])
Print "error after reading pair - expected either , or }"
EndIf
If curchr="}"
Return o
EndIf
Case "}"
Return o
End Select
Wend

Print "error reading Object - expected a } at least!"
End Method

Method parsepair:jsonpair()
Local p:jsonpair=New jsonpair
p.name=parsestring()
If Not getnext([":"])
Print "error reading pair - expected a :"
EndIf
Local v:jsonvalue=parsevalue()
If Not v
Print "error reading pair - couldn't read a value"
EndIf
p.value=v
Return p
End Method

Method parsearray:jsonarray()
Local a:jsonarray=New jsonarray
While getnext(["~q","-","0","1","2","3","4","5","6","7","8","9","{","[","t","f","n","]"])
Select curchr
Case "~q","-","0","1","2","3","4","5","6","7","8","9","{","[","t","f","n"
i:-1
Local v:jsonvalue=parsevalue()
a.values.addlast v
If Not getnext([",","]"])
Print "error - expecting , or ]"
EndIf
If curchr="]"
Return a
EndIf

Case "]"
Return a
End Select
Wend
Print "error - expecting a value or ]"
End Method

Method parsestring:String()
Local oldi:Int=i
Local s:String=""

While getnext(["~q",""],0)
s:+txt[oldi..i-1]
Select curchr
Case "~q"
Return s
Case ""
Select Chr(txt[i])
Case "~q"
s:+"~q"
Case ""
s:+""
Case "/"
s:+"/"
Case "b"
s:+Chr(8)
Case "f"
s:+Chr(12)
Case "n"
s:+"~n"
Case "r"
s:+"~r"
Case "t"
s:+"~t"
Case "u"
s:+parseunicode()
End Select
i:+1
End Select
oldi=i
Wend
End Method

Method parseunicode:String()
Local n:Short=0
For Local t:Int=1 To 4
n:*16
Local c:Int=txt[i+t]
If c>48 And c<57
n:+c-48
ElseIf c>=65 And c<=70
n:+c-55
ElseIf c>=97 And c<=102
n:+c-87
EndIf
Next
i:+4
Return Chr(n)
End Method

Method parsevalue:jsonvalue()
If Not getnext(["~q","-","0","1","2","3","4","5","6","7","8","9","{","[","t","f","n"])
Print "error - expecting the beginning of a value"
EndIf
Select curchr
Case "~q"
Local s:String=parsestring()
Return jsonstringvalue.Create(s,0)
Case "-","0","1","2","3","4","5","6","7","8","9"
Local n:Double=parsenumber()
Return jsonnumbervalue.Create(n)
Case "{"
Local o:jsonobject=parseobject()
Return o
Case "["
Local a:jsonarray=parsearray()
Return a
Case "t"
i:+3
Return jsonliteralvalue.Create(1)
Case "f"
i:+4
Return jsonliteralvalue.Create(0)
Case "n"
i:+2
Return jsonliteralvalue.Create(-1)
End Select
End Method

Method parsenumber:Double()
i:-1
Local sign:Int=1
Local n:Double=0
Select Chr(txt[i])
Case "-"
i:+2
Return parsenumber()*(-1)
Case "0"
i:+1
If getnext(["."])
n=parsefraction()
EndIf
Case "1","2","3","4","5","6","7","8","9"
n=parseinteger()
If getnext(["."])
n:+parsefraction()
EndIf
End Select

If Chr(txt[i])="e" Or Chr(txt[i])="E"
i:+1
Select Chr(txt[i])
Case "+"
sign=1
Case "-"
sign=-1
Default
Print "error - not a + or - when reading exponent in number"
End Select
Local e:Int=parseinteger()
n:*10^(sign*e)
EndIf
DebugLog "parsed number "+String(n)
Return n
End Method

Method parsefraction:Double()
Local digits:Int=0
Local n:Double=0
While txt[i]>=48 And txt[i]<=57 And i<Len(txt)
n:*10
n:+txt[i]-48
i:+1
digits:+1
Wend
n:/(10^digits)
If i=Len(txt)
Print "error - reached EOF while reading number"
EndIf
DebugLog "parsed fraction "+String(n)
Return n
End Method

Method parseinteger:Double()
Local n:Double=0
While txt[i]>=48 And txt[i]<=57 And i<Len(txt)
n:*10
n:+txt[i]-48
i:+1
Wend
If i=Len(txt)
Print "error - reached EOF while reading number"
EndIf
DebugLog "parsed integer "+String(n)
Return n
End Method

End Type

Type jsonvalue

Method repr:String(tabs:String="")
Return tabs
End Method
End Type

Type jsonobject Extends jsonvalue
Field pairs:TList

Method New()
pairs=New TList
End Method

Method addnewpair(txt:String,value:jsonvalue)
pairs.addlast jsonpair.Create(txt,value)
End Method

Method repr:String(tabs:String="")
Local t:String="{"
Local ntabs:String=tabs+"~t"
Local op:jsonpair=Null
For Local p:jsonpair=EachIn pairs
If op Then t:+","
t:+"~n"+ntabs+p.repr(ntabs)
op=p
Next
t:+"~n"+tabs+"}"
Return t
End Method

Method getvalue:jsonvalue(name:String)
For Local p:jsonpair=EachIn pairs
If p.name=name
Return p.value
EndIf
Next
End Method

Method getstringvalue:String(name:String)
Local v:jsonstringvalue=jsonstringvalue(getvalue(name))
If v
Return v.txt
EndIf
End Method

Method getnumbervalue:Double(name:String)
Local v:jsonnumbervalue=jsonnumbervalue(getvalue(name))
If v
Return v.number
EndIf
End Method

Method getliteralvalue:Int(name:String)
Local v:jsonliteralvalue=jsonliteralvalue(getvalue(name))
If v
Return v.value
EndIf
End Method

Method getarrayvalue:jsonarray(name:String)
Local v:jsonarray=jsonarray(getvalue(name))
Return v
End Method

Method getobjectvalue:jsonobject(name:String)
Local v:jsonobject=jsonobject(getvalue(name))
Return v
End Method


End Type

Type jsonpair
Field name:String,value:jsonvalue

Function Create:jsonpair(name:String,value:jsonvalue)
Local p:jsonpair=New jsonpair
p.name=name
p.value=value
Return p
End Function

Method repr:String(tabs:String="")
Local t:String="~q"+name+"~q : "
For Local i:Int=1 To (Len(t)+7)/8
tabs:+"~t"
Next
Local middo:String=""
For Local i:Int=1 To (8-(Len(t) Mod 8))
middo:+" "
Next
Return t+middo+value.repr(tabs)
End Method
End Type

Type jsonarray Extends jsonvalue
Field values:TList

Method New()
values=New TList
End Method

Method repr:String(tabs:String="")
Local t:String="["
Local ntabs:String=tabs+"~t"
Local ov:jsonvalue=Null
For Local v:jsonvalue=EachIn values
If ov Then t:+","
t:+"~n"+ntabs+v.repr(ntabs)
ov=v
Next
t:+"~n"+tabs+"]"
Return t
End Method
End Type


Type jsonstringvalue Extends jsonvalue
Field txt:String

Function Create:jsonstringvalue(txt:String,pretty:Int=1)
Local jsv:jsonstringvalue=New jsonstringvalue

If pretty
Local otxt:String=""
Local i:Int=0
For i=0 To Len(txt)-1
Select Chr(txt[i])
Case "~q"
otxt:+"~q"
Case ""
otxt:+"\"
Case "/"
otxt:+"/"
Case Chr(8)
otxt:+""
Case Chr(12)
otxt:+"f"
Case "~n"
otxt:+"
"
Case "~r"
otxt:+""
Case "~t"
otxt:+" "
Default
otxt:+Chr(txt[i])
End Select
Next
jsv.txt=otxt
Else
jsv.txt=txt
EndIf
Return jsv
End Function

Method repr:String(tabs:String="")
Return "~q"+txt+"~q"
End Method
End Type

Type jsonnumbervalue Extends jsonvalue
Field number:Double

Function Create:jsonnumbervalue(n:Double)
Local jnv:jsonnumbervalue=New jsonnumbervalue
jnv.number=n
Return jnv
End Function

Method repr:String(tabs:String="")
Return String(number)
End Method
End Type

Type jsonliteralvalue Extends jsonvalue
Field value:Int
'1 - true
'0 - false
'-1 - nil

Function Create:jsonliteralvalue(value:Int)
Local jlv:jsonliteralvalue=New jsonliteralvalue
jlv.value=value
Return jlv
End Function

Method repr:String(tabs:String="")
Select value
Case 1
Return "true"
Case 0
Return "false"
Case -1
Return "nil"
End Select
End Method
End Type