Code archives/File Utilities/JSON Reader/Writer

This code has been declared by its author to be Public Domain code.

Download source code

JSON Reader/Writer by grable2007
** 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:
{ 
	string: 'abc', 
	number: 1.0, 
	boolean: true, 
	object: {
		field: null,
		morestuff: "blablabla"
	},
	array: [1,2,3] 
}

Go here for more info on JSON http://json.org

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

Please let me know if there is anything missing or incorrect =)
Rem
**********************************************************************************************************************************
* JSON Reader/Writer and generic handling
*
	
**********************************************************************************************************************************
* 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 http://json.org/ for more info on JSON
	
***********************************************************	
* INFO
*
	author: grable
	email : grable0@gmail.com
	
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
'
Type TJSONValue Abstract
	Field Class:Int
	
	Method GetByIndex:TJSONValue( index:Int)
		Return Null
	EndMethod
	
	Method GetByName:TJSONValue( name:Object) 
		Return Null
	EndMethod	
	
	Method SetByIndex( index:Int, value:TJSONValue)
	EndMethod
	
	Method SetByName( name:Object, value:TJSONValue)
	EndMethod	
	
	Method LookupValue:TJSONValue( value:Object)
		Return Self
	EndMethod
	
	Method ToSource:String( level:Int = 0) 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[i] = 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 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 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", "~t")
						name = name.Replace( "\r", "~r")
						name = name.Replace( "\n", "~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) Or (c = MINUS) Then
								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
				ElseIf c = ARRAY_STOP Then
					' return json array
					Index :+ 1
					If count = 0 Then
						jso.Items = Null
					Else
						jso.Items = jso.Items[..count]					
					EndIf
					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)
					count :+ 1
				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", "~t")
			s = s.Replace( "\r", "~r")
			s = s.Replace( "\n", "~n")
			s = s.Replace( "\\", "\")
			' return json string
			Return TJSONString.Create( s)
			
		ElseIf (c >= NUMBER_START) And (c <= NUMBER_STOP) 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) Then
					Index :+ 1
					Continue
				ElseIf c = NUMBER_SEP Then
					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
'
Type TJSON
	Global NIL:TJSONValue = New TJSONNull
	
	Field Root:TJSONValue = NIL
	Field LookupKey:TJSONKey = New TJSONKey
	
	Function Create:TJSON( source:Object)
		Local json:TJSON = New TJSON
		json.Read( source)
		Return json
	EndFunction
	
	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
	
	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
	
	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

	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
	
	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
		
	Method GetValue:TJSONValue( path:String)
		Local val:TJSONValue = Lookup( path)
		If val Then Return val
		Return NIL
	EndMethod
	
	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
	
	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	
	
	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
	
	Method GetObject:TJSONObject( path:String)
		Local val:TJSONValue = Lookup( path)
		If val And val.Class = JSON_OBJECT Then Return TJSONObject(val)
		Return Null
	EndMethod
	
	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
'	
	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[i].Class
					Case JSON_NUMBER
						a[i] = Int TJSONNumber( val.Items[i]).Value
					Case JSON_STRING
						a[i] = TJSONString( val.Items[i]).Value.ToInt()
					Case JSON_BOOLEAN
						a[i] = TJSONBoolean( val.Items[i]).Value
				EndSelect
			Next
			Return a
		EndIf
		Return Null
	EndMethod
	
	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[i].Class
					Case JSON_NUMBER
						a[i] = TJSONNumber( val.Items[i]).Value
					Case JSON_STRING
						a[i] = TJSONString( val.Items[i]).Value.ToDouble()
					Case JSON_BOOLEAN
						a[i] = Double TJSONBoolean( val.Items[i]).Value
				EndSelect
			Next
			Return a
		EndIf
		Return Null
	EndMethod	
	
	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[i].Class
					Case JSON_NUMBER, JSON_STRING, JSON_BOOLEAN, JSON_NULL
						a[i] = val.Items[i].ToString()
					Case JSON_OBJECT
						a[i] = "{}"
					Case JSON_ARRAY
						a[i] = "[]"
				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[i]  
		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 "--------------------------------------------------------------------"
Print json.GetValue( "this is an array").ToString()
Print "--------------------------------------------------------------------"
Print json.GetValue( "this is an object").ToString()
Print "--------------------------------------------------------------------"
Print json.GetValue( "this is an array.0").ToString()
Print json.GetValue( "this is an array.1").ToString()
Print json.GetValue( "this is an array.2").ToString()
Print json.GetValue( "this is an array.3").ToString()
Print json.GetValue( "this is an array.4").ToString()
Print "--------------------------------------------------------------------"
Print json.GetValue( "this is an object.first").ToString()
Print json.GetValue( "this is an object.second").ToString()
Print json.GetValue( "this is an object.third").ToString()
Print json.GetValue( "this is an object.fourth").ToString()
Print json.GetValue( "this is an object.fifth").ToString()
EndRem

Comments

CoderLaureate2007
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!


grable2007
Thanks, hope you find a use for it =)


N2007
Nice work, reminds me of the SParse thing I did, albeit more flexible syntactically (not sure how the code is, don't plan on using it, just struck me as neat).


Tylerbot2008
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.
http://colosseum.devjavu.com
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).


Jaydubeww2015
Edited this great parser to allow negative numbers and scientific notated numbers as well:




Guy Fawkes2015
Just out of curiousity, can someone help me with this JSON problem I'm having with npm install for windows 8.1?

I just tried npm install, and I get:



This is my package.json file:



Thank you for helping me!

And EXCELLENT code, btw! :)

Sincerely,

~GF


Code Archives Forum