November 28, 2020, 02:04:23 PM

Author Topic: [bmx] JSON Reader/Writer by grable [ 1+ years ago ]  (Read 1015 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
[bmx] JSON Reader/Writer by grable [ 1+ years ago ]
« on: June 29, 2017, 12:28:42 AM »
Title : JSON Reader/Writer
Author : grable
Posted : 1+ years ago

Description : ** UPDATED: 2 May 2017
fixed: off-by-one error with arrays. Element counter incorrectly used "," as a counter.
JSON is short for JavaScript Object Notation, and is very nice for config files and the like (or a more readable alternative to XML files)

It looks something like this:
Code: [Select]
{
string: 'abc',
number: 1.0,
boolean: true,
object: {
field: null,
morestuff: "blablabla"
},
array: [1,2,3]
}

Go here for more info on JSON <a href="http://json.org/" target="_blank">http://json.org[/url]

More info on usage, see the top of the source.

Please let me know if there is anything missing or incorrect =) [/i]

Code :
Code: BlitzMax
  1. Rem
  2. **********************************************************************************************************************************
  3. * JSON Reader/Writer and generic handling
  4. *
  5.        
  6. **********************************************************************************************************************************
  7. * TJSONValue is the generic container for all JSON data types
  8. *      
  9.         Type TJSONValue
  10.                 ' determines the type of value
  11.                 Field Class:Int
  12.                
  13.                 ' returns value by index (only valid for arrays)
  14.                 Method GetByIndex:TJSONValue( index:Int)
  15.                
  16.                 ' returns value by name (only valid for objects)
  17.                 Method GetByName:TJSONValue( name:Object)
  18.                
  19.                 Method SetByIndex( index:Int, value:TJSONValue)
  20.                 Method SetByName( name:Object, value:TJSONValue)
  21.                
  22.                 ' lookup value by string (either a number or a name, valid for arrays & objects)
  23.                 Method LookupValue:TJSONValue( value:Object)
  24.                
  25.                 ' returns properly indented source representation of JSON data
  26.                 Method ToSource:String( level:Int = 0)
  27.                
  28.                 ' returns JSON data as string
  29.                 Method ToString:String()
  30.         EndType
  31.        
  32.         Type TJSONNumber Extends TJSONValue
  33.                 Field Value:Double
  34.         EndType
  35.        
  36.         Type TJSONString Extends TJSONValue
  37.                 Field Value:String
  38.         EndType
  39.        
  40.         Type TJSONBoolean Extends TJSONValue
  41.                 Field Value:Int
  42.         EndType
  43.        
  44.         Type TJSONObject Extends TJSONValue
  45.                 Field Items:TMap ' holds actual fields
  46.                 Field List:TList ' holds field order
  47.         EndType
  48.        
  49.         Type TJSONArray Extends TJSONValue
  50.                 Field Items:TJSONValue[]
  51.         EndType
  52.        
  53.         The methods are implemented in the various subclasses.
  54.                
  55. **********************************************************************************************************************************
  56. * TJSON handles all reading/writing of json data, and allows for easy acces to elements via "paths"
  57. *      
  58.         Type TJSON
  59.                 ' the root JSON value
  60.                 Field Root:TJSONValue
  61.                
  62.                 ' create a new JSON from any source
  63.                 Function Create:TJSON( source:Object)
  64.        
  65.                 ' read JSON data from a TJSONValue, TStream or String
  66.                 Method Read:TJSONValue( source:Object)
  67.                
  68.                 ' write JSON data to a TStream or a file (as String)
  69.                 Method Write( dest:Object)
  70.                
  71.                 ' parses a string into its JSON data representation
  72.                 Method ParseString:TJSONValue( s:Object)
  73.                
  74.                 ' lookup a JSON value at at specified path, returns NULL on failure
  75.                 Method Lookup:TJSONValue( path:String)
  76.        
  77.                 ' sets a JSON value at path to value, value can be a TJSONValue or JSON data as a string
  78.                 Method SetValue( path:String, value:Object)
  79.                
  80.                 ' returns the json value at specified
  81.                 Method GetValue:TJSONValue( path:String)       
  82.                
  83.                 ' returns blitz specific types from specified paths
  84.                 Method GetNumber:Double( path:String)
  85.                 Method GetString:String( path:String)
  86.                 Method GetBoolean:Int( path:String)
  87.                
  88.                 ' returns only these specific objects
  89.                 Method GetObject:TJSONObject( path:String)
  90.                 Method GetArray:TJSONArray( path:String)
  91.        
  92.                 ' get a blitz array from a JSON array or NULL on failure
  93.                 Method GetArrayInt:Int[]( path:String)
  94.                 Method GetArrayDouble:Double[]( path:String)
  95.                 Method GetArrayString:String[]( path:String)
  96.         EndType
  97.        
  98. **********************************************************************************************************************************
  99. * PATHS
  100. *              
  101.         identifiers are seperated with ".", and has special syntax for array indices
  102.                
  103.         example:
  104.                 "users.joe.age"         ' direct access
  105.                 "users.joe.medals.0"    ' array index, arrays are 0 based
  106.        
  107. **********************************************************************************************************************************
  108. * usage example:
  109. *              
  110.         Local json:TJSON = TJSON.Create( "{ string: 'abc', number: 1.0, boolean: true, object: { field: null }, array: [1,2,3] }")
  111.                
  112.         Print json.GetValue("string").ToString()                        ' prints "abc"
  113.         Print json.GetValue("object.field").ToString()  ' prints "null"
  114.         Print json.GetValue("array").ToString()                 ' prints "[1,2,3]"
  115.         Print json.GetValue("array.1").ToString()                       ' prints "2"
  116.                
  117. **********************************************************************************************************************************
  118. * NOTES
  119. *
  120.         ** most of the TJSON.GetXXX methods returns the JSON NULL type on failure if not specified
  121.         ** all identifiers are CASE SENSITIVE
  122.         ** the parser is as close as i could get it with my current understanding of JSON, please let me know if i missed something
  123.         ** see the bottom of this source file for more examples, or check out http://json.org/ for more info on JSON
  124.        
  125. ***********************************************************    
  126. * INFO
  127. *
  128.         author: grable
  129.         email : grable0@gmail.com
  130.        
  131. EndRem
  132.  
  133. SuperStrict
  134.  
  135. Import BRL.LinkedList
  136. Import BRL.Map
  137.  
  138.  
  139. '
  140. ' JSON value classes
  141. '
  142. Const JSON_NULL:Int     = 1
  143. Const JSON_OBJECT:Int   = 2
  144. Const JSON_ARRAY:Int    = 3
  145. Const JSON_STRING:Int   = 4
  146. Const JSON_NUMBER:Int   = 5
  147. Const JSON_BOOLEAN:Int  = 6
  148.  
  149.  
  150. '
  151. ' used by TJSON / TJSONObject for identifier lookup
  152. '
  153. Type TJSONKey
  154.         Field Value:String
  155.        
  156.         Method ToString:String()
  157.                 Return "~q" + Value + "~q"
  158.         EndMethod
  159.        
  160.         Method Compare:Int( o:Object)
  161.                 Local key:TJSONKey = TJSONKey(o)
  162.                 If key Then Return Value.Compare( key.Value)           
  163.                 If String(o) Then Return Value.Compare( o)
  164.                 Return 1
  165.         EndMethod
  166. EndType
  167.  
  168.  
  169. '
  170. ' JSON Value objects
  171. '
  172. Type TJSONValue Abstract
  173.         Field Class:Int
  174.        
  175.         Method GetByIndex:TJSONValue( index:Int)
  176.                 Return Null
  177.         EndMethod
  178.        
  179.         Method GetByName:TJSONValue( name:Object)
  180.                 Return Null
  181.         EndMethod      
  182.        
  183.         Method SetByIndex( index:Int, value:TJSONValue)
  184.         EndMethod
  185.        
  186.         Method SetByName( name:Object, value:TJSONValue)
  187.         EndMethod      
  188.        
  189.         Method LookupValue:TJSONValue( value:Object)
  190.                 Return Self
  191.         EndMethod
  192.        
  193.         Method ToSource:String( level:Int = 0) Abstract
  194. EndType
  195.  
  196. Type TJSONObject Extends TJSONValue
  197.         Field Items:TMap = New TMap
  198.         Field List:TList = New TList ' for keeping the order of fields
  199.        
  200.         Method New()
  201.                 Class = JSON_OBJECT
  202.         EndMethod      
  203.  
  204.         Method ToString:String()
  205.                 Local s:String, lines:Int = 0
  206.                 If List.Count() <= 0 Then Return "{}"
  207.                 For Local o:TNode = EachIn List
  208.                         If lines > 0 Then s :+ ", "
  209.                         s :+ o._key.ToString() +": "
  210.                         Local jsv:TJSONValue = TJSONValue(o._value)
  211.                         If jsv.Class = JSON_STRING Then
  212.                                 s :+ jsv.ToSource()
  213.                         Else
  214.                                 s :+ jsv.ToString()
  215.                         EndIf
  216.                         lines :+ 1
  217.                 Next
  218.                 Return "{ "+ s +" }"
  219.         EndMethod
  220.        
  221.         Method ToSource:String( level:Int = 0)
  222.                 Local s:String, lines:Int = 0
  223.                 If List.Count() <= 0 Then Return "{}"
  224.                 For Local o:TNode = EachIn List
  225.                         If lines > 0 Then s :+ ",~n" + RepeatString( "~t", level + 1)                  
  226.                         s :+ o._key.ToString() +": "+ TJSONValue(o._value).ToSource( level + 1)
  227.                         lines :+ 1
  228.                 Next
  229.                 If lines > 1 Then Return "{~n"+ RepeatString( "~t", level + 1) + s + "~n" + RepeatString( "~t", level) + "}"
  230.                 Return "{ "+ s +" }"
  231.         EndMethod              
  232.        
  233.         Method GetByName:TJSONValue( name:Object)
  234.                 Return TJSONValue( Items.ValueForKey( name))
  235.         EndMethod
  236.        
  237.         Method SetByName( name:Object, value:TJSONValue)
  238.                 Local node:TNode
  239.                 If TJSONKey(name) Then
  240.                         Items.Insert( name, value)
  241.                         node = Items._FindNode( name)
  242.                         If Not List.Contains( node) Then List.AddLast( node)
  243.                 ElseIf String(name) Then
  244.                         Local s:String = String(name)
  245.                         If s.Length > 0 Then
  246.                                 Items.Insert( s, value)
  247.                                 node = Items._FindNode( s)
  248.                                 If Not List.Contains( node) Then List.AddLast( node)
  249.                         EndIf
  250.                 EndIf
  251.         EndMethod      
  252.        
  253.         Method LookupValue:TJSONValue( value:Object)
  254.                 If TJSONKey(value) Then
  255.                         Return GetByName( value)
  256.                 ElseIf String(value) Then
  257.                         If Not IsNumber( String(value)) Then Return GetByName( value)
  258.                 EndIf
  259.         EndMethod
  260. EndType
  261.  
  262. Type TJSONArray Extends TJSONValue
  263.         Field Items:TJSONValue[]
  264.         Field AutoGrow:Int = True
  265.        
  266.         Function Create:TJSONArray( size:Int)
  267.                 Local jso:TJSONArray = New TJSONArray
  268.                 jso.Items = New TJSONValue[ size]
  269.                 Return jso
  270.         EndFunction
  271.        
  272.         Method New()
  273.                 Class = JSON_ARRAY
  274.         EndMethod      
  275.  
  276.         Method ToString:String()
  277.                 Local s:String, lines:Int = 0
  278.                 If Items.Length <= 0 Then Return "[]"
  279.                 For Local o:TJSONValue = EachIn Items
  280.                         If lines > 0 Then s :+ ", "                    
  281.                         If o.Class = JSON_STRING Then
  282.                                 s :+ o.ToSource()
  283.                         Else
  284.                                 s :+ o.ToString()
  285.                         EndIf                  
  286.                         lines :+ 1
  287.                 Next
  288.                 Return "[ "+ s +" ]"
  289.         EndMethod      
  290.        
  291.         Method ToSource:String( level:Int = 0)
  292.                 If Items.Length <= 0 Then Return "[]"
  293.                 Local s:String, lines:Int = 0
  294.                 For Local o:TJSONValue = EachIn Items
  295.                         If lines > 0 Then s :+ ",~n" + RepeatString( "~t", level + 1)
  296.                         s :+ o.ToSource( level + 1)
  297.                         lines :+ 1
  298.                 Next
  299.                 If lines > 1 Then Return "[~n" + RepeatString( "~t", level + 1) + s + "~n" + RepeatString( "~t", level) + "]"
  300.                 Return "[ "+ s +" ]"
  301.         EndMethod
  302.        
  303.         Method GetByIndex:TJSONValue( index:Int)
  304.                 If (index >= 0) And (index < Items.Length) Then
  305.                         Return TJSONValue( Items[ index])
  306.                 EndIf
  307.         EndMethod
  308.        
  309.         Method SetByIndex( index:Int, value:TJSONValue)
  310.                 If (index >= 0) And (index < Items.Length) Then
  311.                         Items[ index] = value
  312.                 ElseIf AutoGrow And (Index >= Items.Length) Then
  313.                         Local oldlen:Int = Items.Length
  314.                         Items = Items[..index + 1]
  315.                         For Local i:Int = oldlen Until Items.Length
  316.                                 Items[i] = TJSON.NIL
  317.                         Next
  318.                         Items[index] = value
  319.                 EndIf
  320.         EndMethod
  321.        
  322.         Method LookupValue:TJSONValue( value:Object)
  323.                 If TJSONKey(value) Then
  324.                         Local s:String = TJSONKey(value).Value
  325.                         If IsNumber( s) Then Return GetByIndex( s.ToInt())
  326.                 ElseIf String(value) Then
  327.                         If IsNumber( String(value)) Then Return GetByIndex( String(value).ToInt())
  328.                 EndIf  
  329.         EndMethod
  330. EndType
  331.  
  332.  
  333. Type TJSONString Extends TJSONValue
  334.         Field Value:String     
  335.        
  336.         Method New()
  337.                 Class = JSON_STRING
  338.         EndMethod
  339.        
  340.         Function Create:TJSONString( value:String)
  341.                 Local jso:TJSONString = New TJSONString
  342.                 jso.Value = value
  343.                 Return jso
  344.         EndFunction
  345.                
  346.         Method ToString:String()
  347.                 Return Value
  348.         EndMethod
  349.        
  350.         Method ToSource:String( level:Int = 0)
  351.                 Return "~q" + Value + "~q"
  352.         EndMethod
  353. EndType
  354.  
  355. Type TJSONNumber Extends TJSONValue
  356.         Field Value:Double
  357.        
  358.         Method New()
  359.                 Class = JSON_NUMBER
  360.         EndMethod      
  361.  
  362.         Function Create:TJSONNumber( value:Double)
  363.                 Local jso:TJSONNumber = New TJSONNumber
  364.                 jso.Value = value
  365.                 Return jso
  366.         EndFunction
  367.        
  368.         Method ToString:String()
  369.                 Return DoubleToString( Value)
  370.         EndMethod      
  371.        
  372.         Method ToSource:String( level:Int = 0)
  373.                 Return DoubleToString( Value)
  374.         EndMethod              
  375. EndType
  376.  
  377. Type TJSONBoolean Extends TJSONValue
  378.         Field Value:Int
  379.        
  380.         Method New()
  381.                 Class = JSON_BOOLEAN
  382.         EndMethod      
  383.        
  384.         Function Create:TJSONBoolean( value:Int)
  385.                 Local jso:TJSONBoolean = New TJSONBoolean
  386.                 jso.Value = value
  387.                 Return jso
  388.         EndFunction
  389.        
  390.         Method ToString:String()
  391.                 If Value Then Return "true"
  392.                 Return "false"
  393.         EndMethod      
  394.        
  395.         Method ToSource:String( level:Int = 0)
  396.                 If Value Then Return "true"
  397.                 Return "false"
  398.         EndMethod              
  399. EndType
  400.  
  401. Type TJSONNull Extends TJSONValue
  402.         Method New()
  403.                 Class = JSON_NULL
  404.         EndMethod
  405.  
  406.         Method ToString:String()
  407.                 Return "null"
  408.         EndMethod
  409.        
  410.         Method ToSource:String( level:Int = 0)
  411.                 Return "null"
  412.         EndMethod      
  413. EndType
  414.  
  415.  
  416.  
  417. '
  418. ' Parses any string into its JSONValue representation
  419. '
  420. Type TJSONParser
  421.         Const ARRAY_GROW_SIZE:Int = 32
  422.  
  423.         Field Source:String
  424.         Field Index:Int
  425.         Field MakeLowerCase:Int
  426.        
  427.         Method Parse:TJSONValue()
  428.                 Const OBJECT_START:Byte = Asc("{")
  429.                 Const OBJECT_STOP:Byte = Asc("}")              
  430.                 Const ARRAY_START:Byte = Asc("[")
  431.                 Const ARRAY_STOP:Byte = Asc("]")
  432.                 Const FIELD_SEP:Byte = Asc(":")
  433.                 Const ELEM_SEP:Byte = Asc(",")
  434.                 Const IDENT_START1:Byte = Asc("a")
  435.                 Const IDENT_STOP1:Byte = Asc("z")
  436.                 Const IDENT_START2:Byte = Asc("A")
  437.                 Const IDENT_STOP2:Byte = Asc("Z")
  438.                 Const UNDERSCORE:Byte = Asc("_")
  439.                 Const MINUS:Byte = Asc("-")            
  440.                 Const NUMBER_START:Byte = Asc("0")
  441.                 Const NUMBER_STOP:Byte = Asc("9")
  442.                 Const NUMBER_SEP:Byte = Asc(".")
  443.                 Const STRING_START1:Byte = Asc("~q")
  444.                 Const STRING_START2:Byte = Asc("'")
  445.                 Const STRING_ESC:Byte = Asc("")
  446.                 Const SPACE:Byte = Asc(" ")
  447.                 Const TAB:Byte = Asc("~t")
  448.                 Const CR:Byte = Asc("~r")
  449.                 Const LF:Byte = Asc("~n")
  450.                
  451.                 Local c:Byte
  452.                 ' skip whitspace & crlf        
  453.                 While Index < Source.Length
  454.                         c = Source[Index]                      
  455.                         If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
  456.                                 Index :+ 1
  457.                                 Continue
  458.                         EndIf
  459.                         Exit
  460.                 Wend
  461.                 ' at end allready ?
  462.                 If (Index >= Source.Length) Or (Source[Index] = 0) Then Return Null
  463.                
  464.                 c = Source[Index]
  465.                 If c = OBJECT_START Then
  466.                         ' OBJECT
  467.                         Local jso:TJSONObject = New TJSONObject
  468.                         Index :+ 1                     
  469.                         While Index < Source.Length
  470.                                 ' skip whitespace & crlf
  471.                                 While Index < Source.Length
  472.                                         c = Source[Index]                      
  473.                                         If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
  474.                                                 Index :+ 1
  475.                                                 Continue
  476.                                         EndIf
  477.                                         Exit
  478.                                 Wend
  479.                                
  480.                                 If c = ELEM_SEP Then
  481.                                         Index :+ 1
  482.                                 ElseIf c = OBJECT_STOP
  483.                                         Index :+ 1
  484.                                         ' return json object
  485.                                         Return jso
  486.                                 Else                           
  487.                                         Local start:Int = Index, idinstr:Int = False
  488.                                         Local name:String
  489.                                         If c = STRING_START1 Or c = STRING_START2 Then                                         
  490.                                                 ' get name enclosed in string tags
  491.                                                 Local strchar:Byte = c
  492.                                                 Index :+ 1
  493.                                                 start = Index
  494.                                                 While (Index < Source.Length) And (Source[Index] <> strchar)
  495.                                                         If Source[Index] = STRING_ESC Then
  496.                                                                 Index :+ 1
  497.                                                         EndIf
  498.                                                         Index :+ 1
  499.                                                 Wend
  500.                                                 name = Source[start..Index]                                    
  501.                                                 ' escape string                
  502.                                                 'name = name.Replace( "/", "/") ' wtf???
  503.                                                 name = name.Replace( "~q", "~q")
  504.                                                 name = name.Replace( "'", "'")
  505.                                                 name = name.Replace( "  ", "~t")
  506.                                                 name = name.Replace( "
  507. ", "~r")
  508.                                                 name = name.Replace( "
  509. ", "~n")
  510.                                                 name = name.Replace( "\", "")
  511.                                                 Index :+ 1
  512.                                                 idinstr = True
  513.                                         Else
  514.                                                 ' get name as an identifier
  515.                                                 Index :+ 1
  516.                                                 While Index < Source.Length
  517.                                                         c = Source[Index]
  518.                                                         If ((c >= IDENT_START1) And (c <= IDENT_STOP1)) Or ((c >= IDENT_START2) And (c <= IDENT_STOP2)) Or ..
  519.                                                                 ((c >= NUMBER_START) And (c <= NUMBER_STOP)) Or (c = UNDERSCORE) Or (c = MINUS) Then
  520.                                                                 Index :+ 1
  521.                                                                 Continue
  522.                                                         EndIf
  523.                                                         name = Source[start..Index]
  524.                                                         Exit
  525.                                                 Wend
  526.                                         EndIf                                                                  
  527.                                         ' skip whitespace & crlf
  528.                                         While Index < Source.Length
  529.                                                 c = Source[Index]                      
  530.                                                 If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
  531.                                                         Index :+ 1
  532.                                                         Continue
  533.                                                 EndIf
  534.                                                 Exit
  535.                                         Wend
  536.                                         ' check for field seperator
  537.                                         If c <> FIELD_SEP Then
  538.                                                 Error( "expected field seperator ~q:~q")
  539.                                                 Return Null
  540.                                         EndIf
  541.                                         Index :+ 1
  542.                                         ' parse value
  543.                                         Local val:TJSONValue = Parse()
  544.                                         If val = Null Then Return Null
  545.                                         If idinstr Then
  546.                                                 Local key:TJSONKey = New TJSONKey
  547.                                                 key.Value = name
  548.                                                 jso.SetByName( key, val)
  549.                                         Else
  550.                                                 jso.SetByName( name, val)
  551.                                         EndIf
  552.                                 EndIf
  553.                         Wend
  554.                 ElseIf c = ARRAY_START Then
  555.                         ' ARRAY
  556.                         Local jso:TJSONArray = TJSONArray.Create( ARRAY_GROW_SIZE)
  557.                         Local count:Int = 0
  558.                         Index :+ 1                     
  559.                         While Index < Source.Length
  560.                                 ' skip whitespace & crlf
  561.                                 While Index < Source.Length
  562.                                         c = Source[Index]                      
  563.                                         If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
  564.                                                 Index :+ 1
  565.                                                 Continue
  566.                                         EndIf
  567.                                         Exit
  568.                                 Wend   
  569.                                 ' parse value
  570.                                 If c = ELEM_SEP Then
  571.                                         Index :+ 1
  572.                                 ElseIf c = ARRAY_STOP Then
  573.                                         ' return json array
  574.                                         Index :+ 1
  575.                                         If count = 0 Then
  576.                                                 jso.Items = Null
  577.                                         Else
  578.                                                 jso.Items = jso.Items[..count]                                 
  579.                                         EndIf
  580.                                         Return jso
  581.                                 Else
  582.                                         Local val:TJSONValue = Parse()
  583.                                         If val = Null Then Return Null
  584.                                         ' expand array if needed
  585.                                         If count >= jso.Items.Length Then
  586.                                                 jso.Items = jso.Items[..jso.Items.Length+ARRAY_GROW_SIZE]
  587.                                         EndIf                                  
  588.                                         jso.SetByIndex( count, val)
  589.                                         count :+ 1
  590.                                 EndIf
  591.                         Wend
  592.                 ElseIf c = STRING_START1 Or c = STRING_START2 Then                     
  593.                         ' STRING
  594.                         Local strchar:Byte = c
  595.                         Index :+ 1
  596.                         Local start:Int = Index
  597.                         While (Index < Source.Length) And (Source[Index] <> strchar)
  598.                                 If Source[Index] = STRING_ESC Then
  599.                                         Index :+ 1
  600.                                 EndIf                          
  601.                                 Index :+ 1                             
  602.                         Wend
  603.                         Index :+ 1
  604.                         ' escape string
  605.                         Local s:String = Source[start..Index-1]
  606.                         's = s.Replace( "/", "/") ' wtf???
  607.                         s = s.Replace( "~q", "~q")
  608.                         s = s.Replace( "'", "'")
  609.                         s = s.Replace( "        ", "~t")
  610.                         s = s.Replace( "
  611. ", "~r")
  612.                         s = s.Replace( "
  613. ", "~n")
  614.                         s = s.Replace( "\", "")
  615.                         ' return json string
  616.                         Return TJSONString.Create( s)
  617.                        
  618.                 ElseIf (c >= NUMBER_START) And (c <= NUMBER_STOP) Then
  619.                         ' NUMBER
  620.                         Local start:Int = Index, gotsep:Int = False
  621.                         ' scan for rest of number
  622.                         Index :+ 1
  623.                         While Index < Source.Length
  624.                                 c = Source[Index]
  625.                                 If (c >= NUMBER_START) And (c <= NUMBER_STOP) Then
  626.                                         Index :+ 1
  627.                                         Continue
  628.                                 ElseIf c = NUMBER_SEP Then
  629.                                         If gotsep Then
  630.                                                 Error( "invalid floating point number")
  631.                                                 Return Null
  632.                                         EndIf
  633.                                         gotsep = True
  634.                                         Index :+ 1
  635.                                         Continue
  636.                                 EndIf
  637.                                 Exit
  638.                         Wend
  639.                         ' return json number
  640.                         Return TJSONNumber.Create( Source[start..Index].ToDouble())
  641.                        
  642.                 ElseIf (c >= IDENT_START1) And (c <= IDENT_STOP1)  Then
  643.                         ' TRUE FALSE NULL              
  644.                         Local start:Int = Index
  645.                         ' scan for rest of identifier
  646.                         While Index < Source.Length
  647.                                 c = source[Index]
  648.                                 If (c >= IDENT_START1) And (c <= IDENT_STOP1) Then
  649.                                         Index :+ 1
  650.                                         Continue
  651.                                 EndIf
  652.                                 Exit
  653.                         Wend
  654.                         ' validate identifier
  655.                         Local s:String = Source[start..Index]
  656.                         If s = "false" Then Return TJSONBoolean.Create( False)
  657.                         If s = "true" Then Return TJSONBoolean.Create( True)
  658.                         If s = "null" Then Return TJSON.NIL
  659.                         Error( "expected ~qtrue~q,~qfalse~q Or ~qnull~q")
  660.                         Return Null
  661.                 Else
  662.                         DebugLog "unknown character: " + c + " => " + Chr(c)
  663.                 EndIf
  664.         EndMethod
  665.        
  666.         Method Error( msg:String)
  667.                 DebugLog "JSON-PARSER-ERROR[ index:"+Index+" ]: " + msg
  668.         EndMethod
  669. EndType
  670.  
  671.  
  672.  
  673.  
  674. '
  675. ' Main JSON object, allows access to values via paths and for reading/writing
  676. '
  677. Type TJSON
  678.         Global NIL:TJSONValue = New TJSONNull
  679.        
  680.         Field Root:TJSONValue = NIL
  681.         Field LookupKey:TJSONKey = New TJSONKey
  682.        
  683.         Function Create:TJSON( source:Object)
  684.                 Local json:TJSON = New TJSON
  685.                 json.Read( source)
  686.                 Return json
  687.         EndFunction
  688.        
  689.         Method Read:TJSONValue( source:Object)
  690.                 Root = NIL
  691.                 If TJSONValue(source) Then
  692.                         ' set root     
  693.                         Root = TJSONValue( source)
  694.                         Return Root
  695.                 ElseIf TStream(source) Then
  696.                         ' read strings from stream
  697.                         Local s:String, stream:TStream = TStream(source)
  698.                         While Not stream.Eof()
  699.                                 s :+ stream.ReadLine() + "~n"
  700.                         Wend
  701.                         ' parse string
  702.                         Local parser:TJSONParser = New TJSONParser
  703.                         parser.Source = s
  704.                         Root = parser.Parse()
  705.                         If Root Then Return Root
  706.                         Root = NIL
  707.                 ElseIf String(source) Then
  708.                         ' parse string
  709.                         Local parser:TJSONParser = New TJSONParser
  710.                         parser.Source = String(source)
  711.                         Root = parser.Parse()
  712.                         If Root Then Return Root                       
  713.                         Root = NIL
  714.                 EndIf
  715.                 Return Null
  716.         EndMethod
  717.        
  718.         Method Write( dest:Object)
  719.                 If TStream(dest) Then
  720.                         TStream(dest).WriteString( Root.ToSource())
  721.                 ElseIf String(dest) Then
  722.                         Local stream:TStream = WriteFile( String(dest))
  723.                         If Not stream Then Return
  724.                         stream.WriteString( Root.ToSource())
  725.                         stream.Close()
  726.                 EndIf
  727.         EndMethod
  728.        
  729.         Method ParseString:TJSONValue( s:Object)
  730.                 If TJSONValue(s) Then Return TJSONValue(s)
  731.                 If Not String(s) Then Return NIL
  732.                 Local parser:TJSONParser = New TJSONParser
  733.                 parser.Source = String(s)
  734.                 Local val:TJSONValue = parser.Parse()
  735.                 If val Then Return val
  736.                 Return NIL
  737.         EndMethod
  738.  
  739.         Method Lookup:TJSONValue( path:String)
  740.                 If (path.Length = 0) Or (path.ToLower() = "root") Then Return Root
  741.                 LookupKey.Value = GetNext( path, ".")
  742.                 Local val:TJSONValue = Root.LookupValue( LookupKey)
  743.                 If val Then
  744.                         Local last:TJSONValue = val
  745.                         While path.Length > 0
  746.                                 last = val
  747.                                 LookupKey.Value = GetNext( path, ".")
  748.                                 val = last.LookupValue( LookupKey)
  749.                         Wend                   
  750.                         Return val
  751.                 EndIf
  752.         EndMethod
  753.        
  754.         Method SetValue( path:String, value:Object)
  755.                 LookupKey.Value = GetNext( path, ".")
  756.                 Local val:TJSONValue = Root.LookupValue( LookupKey)
  757.                 If val Then
  758.                         Local last:TJSONValue = Root
  759.                         While (path.Length > 0) And val
  760.                                 last = val
  761.                                 LookupKey.Value = GetNext( path, ".")
  762.                                 val = last.LookupValue( LookupKey)
  763.                         Wend                   
  764.                         If (last.Class = JSON_ARRAY) And IsNumber( LookupKey.Value) Then
  765.                                 last.SetByIndex( LookupKey.Value.ToInt(), ParseString(value))
  766.                         ElseIf (last.Class = JSON_OBJECT) And (Not IsNumber( LookupKey.Value)) Then
  767.                                 last.SetByName( LookupKey.Value, ParseString(value))
  768.                         EndIf
  769.                 Else
  770.                         If (Root.Class = JSON_ARRAY) And IsNumber( LookupKey.Value) Then
  771.                                 Root.SetByIndex( LookupKey.Value.ToInt(), ParseString(value))
  772.                         ElseIf (Root.Class = JSON_OBJECT) And (Not IsNumber( LookupKey.Value)) Then
  773.                                 Root.SetByName( LookupKey.Value, ParseString(value))
  774.                         EndIf                  
  775.                 EndIf
  776.         EndMethod
  777.                
  778.         Method GetValue:TJSONValue( path:String)
  779.                 Local val:TJSONValue = Lookup( path)
  780.                 If val Then Return val
  781.                 Return NIL
  782.         EndMethod
  783.        
  784.         Method GetNumber:Double( path:String)
  785.                 Local val:TJSONValue = Lookup( path)
  786.                 If val And val.Class = JSON_NUMBER Then Return TJSONNumber(val).Value
  787.                 Return 0.0
  788.         EndMethod
  789.        
  790.         Method GetString:String( path:String)
  791.                 Local val:TJSONValue = Lookup( path)
  792.                 If val And val.Class = JSON_STRING Then Return TJSONString(val).Value
  793.                 Return Null
  794.         EndMethod      
  795.        
  796.         Method GetBoolean:Int( path:String)
  797.                 Local val:TJSONValue = Lookup( path)
  798.                 If val And val.Class = JSON_BOOLEAN Then Return TJSONBoolean(val).Value
  799.                 Return False
  800.         EndMethod
  801.        
  802.         Method GetObject:TJSONObject( path:String)
  803.                 Local val:TJSONValue = Lookup( path)
  804.                 If val And val.Class = JSON_OBJECT Then Return TJSONObject(val)
  805.                 Return Null
  806.         EndMethod
  807.        
  808.         Method GetArray:TJSONArray( path:String)
  809.                 Local val:TJSONValue = Lookup( path)
  810.                 If val And val.Class = JSON_ARRAY Then Return TJSONArray(val)
  811.                 Return Null
  812.         EndMethod      
  813.  
  814. '
  815. ' not realy sure if these GetArrayXXX are necessary
  816. '      
  817.         Method GetArrayInt:Int[]( path:String)
  818.                 Local val:TJSONArray = GetArray( path)
  819.                 If val And (val.Items.Length > 0) Then
  820.                         Local a:Int[] = New Int[ val.Items.Length]
  821.                         For Local i:Int = 0 Until val.Items.Length
  822.                                 Select val.Items[i].Class
  823.                                         Case JSON_NUMBER
  824.                                                 a[i] = Int TJSONNumber( val.Items[i]).Value
  825.                                         Case JSON_STRING
  826.                                                 a[i] = TJSONString( val.Items[i]).Value.ToInt()
  827.                                         Case JSON_BOOLEAN
  828.                                                 a[i] = TJSONBoolean( val.Items[i]).Value
  829.                                 EndSelect
  830.                         Next
  831.                         Return a
  832.                 EndIf
  833.                 Return Null
  834.         EndMethod
  835.        
  836.         Method GetArrayDouble:Double[]( path:String)
  837.                 Local val:TJSONArray = GetArray( path)
  838.                 If val And (val.Items.Length > 0) Then
  839.                         Local a:Double[] = New Double[ val.Items.Length]
  840.                         For Local i:Int = 0 Until val.Items.Length
  841.                                 Select val.Items[i].Class
  842.                                         Case JSON_NUMBER
  843.                                                 a[i] = TJSONNumber( val.Items[i]).Value
  844.                                         Case JSON_STRING
  845.                                                 a[i] = TJSONString( val.Items[i]).Value.ToDouble()
  846.                                         Case JSON_BOOLEAN
  847.                                                 a[i] = Double TJSONBoolean( val.Items[i]).Value
  848.                                 EndSelect
  849.                         Next
  850.                         Return a
  851.                 EndIf
  852.                 Return Null
  853.         EndMethod      
  854.        
  855.         Method GetArrayString:String[]( path:String)
  856.                 Local val:TJSONArray = GetArray( path)
  857.                 If val And (val.Items.Length > 0) Then
  858.                         Local a:String[] = New String[ val.Items.Length]
  859.                         For Local i:Int = 0 Until val.Items.Length
  860.                                 Select val.Items[i].Class
  861.                                         Case JSON_NUMBER, JSON_STRING, JSON_BOOLEAN, JSON_NULL
  862.                                                 a[i] = val.Items[i].ToString()
  863.                                         Case JSON_OBJECT
  864.                                                 a[i] = "{}"
  865.                                         Case JSON_ARRAY
  866.                                                 a[i] = "[]"
  867.                                 EndSelect
  868.                         Next
  869.                         Return a
  870.                 EndIf
  871.                 Return Null
  872.         EndMethod      
  873.                
  874.         Method ToString:String()
  875.                 Return Root.ToString()
  876.         EndMethod
  877.        
  878.         Method ToSource:String( level:Int = 0)
  879.                 Return Root.ToSource( level)
  880.         EndMethod      
  881. EndType
  882.  
  883.  
  884.  
  885. '
  886. 'MARK: Support Functions
  887. '
  888. Private
  889.  
  890. '
  891. ' Simple token seperator
  892. '
  893. Function GetNext:String( value:String Var, sep:String) 
  894.         If (value.Length <= 0) Or (sep.Length <= 0) Then Return Null
  895.         Local res:String, index:Int = value.Find( sep)
  896.         If index = 0 Then
  897.                 value = value[1..]
  898.                 Return Null
  899.         ElseIf index >= 1 Then
  900.                 res = value[..index]
  901.                 value = value[ 1 + res.Length..]
  902.                 Return res
  903.         EndIf  
  904.         res = value
  905.         value = Null
  906.         Return res
  907. EndFunction
  908.  
  909. '
  910. ' Checks if a string is a number
  911. '
  912. Function IsNumber:Int( value:String)
  913.         Const START:Int = Asc("0")
  914.         Const STOP:Int = Asc("9")              
  915.         For Local i:Int = 0 Until value.Length
  916.                 Local c:Byte = value[i]  
  917.                 If (c < START) Or (c > STOP) Then Return False
  918.         Next
  919.         Return True
  920. EndFunction
  921.  
  922. '
  923. ' Returns a "pretty" floating point number
  924. '
  925. Function DoubleToString:String( value:Double)
  926.         Const STR_FMT:String = "%f"
  927.         Const CHAR_0:Byte = Asc("0")
  928.         Const CHAR_DOT:Byte = Asc(".")
  929.         Extern "C"
  930.                 Function modf_:Double( x:Double, iptr:Double Var) = "modf"
  931.                 Function snprintf_:Int( s:Byte Ptr, n:Int, Format$z, v1:Double) = "snprintf"
  932.         EndExtern      
  933.  
  934.         Local i:Double
  935.         If modf_( value, i) = 0.0 Then
  936.                 Return String.FromLong( Long i)
  937.         Else
  938.                 Local buf:Byte[32]
  939.                 Local sz:Int = snprintf_( buf, buf.Length, STR_FMT, value)
  940.                 sz :- 1
  941.                 While (sz > 0) And (buf[ sz] = CHAR_0)
  942.                         If buf[ sz-1] = CHAR_DOT Then Exit
  943.                         sz :- 1
  944.                 Wend
  945.                 sz :+ 1
  946.                 If sz > 0 Then Return String.FromBytes( buf, sz)
  947.         EndIf
  948.         Return "0"
  949. EndFunction
  950.  
  951. Function RepeatString:String( s:String, count:Int)
  952.         Local res:String
  953.         While count > 0
  954.                 res :+ s
  955.                 count :- 1
  956.         Wend
  957.         Return res     
  958. EndFunction
  959.  
  960. Public
  961.  
  962. '
  963. 'MARK: various test cases, each in its own Rem/EndRem block
  964. '
  965.  
  966. Rem
  967. Local array:TJSONValue = TJSONArray.Create( 4)
  968. array.SetByIndex( 0, TJSONString.Create( "string value"))
  969. array.SetByIndex( 1, TJSONNumber.Create( 1.5))
  970. array.SetByIndex( 2, TJSONBoolean.Create( True))
  971. array.SetByIndex( 3, TJSON.NIL)
  972.  
  973. Local object_:TJSONValue = New TJSONObject
  974. object_.SetByName( "first", TJSONString.Create( "string value"))
  975. object_.SetByName( "second", TJSONNumber.Create( 1.5))
  976. object_.SetByName( "third", TJSONBoolean.Create( True))
  977. object_.SetByName( "fourth", TJSON.NIL)
  978.  
  979.  
  980. Local json:TJSON = TJSON.Create( New TJSONObject)
  981. json.Root.SetByName( "first", TJSONString.Create( "string value"))
  982. json.Root.SetByName( "second", TJSONNumber.Create( 1.5))
  983. json.Root.SetByName( "third", TJSONBoolean.Create( True))
  984. json.Root.SetByName( "fourth", TJSON.NIL)
  985. json.Root.SetByName( "array", array)
  986. json.Root.SetByName( "object", object_)
  987.  
  988. Print json.ToSource()
  989. EndRem
  990.  
  991.  
  992. Rem
  993. Local jsop:TJSONParser = New TJSONParser
  994. Local json:TJSON = New TJSON
  995. jsop.Source = LoadString( "test.json")
  996. Print jsop.Source
  997. json.Root = jsop.Parse()
  998. If Not json.Root Then
  999.         Print "~noops"
  1000.         End
  1001. EndIf
  1002. EndRem
  1003.  
  1004.  
  1005. Rem
  1006. Local json:TJSON = TJSON.Create( "[ 1,2,~q3.4 + 2~q,4,5 ]")
  1007. Print "--------------------------------------------------------------------"
  1008. Print json.ToSource()
  1009. Print "--------------------------------------------------------------------"
  1010. For Local s:String = EachIn json.GetArrayString("root")
  1011.         Print s
  1012. Next
  1013. EndRem
  1014.  
  1015.  
  1016. Rem
  1017. Const TEST_JSON:String = ..
  1018. "{" +..
  1019. "       first: ~qString value~q," +..
  1020. "       second: 1.5," +..
  1021. "       third: true," +..
  1022. "       fourth: null," +..
  1023. "       ~qthis is an array~q: [" +..
  1024. "               ~qstring value~q," +..
  1025. "               1.5," +..
  1026. "               true," +..
  1027. "               null" +..
  1028. "       ]," +..
  1029. "       ~qthis is an object~q: {" +..
  1030. "       first: ~qstring value~q," +..
  1031. "       second: 1.5," +..
  1032. "       third: true," +..
  1033. "       fourth: null" +..
  1034. "       }" +..
  1035. "}"
  1036.  
  1037. 'Local json:TJSON = TJSON.Create( LoadString( "test.json"))
  1038. Local json:TJSON = TJSON.Create( TEST_JSON)
  1039.  
  1040.  
  1041. ' change some values
  1042. 'json.SetValue( "fifth", New TJSONObject)
  1043. 'json.SetValue( "this is an array.4", New TJSONObject)
  1044. 'json.SetValue( "this is an object.fifth", New TJSONObject)
  1045.  
  1046. Print "--------------------------------------------------------------------"
  1047. Print json.ToString()
  1048. Print "--------------------------------------------------------------------"
  1049. Print json.ToSource()
  1050. Print "--------------------------------------------------------------------"
  1051. Print json.GetValue( "first").ToString()
  1052. Print json.GetValue( "second").ToString()
  1053. Print json.GetValue( "third").ToString()
  1054. Print json.GetValue( "fourth").ToString()
  1055. Print json.GetValue( "fifth").ToString()
  1056. Print "--------------------------------------------------------------------"
  1057. Print json.GetValue( "this is an array").ToString()
  1058. Print "--------------------------------------------------------------------"
  1059. Print json.GetValue( "this is an object").ToString()
  1060. Print "--------------------------------------------------------------------"
  1061. Print json.GetValue( "this is an array.0").ToString()
  1062. Print json.GetValue( "this is an array.1").ToString()
  1063. Print json.GetValue( "this is an array.2").ToString()
  1064. Print json.GetValue( "this is an array.3").ToString()
  1065. Print json.GetValue( "this is an array.4").ToString()
  1066. Print "--------------------------------------------------------------------"
  1067. Print json.GetValue( "this is an object.first").ToString()
  1068. Print json.GetValue( "this is an object.second").ToString()
  1069. Print json.GetValue( "this is an object.third").ToString()
  1070. Print json.GetValue( "this is an object.fourth").ToString()
  1071. Print json.GetValue( "this is an object.fifth").ToString()
  1072. EndRem


Comments :


CoderLaureate(Posted 1+ years ago)

 Oh Man!  You beat me to it!  I just wrote a class in C# that does this for .Net 1.1, and 2.0 projects, and thought I would do the same for BMAX.  Way to go!  It looks really great!


grable(Posted 1+ years ago)

 Thanks, hope you find a use for it =)


N(Posted 1+ years ago)

 Nice work, reminds me of the <a href="http://spifftastic.net/mod/cower.sparse.txt" target="_blank">SParse[/url] thing I did, albeit more flexible syntactically (not sure how the code is, don't plan on using it, just struck me as neat).


Tylerbot(Posted 1+ years ago)

 grable, thank you so much! I will be using this in my game, Colosseum. This makes data handling so easy! I will also give you credit both in the source file itself (unchanged) and in my main menu. Again, thanks for writing this, you totally rock!My game is here, if you want to take a look.<a href="http://colosseum.devjavu.com/" target="_blank">http://colosseum.devjavu.com[/url]JSON will be used in my next release, 0.2.7, to read and write all my game data and configurations, instead of what I was going to use (a homebrew bastardized .ini format which kind of sucked).


Jaydubeww(Posted 1+ years ago)

 Edited this great parser to allow negative numbers and scientific notated numbers as well:
[code]
Rem
**********************************************************************************************************************************
* JSON Reader/Writer and generic handling
*
REVISIONS:
   line 453: added " Const MINUS:Byte = Asc("-") "      
   line 454: added " Const SCIENTIFIC:Byte = Asc("e") "
   line 625: added " or (c = MINUS) " to successfully parse negative numbers
   line 632: added " Or (c = MINUS) Or (c = SCIENTIFIC) " to successfully parse scientific notated numbers

***********************************************************   
* INFO
*
   author: grable
   email : grable0@...
   
**********************************************************************************************************************************
* TJSONValue is the generic container for all JSON data types
*   
   Type TJSONValue
      ' determines the type of value
      Field Class:Int
      
      ' returns value by index (only valid for arrays)
      Method GetByIndex:TJSONValue( index:Int)
      
      ' returns value by name (only valid for objects)
      Method GetByName:TJSONValue( name:Object)
      
      Method SetByIndex( index:Int, value:TJSONValue)
      Method SetByName( name:Object, value:TJSONValue)
      
      ' lookup value by string (either a number or a name, valid for arrays & objects)
      Method LookupValue:TJSONValue( value:Object)
      
      ' returns properly indented source representation of JSON data
      Method ToSource:String( level:Int = 0)
      
      ' returns JSON data as string
      Method ToString:String()
   EndType
   
   Type TJSONNumber Extends TJSONValue
      Field Value:Double
   EndType
   
   Type TJSONString Extends TJSONValue
      Field Value:String
   EndType
   
   Type TJSONBoolean Extends TJSONValue
      Field Value:Int
   EndType
   
   Type TJSONObject Extends TJSONValue
      Field Items:TMap ' holds actual fields
      Field List:TList ' holds field order
   EndType
   
   Type TJSONArray Extends TJSONValue
      Field Items:TJSONValue[]
   EndType
   
   The methods are implemented in the various subclasses.
      
**********************************************************************************************************************************
* TJSON handles all reading/writing of json data, and allows for easy acces to elements via "paths"
*   
   Type TJSON
      ' the root JSON value
      Field Root:TJSONValue
      
      ' create a new JSON from any source
      Function Create:TJSON( source:Object)
   
      ' read JSON data from a TJSONValue, TStream or String
      Method Read:TJSONValue( source:Object)
      
      ' write JSON data to a TStream or a file (as String)
      Method Write( dest:Object)
      
      ' parses a string into its JSON data representation
      Method ParseString:TJSONValue( s:Object)
      
      ' lookup a JSON value at at specified path, returns NULL on failure
      Method Lookup:TJSONValue( path:String)
   
      ' sets a JSON value at path to value, value can be a TJSONValue or JSON data as a string
      Method SetValue( path:String, value:Object)
      
      ' returns the json value at specified
      Method GetValue:TJSONValue( path:String)   
      
      ' returns blitz specific types from specified paths
      Method GetNumber:Double( path:String)
      Method GetString:String( path:String)
      Method GetBoolean:Int( path:String)
      
      ' returns only these specific objects
      Method GetObject:TJSONObject( path:String)
      Method GetArray:TJSONArray( path:String)
   
      ' get a blitz array from a JSON array or NULL on failure
      Method GetArrayInt:Int[]( path:String)
      Method GetArrayDouble:Double[]( path:String)
      Method GetArrayString:String[]( path:String)
   EndType
   
**********************************************************************************************************************************
* PATHS
*      
   identifiers are seperated with ".", and has special syntax for array indices
      
   example:
      "users.joe.age"      ' direct access
      "users.joe.medals.0"    ' array index, arrays are 0 based
   
**********************************************************************************************************************************
* usage example:
*      
   Local json:TJSON = TJSON.Create( "{ string: 'abc', number: 1.0, boolean: true, object: { field: null }, array: [1,2,3] }")
      
   Print json.GetValue("string").ToString()         ' prints "abc"
   Print json.GetValue("object.field").ToString()   ' prints "null"
   Print json.GetValue("array").ToString()         ' prints "[1,2,3]"
   Print json.GetValue("array.1").ToString()         ' prints "2"
      
**********************************************************************************************************************************
* NOTES
*
   ** most of the TJSON.GetXXX methods returns the JSON NULL type on failure if not specified
   ** all identifiers are CASE SENSITIVE
   ** the parser is as close as i could get it with my current understanding of JSON, please let me know if i missed something
   ** see the bottom of this source file for more examples, or check out <a href="http://json.org/" target="_blank">http://json.org/[/url] for more info on JSON
   
EndRem

SuperStrict

Import BRL.LinkedList
Import BRL.Map


'
' JSON value classes
'
Const JSON_NULL:Int   = 1
Const JSON_OBJECT:Int   = 2
Const JSON_ARRAY:Int   = 3
Const JSON_STRING:Int   = 4
Const JSON_NUMBER:Int   = 5
Const JSON_BOOLEAN:Int   = 6


'
' used by TJSON / TJSONObject for identifier lookup
'
Type TJSONKey
   Field Value:String
   
   Method ToString:String()
      Return "~q" + Value + "~q"
   EndMethod
   
   Method Compare:Int( o:Object)
      Local key:TJSONKey = TJSONKey(o)
      If key Then Return Value.Compare( key.Value)      
      If String(o) Then Return Value.Compare( o)
      Return 1
   EndMethod
EndType

'
' JSON Value objects
'

' TJSONValue is the generic container For all JSON data types
Type TJSONValue Abstract
   ' Determines the type of value
   Field Class:Int
   
   ' Returns value by index (only valid for arrays)
   Method GetByIndex:TJSONValue( index:Int)
      Return Null
   EndMethod
   
   ' Returns value by name (only valid for objects)
   Method GetByName:TJSONValue( name:Object)
      Return Null
   EndMethod   
   
   Method SetByIndex( index:Int, value:TJSONValue)
   EndMethod
   
   Method SetByName( name:Object, value:TJSONValue)
   EndMethod   
   
   ' Loopup value by string (either a number or a name, valid for arrays & objects)
   Method LookupValue:TJSONValue( value:Object)
      Return Self
   EndMethod
   
   ' Returns properly indented source representation of JSON data
   Method ToSource:String( level:Int = 0) Abstract
   
   ' Returns JSON data as string
   Method ToString:String() Abstract
EndType

Type TJSONObject Extends TJSONValue
   Field Items:TMap = New TMap
   Field List:TList = New TList ' for keeping the order of fields
   
   Method New()
      Class = JSON_OBJECT
   EndMethod   

   Method ToString:String()
      Local s:String, lines:Int = 0
      If List.Count() <= 0 Then Return "{}"
      For Local o:TNode = EachIn List
         If lines > 0 Then s :+ ", "
         s :+ o._key.ToString() +": "
         Local jsv:TJSONValue = TJSONValue(o._value)
         If jsv.Class = JSON_STRING Then
            s :+ jsv.ToSource()
         Else
            s :+ jsv.ToString()
         EndIf
         lines :+ 1
      Next
      Return "{ "+ s +" }"
   EndMethod
   
   Method ToSource:String( level:Int = 0)
      Local s:String, lines:Int = 0
      If List.Count() <= 0 Then Return "{}"
      For Local o:TNode = EachIn List
         If lines > 0 Then s :+ ",~n" + RepeatString( "~t", level + 1)         
         s :+ o._key.ToString() +": "+ TJSONValue(o._value).ToSource( level + 1)
         lines :+ 1
      Next
      If lines > 1 Then Return "{~n"+ RepeatString( "~t", level + 1) + s + "~n" + RepeatString( "~t", level) + "}"
      Return "{ "+ s +" }"
   EndMethod      
   
   Method GetByName:TJSONValue( name:Object)
      Return TJSONValue( Items.ValueForKey( name))
   EndMethod
   
   Method SetByName( name:Object, value:TJSONValue)
      Local node:TNode
      If TJSONKey(name) Then
         Items.Insert( name, value)
         node = Items._FindNode( name)
         If Not List.Contains( node) Then List.AddLast( node)
      ElseIf String(name) Then
         Local s:String = String(name)
         If s.Length > 0 Then
            Items.Insert( s, value)
            node = Items._FindNode( s)
            If Not List.Contains( node) Then List.AddLast( node)
         EndIf
      EndIf
   EndMethod   
   
   Method LookupValue:TJSONValue( value:Object)
      If TJSONKey(value) Then
         Return GetByName( value)
      ElseIf String(value) Then
         If Not IsNumber( String(value)) Then Return GetByName( value)
      EndIf
   EndMethod
EndType

Type TJSONArray Extends TJSONValue
   Field Items:TJSONValue[]
   Field AutoGrow:Int = True
   
   Function Create:TJSONArray( size:Int)
      Local jso:TJSONArray = New TJSONArray
      jso.Items = New TJSONValue[ size]
      Return jso
   EndFunction
   
   Method New()
      Class = JSON_ARRAY
   EndMethod   

   Method ToString:String()
      Local s:String, lines:Int = 0
      If Items.Length <= 0 Then Return "[]"
      For Local o:TJSONValue = EachIn Items
         If lines > 0 Then s :+ ", "         
         If o.Class = JSON_STRING Then
            s :+ o.ToSource()
         Else
            s :+ o.ToString()
         EndIf         
         lines :+ 1
      Next
      Return "[ "+ s +" ]"
   EndMethod   
   
   Method ToSource:String( level:Int = 0)
      If Items.Length <= 0 Then Return "[]"
      Local s:String, lines:Int = 0
      For Local o:TJSONValue = EachIn Items
         If lines > 0 Then s :+ ",~n" + RepeatString( "~t", level + 1)
         s :+ o.ToSource( level + 1)
         lines :+ 1
      Next
      If lines > 1 Then Return "[~n" + RepeatString( "~t", level + 1) + s + "~n" + RepeatString( "~t", level) + "]"
      Return "[ "+ s +" ]"
   EndMethod
   
   Method GetByIndex:TJSONValue( index:Int)
      If (index >= 0) And (index < Items.Length) Then
         Return TJSONValue( Items[ index])
      EndIf
   EndMethod
   
   Method SetByIndex( index:Int, value:TJSONValue)
      If (index >= 0) And (index < Items.Length) Then
         Items[ index] = value
      ElseIf AutoGrow And (Index >= Items.Length) Then
         Local oldlen:Int = Items.Length
         Items = Items[..index + 1]
         For Local i:Int = oldlen Until Items.Length
            Items = TJSON.NIL
         Next
         Items[index] = value
      EndIf
   EndMethod
   
   Method LookupValue:TJSONValue( value:Object)
      If TJSONKey(value) Then
         Local s:String = TJSONKey(value).Value
         If IsNumber( s) Then Return GetByIndex( s.ToInt())
      ElseIf String(value) Then
         If IsNumber( String(value)) Then Return GetByIndex( String(value).ToInt())
      EndIf   
   EndMethod
EndType


Type TJSONString Extends TJSONValue
   Field Value:String   
   
   Method New()
      Class = JSON_STRING
   EndMethod
   
   Function Create:TJSONString( value:String)
      Local jso:TJSONString = New TJSONString
      jso.Value = value
      Return jso
   EndFunction
      
   Method ToString:String()
      Return Value
   EndMethod
   
   Method ToSource:String( level:Int = 0)
      Return "~q" + Value + "~q"
   EndMethod
EndType

Type TJSONNumber Extends TJSONValue
   Field Value:Double
   
   Method New()
      Class = JSON_NUMBER
   EndMethod   

   Function Create:TJSONNumber( value:Double)
      Local jso:TJSONNumber = New TJSONNumber
      jso.Value = value
      Return jso
   EndFunction
   
   Method ToString:String()
      Return DoubleToString( Value)
   EndMethod   
   
   Method ToSource:String( level:Int = 0)
      Return DoubleToString( Value)
   EndMethod      
EndType

Type TJSONBoolean Extends TJSONValue
   Field Value:Int
   
   Method New()
      Class = JSON_BOOLEAN
   EndMethod   
   
   Function Create:TJSONBoolean( value:Int)
      Local jso:TJSONBoolean = New TJSONBoolean
      jso.Value = value
      Return jso
   EndFunction
   
   Method ToString:String()
      If Value Then Return "true"
      Return "false"
   EndMethod   
   
   Method ToSource:String( level:Int = 0)
      If Value Then Return "true"
      Return "false"
   EndMethod      
EndType

Type TJSONNull Extends TJSONValue
   Method New()
      Class = JSON_NULL
   EndMethod

   Method ToString:String()
      Return "null"
   EndMethod
   
   Method ToSource:String( level:Int = 0)
      Return "null"
   EndMethod   
EndType



'
' Parses any string into its JSONValue representation
'
Type TJSONParser
   Const ARRAY_GROW_SIZE:Int = 32

   Field Source:String
   Field Index:Int
   Field MakeLowerCase:Int
   
   Method Parse:TJSONValue()
      Const OBJECT_START:Byte = Asc("{")
      Const OBJECT_STOP:Byte = Asc("}")      
      Const ARRAY_START:Byte = Asc("[")
      Const ARRAY_STOP:Byte = Asc("]")
      Const FIELD_SEP:Byte = Asc(":")
      Const ELEM_SEP:Byte = Asc(",")
      Const IDENT_START1:Byte = Asc("a")
      Const IDENT_STOP1:Byte = Asc("z")
      Const IDENT_START2:Byte = Asc("A")
      Const IDENT_STOP2:Byte = Asc("Z")
      Const UNDERSCORE:Byte = Asc("_")
      Const MINUS:Byte = Asc("-")      
      Const SCIENTIFIC:Byte = Asc("e")
      Const NUMBER_START:Byte = Asc("0")
      Const NUMBER_STOP:Byte = Asc("9")
      Const NUMBER_SEP:Byte = Asc(".")
      Const STRING_START1:Byte = Asc("~q")
      Const STRING_START2:Byte = Asc("'")
      Const STRING_ESC:Byte = Asc("")
      Const SPACE:Byte = Asc(" ")
      Const TAB:Byte = Asc("~t")
      Const CR:Byte = Asc("~r")
      Const LF:Byte = Asc( "~n")
      
      Local c:Byte
      ' skip beginning whitspace & crlf      
      While Index < Source.Length
         c = Source[Index]         
         If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
            Index :+ 1
            Continue
         EndIf
         Exit
      Wend
      ' at end allready ?
      If (Index >= Source.Length) Or (Source[Index] = 0) Then Return Null
      
      c = Source[Index]
      If c = OBJECT_START Then
         ' OBJECT
         Local jso:TJSONObject = New TJSONObject
         Index :+ 1         
         While Index < Source.Length
            ' skip whitespace & crlf
            While Index < Source.Length
               c = Source[Index]         
               If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
                  Index :+ 1
                  Continue
               EndIf
               Exit
            Wend
            
            If c = ELEM_SEP Then
               Index :+ 1
            ElseIf c = OBJECT_STOP
               Index :+ 1
               ' return json object
               Return jso
            Else            
               Local start:Int = Index, idinstr:Int = False
               Local name:String
               If c = STRING_START1 Or c = STRING_START2 Then                  
                  ' get name enclosed in string tags
                  Local strchar:Byte = c
                  Index :+ 1
                  start = Index
                  While (Index < Source.Length) And (Source[Index] <> strchar)
                     If Source[Index] = STRING_ESC Then
                        Index :+ 1
                     EndIf
                     Index :+ 1
                  Wend
                  name = Source[start..Index]               
                  ' escape string         
                  'name = name.Replace( "/", "/") ' wtf???
                  name = name.Replace( "~q", "~q")
                  name = name.Replace( "'", "'")
                  name = name.Replace( "   ", "~t")
                  name = name.Replace( "", "~r")
                  name = name.Replace( "
", "~n")
                  name = name.Replace( "\", "")
                  Index :+ 1
                  idinstr = True
               Else
                  ' get name as an identifier
                  Index :+ 1
                  While Index < Source.Length
                     c = Source[Index]
                     If ((c >= IDENT_START1) And (c <= IDENT_STOP1)) Or ((c >= IDENT_START2) And (c <= IDENT_STOP2)) Or ..
                        ((c >= NUMBER_START) And (c <= NUMBER_STOP)) Or (c = UNDERSCORE) Then ' Or (c = MINUS)
                        Index :+ 1
                        Continue
                     EndIf
                     name = Source[start..Index]
                     Exit
                  Wend
               EndIf                           
               ' skip whitespace & crlf
               While Index < Source.Length
                  c = Source[Index]         
                  If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
                     Index :+ 1
                     Continue
                  EndIf
                  Exit
               Wend
               ' check for field seperator
               If c <> FIELD_SEP Then
                  Error( "expected field seperator ~q:~q")
                  Return Null
               EndIf
               Index :+ 1
               ' parse value
               Local val:TJSONValue = Parse()
               If val = Null Then Return Null
               If idinstr Then
                  Local key:TJSONKey = New TJSONKey
                  key.Value = name
                  jso.SetByName( key, val)
               Else
                  jso.SetByName( name, val)
               EndIf
            EndIf
         Wend
      ElseIf c = ARRAY_START Then
         ' ARRAY
         Local jso:TJSONArray = TJSONArray.Create( ARRAY_GROW_SIZE)
         Local count:Int = 0
         Index :+ 1         
         While Index < Source.Length
            ' skip whitespace & crlf
            While Index < Source.Length
               c = Source[Index]         
               If (c = SPACE) Or (c = TAB) Or (c = CR) Or (c = LF) Then
                  Index :+ 1
                  Continue
               EndIf
               Exit
            Wend   
            ' parse value
            If c = ELEM_SEP Then
               Index :+ 1
               count :+ 1
            ElseIf c = ARRAY_STOP Then
               Index :+ 1
               ' return json array
               jso.Items = jso.Items[..count+1]               
               Return jso
            Else
               Local val:TJSONValue = Parse()
               If val = Null Then Return Null
               ' expand array if needed
               If count >= jso.Items.Length Then
                  jso.Items = jso.Items[..jso.Items.Length+ARRAY_GROW_SIZE]
               EndIf               
               jso.SetByIndex( count, val)
            EndIf
         Wend
      ElseIf c = STRING_START1 Or c = STRING_START2 Then         
         ' STRING
         Local strchar:Byte = c
         Index :+ 1
         Local start:Int = Index
         While (Index < Source.Length) And (Source[Index] <> strchar)
            If Source[Index] = STRING_ESC Then
               Index :+ 1
            EndIf            
            Index :+ 1            
         Wend
         Index :+ 1
         ' escape string
         Local s:String = Source[start..Index-1]
         's = s.Replace( "/", "/") ' wtf???
         s = s.Replace( "~q", "~q")
         s = s.Replace( "'", "'")
         s = s.Replace( "   ", "~t")
         s = s.Replace( "", "~r")
         s = s.Replace( "
", "~n")
         s = s.Replace( "\", "")
         ' return json string
         Return TJSONString.Create( s)
         
      ElseIf (c >= NUMBER_START) And (c <= NUMBER_STOP) Or (c = MINUS) Then
         ' NUMBER
         Local start:Int = Index, gotsep:Int = False
         ' scan for rest of number
         Index :+ 1
         While Index < Source.Length
            c = Source[Index]
            If (c >= NUMBER_START) And (c <= NUMBER_STOP) Or (c = MINUS) Or (c = SCIENTIFIC) Then
               Index :+ 1
               Continue
            ElseIf c = NUMBER_SEP Then ' Only allow one period per number
               If gotsep Then
                  Error( "invalid floating point number")
                  Return Null
               EndIf
               gotsep = True
               Index :+ 1
               Continue
            EndIf
            Exit
         Wend
         ' return json number
         Return TJSONNumber.Create( Source[start..Index].ToDouble())
         
      ElseIf (c >= IDENT_START1) And (c <= IDENT_STOP1)  Then
         ' TRUE FALSE NULL      
         Local start:Int = Index
         ' scan for rest of identifier
         While Index < Source.Length
            c = source[Index]
            If (c >= IDENT_START1) And (c <= IDENT_STOP1) Then
               Index :+ 1
               Continue
            EndIf
            Exit
         Wend
         ' validate identifier
         Local s:String = Source[start..Index]
         If s = "false" Then Return TJSONBoolean.Create( False)
         If s = "true" Then Return TJSONBoolean.Create( True)
         If s = "null" Then Return TJSON.NIL
         Error( "expected ~qtrue~q,~qfalse~q Or ~qnull~q")
         Return Null
      Else
         DebugLog "unknown character: " + c + " => " + Chr(c)
      EndIf
   EndMethod
   
   Method Error( msg:String)
      DebugLog "JSON-PARSER-ERROR[ index:"+Index+" ]: " + msg
   EndMethod
EndType

'
' Main JSON object, allows access to values via paths and for reading/writing
' TJSON handles all reading/writing of json data, And allows For easy acces To elements via "paths"
'
Type TJSON
   Global NIL:TJSONValue = New TJSONNull
   
   ' The root JSON value
   Field Root:TJSONValue = NIL
   Field LookupKey:TJSONKey = New TJSONKey
   
   ' Create a new JSON from any source
   Function Create:TJSON( source:Object)
      Local json:TJSON = New TJSON
      json.Read( source)
      Return json
   EndFunction
   
   ' Read JSON data from a TJSONValue, TStream or String
   Method Read:TJSONValue( source:Object)
      Root = NIL
      If TJSONValue(source) Then
         ' set root   
         Root = TJSONValue( source)
         Return Root
      ElseIf TStream(source) Then
         ' read strings from stream
         Local s:String, stream:TStream = TStream(source)
         While Not stream.Eof()
            s :+ stream.ReadLine() + "~n"
         Wend
         ' parse string
         Local parser:TJSONParser = New TJSONParser
         parser.Source = s
         Root = parser.Parse()
         If Root Then Return Root
         Root = NIL
      ElseIf String(source) Then
         ' parse string
         Local parser:TJSONParser = New TJSONParser
         parser.Source = String(source)
         Root = parser.Parse()
         If Root Then Return Root         
         Root = NIL
      EndIf
      Return Null
   EndMethod
   
   ' Write JSON data to a TStream or a file (as String)
   Method Write( dest:Object)
      If TStream(dest) Then
         TStream(dest).WriteString( Root.ToSource())
      ElseIf String(dest) Then
         Local stream:TStream = WriteFile( String(dest))
         If Not stream Then Return
         stream.WriteString( Root.ToSource())
         stream.Close()
      EndIf
   EndMethod
   
   ' Parses a string into its JSON data representation
   Method ParseString:TJSONValue( s:Object)
      If TJSONValue(s) Then Return TJSONValue(s)
      If Not String(s) Then Return NIL
      Local parser:TJSONParser = New TJSONParser
      parser.Source = String(s)
      Local val:TJSONValue = parser.Parse()
      If val Then Return val
      Return NIL
   EndMethod

   ' Lookup a JSON value at at specified path, returns NULL on failure
   Method Lookup:TJSONValue( path:String)
      If (path.Length = 0) Or (path.ToLower() = "root") Then Return Root
      LookupKey.Value = GetNext( path, ".")
      Local val:TJSONValue = Root.LookupValue( LookupKey)
      If val Then
         Local last:TJSONValue = val
         While path.Length > 0
            last = val
            LookupKey.Value = GetNext( path, ".")
            val = last.LookupValue( LookupKey)
         Wend         
         Return val
      EndIf
   EndMethod
   
   ' Sets a JSON value at path to value, value can be a TJSONValue or JSON data as a string
   Method SetValue( path:String, value:Object)
      LookupKey.Value = GetNext( path, ".")
      Local val:TJSONValue = Root.LookupValue( LookupKey)
      If val Then
         Local last:TJSONValue = Root
         While (path.Length > 0) And val
            last = val
            LookupKey.Value = GetNext( path, ".")
            val = last.LookupValue( LookupKey)
         Wend         
         If (last.Class = JSON_ARRAY) And IsNumber( LookupKey.Value) Then
            last.SetByIndex( LookupKey.Value.ToInt(), ParseString(value))
         ElseIf (last.Class = JSON_OBJECT) And (Not IsNumber( LookupKey.Value)) Then
            last.SetByName( LookupKey.Value, ParseString(value))
         EndIf
      Else
         If (Root.Class = JSON_ARRAY) And IsNumber( LookupKey.Value) Then
            Root.SetByIndex( LookupKey.Value.ToInt(), ParseString(value))
         ElseIf (Root.Class = JSON_OBJECT) And (Not IsNumber( LookupKey.Value)) Then
            Root.SetByName( LookupKey.Value, ParseString(value))
         EndIf         
      EndIf
   EndMethod
   
   ' Returns the json value at specified
   Method GetValue:TJSONValue( path:String)
      Local val:TJSONValue = Lookup( path)
      If val Then Return val
      Return NIL
   EndMethod
   
   ' Returns the json value at specified
   Method GetNumber:Double( path:String)
      Local val:TJSONValue = Lookup( path)
      If val And val.Class = JSON_NUMBER Then Return TJSONNumber(val).Value
      Return 0.0
   EndMethod
   
   ' Returns blitz specific types from specified paths
   Method GetString:String( path:String)
      Local val:TJSONValue = Lookup( path)
      If val And val.Class = JSON_STRING Then Return TJSONString(val).Value
      Return Null
   EndMethod   
   
   ' Returns only these specific objects
   Method GetBoolean:Int( path:String)
      Local val:TJSONValue = Lookup( path)
      If val And val.Class = JSON_BOOLEAN Then Return TJSONBoolean(val).Value
      Return False
   EndMethod
   
   ' Returns only these specific objects
   Method GetObject:TJSONObject( path:String)
      Local val:TJSONValue = Lookup( path)
      If val And val.Class = JSON_OBJECT Then Return TJSONObject(val)
      Return Null
   EndMethod
   ' Returns only these specific objects
   Method GetArray:TJSONArray( path:String)
      Local val:TJSONValue = Lookup( path)
      If val And val.Class = JSON_ARRAY Then Return TJSONArray(val)
      Return Null
   EndMethod   

'
' not realy sure if these GetArrayXXX are necessary
'   
   ' Get a blitz array from a JSON array or NULL on failure
   Method GetArrayInt:Int[]( path:String)
      Local val:TJSONArray = GetArray( path)
      If val And (val.Items.Length > 0) Then
         Local a:Int[] = New Int[ val.Items.Length]
         For Local i:Int = 0 Until val.Items.Length
            Select val.Items.Class
               Case JSON_NUMBER
                  a = Int TJSONNumber( val.Items).Value
               Case JSON_STRING
                  a = TJSONString( val.Items).Value.ToInt()
               Case JSON_BOOLEAN
                  a = TJSONBoolean( val.Items).Value
            EndSelect
         Next
         Return a
      EndIf
      Return Null
   EndMethod
   
   ' Get a blitz array from a JSON array or NULL on failure
   Method GetArrayDouble:Double[]( path:String)
      Local val:TJSONArray = GetArray( path)
      If val And (val.Items.Length > 0) Then
         Local a:Double[] = New Double[ val.Items.Length]
         For Local i:Int = 0 Until val.Items.Length
            Select val.Items.Class
               Case JSON_NUMBER
                  a = TJSONNumber( val.Items).Value
               Case JSON_STRING
                  a = TJSONString( val.Items).Value.ToDouble()
               Case JSON_BOOLEAN
                  a = Double TJSONBoolean( val.Items).Value
            EndSelect
         Next
         Return a
      EndIf
      Return Null
   EndMethod   
   
    ' Get a blitz array from a JSON array or NULL on failure
   Method GetArrayString:String[]( path:String)
      Local val:TJSONArray = GetArray( path)
      If val And (val.Items.Length > 0) Then
         Local a:String[] = New String[ val.Items.Length]
         For Local i:Int = 0 Until val.Items.Length
            Select val.Items.Class
               Case JSON_NUMBER, JSON_STRING, JSON_BOOLEAN, JSON_NULL
                  a = val.Items.ToString()
               Case JSON_OBJECT
                  a = "{}"
               Case JSON_ARRAY
                  a = "[]"
            EndSelect
         Next
         Return a
      EndIf
      Return Null
   EndMethod   
      
   Method ToString:String()
      Return Root.ToString()
   EndMethod
   
   Method ToSource:String( level:Int = 0)
      Return Root.ToSource( level)
   EndMethod   
EndType



'
'MARK: Support Functions
'
Private

'
' Simple token seperator
'
Function GetNext:String( value:String Var, sep:String)   
   If (value.Length <= 0) Or (sep.Length <= 0) Then Return Null
   Local res:String, index:Int = value.Find( sep)
   If index = 0 Then
      value = value[1..]
      Return Null
   ElseIf index >= 1 Then
      res = value[..index]
      value = value[ 1 + res.Length..]
      Return res
   EndIf   
   res = value
   value = Null
   Return res
EndFunction

'
' Checks if a string is a number
'
Function IsNumber:Int( value:String)
   Const START:Int = Asc("0")
   Const STOP:Int = Asc("9")      
   For Local i:Int = 0 Until value.Length
      Local c:Byte = value  
      If (c < START) Or (c > STOP) Then Return False
   Next
   Return True
EndFunction

'
' Returns a "pretty" floating point number
'
Function DoubleToString:String( value:Double)
   Const STR_FMT:String = "%f"
   Const CHAR_0:Byte = Asc("0")
   Const CHAR_DOT:Byte = Asc(".")
   Extern "C"
      Function modf_:Double( x:Double, iptr:Double Var) = "modf"
      Function snprintf_:Int( s:Byte Ptr, n:Int, Format$z, v1:Double) = "snprintf"
   EndExtern   

   Local i:Double
   If modf_( value, i) = 0.0 Then
      Return String.FromLong( Long i)
   Else
      Local buf:Byte[32]
      Local sz:Int = snprintf_( buf, buf.Length, STR_FMT, value)
      sz :- 1
      While (sz > 0) And (buf[ sz] = CHAR_0)
         If buf[ sz-1] = CHAR_DOT Then Exit
         sz :- 1
      Wend
      sz :+ 1
      If sz > 0 Then Return String.FromBytes( buf, sz)
   EndIf
   Return "0"
EndFunction

Function RepeatString:String( s:String, count:Int)
   Local res:String
   While count > 0
      res :+ s
      count :- 1
   Wend
   Return res   
EndFunction

Public



'
'MARK: various test cases, each in its own Rem/EndRem block
'

Rem
Local array:TJSONValue = TJSONArray.Create( 4)
array.SetByIndex( 0, TJSONString.Create( "string value"))
array.SetByIndex( 1, TJSONNumber.Create( 1.5))
array.SetByIndex( 2, TJSONBoolean.Create( True))
array.SetByIndex( 3, TJSON.NIL)

Local object_:TJSONValue = New TJSONObject
object_.SetByName( "first", TJSONString.Create( "string value"))
object_.SetByName( "second", TJSONNumber.Create( 1.5))
object_.SetByName( "third", TJSONBoolean.Create( True))
object_.SetByName( "fourth", TJSON.NIL)


Local json:TJSON = TJSON.Create( New TJSONObject)
json.Root.SetByName( "first", TJSONString.Create( "string value"))
json.Root.SetByName( "second", TJSONNumber.Create( 1.5))
json.Root.SetByName( "third", TJSONBoolean.Create( True))
json.Root.SetByName( "fourth", TJSON.NIL)
json.Root.SetByName( "array", array)
json.Root.SetByName( "object", object_)

Print json.ToSource()
EndRem


Rem
Local jsop:TJSONParser = New TJSONParser
Local json:TJSON = New TJSON
jsop.Source = LoadString( "test.json")
Print jsop.Source
json.Root = jsop.Parse()
If Not json.Root Then
   Print "~noops"
   End
EndIf
EndRem


Rem
Local json:TJSON = TJSON.Create( "[ 1,2,~q3.4 + 2~q,4,5 ]")
Print "--------------------------------------------------------------------"
Print json.ToSource()
Print "--------------------------------------------------------------------"
For Local s:String = EachIn json.GetArrayString("root")
   Print s
Next
EndRem


Rem
Const TEST_JSON:String = ..
"{" +..
"   first: ~qString value~q," +..
"   second: 1.5," +..
"   third: true," +..
"   fourth: null," +..
"   ~qthis is an array~q: [" +..
"      ~qstring value~q," +..
"      1.5," +..
"      true," +..
"      null" +..
"   ]," +..
"   ~qthis is an object~q: {" +..
"   first: ~qstring value~q," +..
"   second: 1.5," +..
"   third: true," +..
"   fourth: null" +..
"   }" +..
"}"

'Local json:TJSON = TJSON.Create( LoadString( "test.json"))
Local json:TJSON = TJSON.Create( TEST_JSON)


' change some values
'json.SetValue( "fifth", New TJSONObject)
'json.SetValue( "this is an array.4", New TJSONObject)
'json.SetValue( "this is an object.fifth", New TJSONObject)

Print "--------------------------------------------------------------------"
Print json.ToString()
Print "--------------------------------------------------------------------"
Print json.ToSource()
Print "--------------------------------------------------------------------"
Print json.GetValue( "first").ToString()
Print json.GetValue( "second").ToString()
Print json.GetValue( "third").ToString()
Print json.GetValue( "fourth").ToString()
Print json.GetValue( "fifth").ToString()
Print "----------------------

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal