Code archives/File Utilities/Serialize/deserialize objects as JSON

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

Download source code

Serialize/deserialize objects as JSON by Pineapple2015
Basic usage:

serializer:jsonSerializer = New jsonSerializer.init()
json:jsonValue = serializer.serializeObject( obj )
jsonstr:String = json.toString()

deserializer:jsonDeserializer = New jsonDeserializer.init()
parsedjson:jsonValue = jsonValue.fromString( jsonstr )
typeid:TTypeId = TTypeId.ForName( "MyType" )
obj:MyType = MyType( deserializer.deserializeObject( parsedjson, typeid ) )

See the code for a thorough example.

It's possible to implement special handling for classes which inherit from Object. There are default handlers for String, TList, and TMap. For example, TList objects are serialized as json lists. It is possible to add your own handlers - see the init() method of jsonSerializer and jsonDeserializer for examples.

Caveats: Arrays of more than one dimension are unsupported. Be careful about having cyclic references, the serializer is not equipped to handle them.

Make good use of {json}, {nojson}, {jsonfld}, {nojsonfld}, {jsonbool}, and {jsoncont} metadata tags. See comments in the top of the source as well as the example code to understand usage.

Requires this archive entry ("wild.bmx"): http://www.blitzmax.com/codearcs/codearcs.php?code=3176
'   --+-----------------------------------------------------------------------------------------+--
'     | This code was originally written by Sophie Kirschner (meapineapple@gmail.com) and it is |  
'     | released as public domain. Please do not interpret that as liberty to claim credit that |  
'     | is not yours, or to sell this code when it could otherwise be obtained for free because |  
'     |                    that would be a really shitty thing of you to do.                    |
'   --+-----------------------------------------------------------------------------------------+--



SuperStrict

Import brl.reflection
Import brl.linkedlist
Import brl.map
Import "wild.bmx"       ' Dependency available here: http://www.blitzmax.com/codearcs/codearcs.php?code=3176
'Import brl.standardio   ' Used by example program



' Usage: Field MyField {json}                                        All other fields in this class without {json} metadata will not be serialized.
Const jsonSerializeMetadataInclude:String = "json"

' Usage: Field MyField {nojson}                                              This field will not be serialized. This has no impact on other fields.
Const jsonSerializeMetadataExclude:String = "nojson"

' Usage: Field MyField {jsonbool}              Compensates for BlitzMax's lack of a boolean primitive: Tag an integer to serialize it as a boolean.
Const jsonSerializeMetadataBoolean:String = "jsonbool"

' Usage: Field MyField {jsonfld="MyField2,MyField3"}         Tag an object to serialize only the listed field(s). Supersedes the object's own tags.
Const jsonSerializeMetadataIncludeSub:String = "jsonfld"

' Usage: Field MyField {nojsonfld="MyField*"}                                         Tag an object to exlude the listed fields from serialization.
Const jsonSerializeMetadataExcludeSub:String = "nojsonfld"

' Usage: Field MyField {jsoncont="MyType"}          Tag a TList or TMap to inform the deserializer what class objects it contains should belong to.
Const jsonSerializeMetadataClassSub:String = "jsoncont"

' For info on what special wildcard characters are allowed in {jsonf} and {nojsonf} tags, please refer to "wild.bmx".



' This is stuff you really shouldn't be touching
Private

Global ListTypeId:TTypeId = TTypeId.ForName( "TList" )
Global MapTypeId:TTypeId = TTypeId.ForName( "TMap" )

Const jsonOpenBraceAsc:Int = Asc( "{" )
Const jsonCloseBraceAsc:Int = Asc( "}" )
Const jsonOpenBracketAsc:Int = Asc( "[" )
Const jsonCloseBracketAsc:Int = Asc( "]" )
Const jsonAssignmentAsc:Int = Asc( ":" )
Const jsonDelimiterAsc:Int = Asc( "," )
Const jsonDecimalAsc:Int = Asc( "." )
Const jsonStringAsc:Int = Asc( "~q" )
Const jsonEscapeAsc:Int = Asc( "\" )

Extern
Function bbRefFieldPtr:Byte Ptr( obj:Object, index:Int )
Function bbRefArrayElementPtr:Byte Ptr( sz:Int, array:Object, index:Int )
Function bbRefGetObject:Object( p:Byte Ptr )
Function bbRefGetObjectClass:Int( obj:Object )
Function bbRefGetSuperClass:Int( class:Int )
Function bbRefAssignObject( p:Byte Ptr, obj:Object )
End Extern

Public



' Example code

Rem

Type MyType

    ' Serialize a null reference
    Field MyNull:Object = Null
    
    ' Serialize an integer
    Field MyInt:Int = 8
    
    ' Serialize a boolean
    Field MyBoolean:Int = True {jsonbool}
    
    ' Serialize a floating-point number
    Field MyNumber:Double = 3.14
    
    ' Serialize a string
    Field MyString:String = "hi"
    
    ' Serialize an array of strings
    Field MyArray:String[] = [ "foo", "bar", "foobar" ]
    
    ' Serialize an object
    Field MyObject:MySubType = New MySubType
    
    ' DON'T serialize this field
    Field MyUnserializedVar:String = "don't serialize me!" {nojson}
    
    ' Create a list to be filled with data then serialized
    Field MyList:TList = CreateList()
    
    ' Create a map to be filled with data then serialized
    Field MyMap:TMap = CreateMap()
    
    ' Fill the list and map with data upon initialization
    Method New()
        MyList.addlast( "one" )
        MyList.addlast( "two" )
        MyList.addlast( "three" )
        MyList.addlast( "four" )
        MyMap.insert( "5", "five" )
        MyMap.insert( "6", "six" )
        MyMap.insert( "7", "seven" )
        MyMap.insert( "8", "eight" )
        MyMap.insert( "9,10,11", [ 9:Int, 10:Int, 11:Int ] )
    End Method
    
End Type

Type MySubType

    ' Serialize a string
    Field name:String = "Bob" {json}
    
    ' Serialize a long int
    Field money:Long = 99999999 {json}
    
    ' Serialize a string
    Field power:String = "lots" {json}
    
    ' Serialize an array of ints
    Field favoriteNumbers:Int[] = [ 7, 9, 12 ] {json}
    
    ' DON'T serialize this field, because we're going to put a cyclic reference here
    Field parent:MyType
    
    ' Serialize a list of objects, and inform the deserializer of the class of its contents using metadata
    Field MyObjectList:TList = ListFromArray([ MyListEntry.Create("foo"), MyListEntry.Create("bar") ]) {jsoncont="MyListEntry"}
    
    ' Serialize this field differently to hand the cyclic reference: Include only the specified fields of the referenced object
    Field selfReference1:MySubType = Self {jsonfld="name,money"}
    
    ' Serialize the same thing as above, except inversely defined
    ' (note it's not necessary to explicitly exclude the parent field, as the normal tags already do that)
    Field selfReference2:MySubType = Self {nojsonfld="power,favoriteNumbers,MyObjectList,selfReference*"}
    
End Type

Type MyListEntry

    Function Create:MyListEntry( name:String )
        Local this:MyListEntry = New MyListEntry; this.name = name; Return this
    End Function

    Field name:String
    
End Type

' Create the object to be serialized
Local obj:MyType = New MyType
' Add a cyclic reference
obj.MyObject.parent = obj

' Finally, do the actual serialization
Print "Creating serializer"
Local serializer:jsonSerializer = New jsonSerializer.init()
Print "Serializing json"
Local json:jsonValue = serializer.serializeObject( obj )
Print "Generating string"
Local str:String = json.toString()
Print "Final json:"
Print str

' Now deserialze to get the original object back
Print "~nParsing output"
Local parsedjson:jsonValue = jsonValue.fromString( str )
Print "Creating deserializer"
Local deserializer:jsonDeserializer = New jsonDeserializer.init()
Print "Deserializing output"
Local newobj:MyType = MyType( deserializer.deserializeObject( parsedjson, TTypeId.ForName( "MyType" ) ) )
Print "Deserialized object's MyArray[2] field (should be ~qfoobar~q):"
Print newobj.MyArray[2]


EndRem






' Functions define special handling for types which inherit from Object
' If the absence of a handler function for serialization and deserialization a class is treated 
' as a simple 1:1 correspondence between dict key,value pairs and class field,value pairs.

' Serialize string
Function jsonSerializeString:Object( obj:Object, member:TMember, controller:jsonSerializationController )
    Return jsonValueString.Create( String( obj ) )
End Function

' Serialize linked list
Function jsonSerializeList:Object( obj:Object, member:TMember, controller:jsonSerializationController )
    Local list:TList = TList( obj )
    Local jsonList:jsonValueList = jsonValueList.Create()
    For Local member:Object = EachIn list
        jsonList.add( jsonSerializer( controller ).serializeObject( member ) )
    Next 
    Return jsonList
End Function

' Serialize map
Function jsonSerializeMap:Object( obj:Object, member:TMember, controller:jsonSerializationController )
    Local map:TMap = TMap( obj )
    Local dict:jsonValueDict = jsonValueDict.Create()
    For Local key:Object = EachIn map.Keys()
        dict.add( key, jsonSerializer( controller ).serializeObject( map.ValueForKey( key ) ) )
    Next
    Return dict
End Function

' Deserialize string
Function jsonDeserializeString:Object( obj:Object, member:TMember, controller:jsonSerializationController )
    Local value:jsonValueString = jsonValueString( obj )
    If value
        Return value.get()
    Else
        Return Null
    EndIf
End Function

' Deserialize linked list
Function jsonDeserializeList:Object( obj:Object, member:TMember, controller:jsonSerializationController )
    Local list:jsonValueList = jsonValueList( obj )
    If list
        Local retlist:TList = CreateList()
        For Local value:jsonValue = EachIn list.get()
            retlist.addlast( jsonDeserializer( controller ).deserializeObject( value, jsonDeserializer.jsonValueTypeId( value, member ) ) )
        Next
        Return retlist
    Else 
        Return Null
    EndIf
End Function

' Deserialize map
Function jsonDeserializeMap:Object( obj:Object, member:TMember, controller:jsonSerializationController )
    Local dict:jsonValueDict = jsonValueDict( obj )
    If dict
        Local map:TMap = CreateMap()
        For Local key:String = EachIn dict.get().Keys()
            Local value:jsonValue = jsonValue( dict.get().ValueForKey( key ) )
            map.insert( key, jsonDeserializer( controller ).deserializeObject( value, jsonDeserializer.jsonValueTypeId( value, member ) ) )
        Next
        Return map
    Else
        Return Null
    EndIf
End Function





' jsonSerializer and jsonDeserializer both extend this class
Type jsonSerializationController

    ' Associates functions with TTypeId keys
    Field typeHandlers:TMap = CreateMap()
    
    ' Add a new serializer/deserializer
    Method addTypeHandler( typeid:TTypeId, typeHandlerFunc:Object( obj:Object, member:TMember, controller:jsonSerializationController ) )
        typeHandlers.insert( typeid, jsonTypeHandlerFunc.Create( typeHandlerFunc ) )
    End Method

End Type

' Container for serialization and deserializeation functions
Type jsonTypeHandlerFunc
    ' Reference to handler function
    ' obj - the jsonValue to serialize or Object to deserialize
    ' member - the TField where this value belongs
    ' controller - Reference to jsonSerializer or jsonDeserializer object
    Field func:Object( obj:Object, member:TMember, controller:jsonSerializationController )
    ' Create a new handler
    Function Create:jsonTypeHandlerFunc( func:Object( obj:Object, member:TMember, controller:jsonSerializationController ) )
        Local this:jsonTypeHandlerFunc = New jsonTypeHandlerFunc; this.func = func; Return this
    End Function
    ' Call handler function
    Method handle:Object( obj:Object, member:TMember, controller:jsonSerializationController )
        Return func( obj, member, controller )
    End Method
End Type

' Recursively turn an arbitrary object and its fields into jsonValue objects which can be encoded as a string
Type jsonSerializer Extends jsonSerializationController
    
    ' Initialize with standard serializers (String, TList, and TMap)
    ' Easy to add your own handlers upon calling init. Example call:
    ' json.init( [ MyFirstTypeId, MySecondTypeId ], [ jsonSerialize1Type, jsonSerialize2Type ] )
    Method init:jsonSerializer( ..
        moreHandlersTypeId:TTypeId[] = Null, ..
        moreHandlersFunc:Object( obj:Object, member:TMember, controller:jsonSerializationController )[] = Null ..
    )
        addTypeHandler( StringTypeId, jsonSerializeString )
        addTypeHandler( ListTypeId, jsonSerializeList )
        addTypeHandler( MapTypeId, jsonSerializeMap )
        If moreHandlersTypeId And moreHandlersFunc
            For Local i:Int = 0 Until moreHandlersTypeId.length
                addTypeHandler( moreHandlersTypeId[i], moreHandlersFunc[i] )
            Next
        EndIf
        Return Self
    End Method
    
    ' Serialize an object
    Method serializeObject:jsonValue( obj:Object, parentField:TField = Null )
        If obj = Null
            Return jsonValueNull.Create()
        Else
            Local typeid:TTypeId = TTypeId.ForObject( obj )
            If typeid.ElementType()
                Return jsonSerialize1DArray( obj, Self )
            ElseIf typeHandlers.Contains( typeid )
                Local handler:jsonTypeHandlerFunc = jsonTypeHandlerFunc( typeHandlers.ValueForKey( typeid ) )
                Return jsonValue( handler.handle( obj, parentField, Self ) )
            Else
                ' look for {jsonf} and {nojsonf} tags belonging to parent field
                Local jsonf:String[] = Null
                Local nojsonf:String[] = Null
                If parentField
                    Local jsonfMeta:String = parentField.MetaData( jsonSerializeMetadataIncludeSub )
                    Local nojsonfMeta:String = parentField.MetaData( jsonSerializeMetadataExcludeSub )
                    If jsonfMeta
                        jsonf = jsonfMeta.split(",")
                    ElseIf nojsonfMeta
                        nojsonf = nojsonfMeta.split(",")
                    EndIf
                EndIf
                ' look for {json} tags
                Local serializeMetadataOnly:Int = False
                For Local member:TField = EachIn typeid.EnumFields()
                    If member.MetaData( jsonSerializeMetadataInclude )
                        serializeMetadataOnly = True
                        Exit
                    EndIf
                Next
                ' build the dict
                Local dict:jsonValueDict = jsonValueDict.Create()
                For Local member:TField = EachIn typeid.EnumFields()
                    Local add:Int = False
                    If nojsonf And metaContains( nojsonf, member.Name() )
                        add = False
                    ElseIf jsonf
                        add = metaContains( jsonf, member.Name() )
                    ElseIf  member.MetaData( jsonSerializeMetadataIncludeSub ) Or ..
                            member.MetaData( jsonSerializeMetadataExcludeSub ) Or ..
                            member.MetaData( jsonSerializeMetadataBoolean ) Or ..
                            member.MetaData( jsonSerializeMetadataClassSub )
                        add = True
                    Else
                        ' {json}
                        Local incl:Int = ( (Not serializeMetadataOnly) Or member.MetaData( jsonSerializeMetadataInclude ) )
                        ' {nojson}
                        Local excl:Int = member.MetaData( jsonSerializeMetadataExclude ) <> Null
                        ' result
                        add = incl And Not excl
                    EndIf
                    If add dict.add( member.Name(), serializeField( obj, member ) )
                Next
                Return dict
            EndIf
        EndIf
    End Method
    Method serializeField:jsonValue( obj:Object, member:TField )
        Local id:TTypeId = member.TypeId()
        Local p:Byte Ptr = bbRefFieldPtr( obj, member._index )
        If id = ByteTypeId
            Return jsonValueInt.Create( (Byte Ptr p)[0] )
        ElseIf id = ShortTypeId
            Return jsonValueInt.Create( (Short Ptr p)[0] )
        ElseIf id = IntTypeId
            If member.MetaData( jsonSerializeMetadataBoolean )
                Return jsonValueBool.Create( (Int Ptr p)[0] )
            Else
                Return jsonValueInt.Create( (Int Ptr p)[0] )
            EndIf
        ElseIf id = LongTypeId
            Return jsonValueInt.Create( (Long Ptr p)[0] )
        ElseIf id = FloatTypeId
            Return jsonValueFloat.Create( (Float Ptr p)[0] )
        ElseIf id = DoubleTypeId
            Return jsonValueFloat.Create( (Double Ptr p)[0] )
        Else
            Return serializeObject( bbRefGetObject( p ), member )
        EndIf
    End Method
    Function metaContains:Int( array:String[], sub:String )
        For Local str:String = EachIn array
            If matchWild( str, sub ) Return True
        Next
        Return False
    End Function
    
    ' Make jsonValueList from 1D array
    Function jsonSerialize1DArray:jsonValueList( obj:Object, controller:jsonSerializationController )
        Local typeid:TTypeId = TTypeId.ForObject( obj )
        Local elementid:TTypeId = typeid.ElementType()
        Local list:jsonValueList = jsonValueList.Create()
        If elementid = ByteTypeId
            Local array:Byte[] = Byte[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonValueInt.Create( array[ member ] ) )
            Next
        ElseIf elementid = ShortTypeId
            Local array:Short[] = Short[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonValueInt.Create( array[ member ] ) )
            Next
        ElseIf elementid = IntTypeId
            Local array:Int[] = Int[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonValueInt.Create( array[ member ] ) )
            Next
        ElseIf elementid = LongTypeId
            Local array:Long[] = Long[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonValueInt.Create( array[ member ] ) )
            Next
        ElseIf elementid = FloatTypeId
            Local array:Float[] = Float[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonValueFloat.Create( array[ member ] ) )
            Next
        ElseIf elementid = DoubleTypeId
            Local array:Double[] = Double[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonValueFloat.Create( array[ member ] ) )
            Next
        ElseIf elementid = StringTypeId
            Local array:String[] = String[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonValueString.Create( array[ member ] ) )
            Next
        Else
            Local array:Object[] = Object[] obj
            For Local member:Int = 0 Until array.length
                list.add( jsonSerializer( controller ).serializeObject( array[ member ] ) )
            Next
        EndIf
        Return list
    End Function
    
End Type

' Recusively turn jsonValue objects, which can be decoded from a string, into an arbitrary class with the specified fields
Type jsonDeserializer Extends jsonSerializationController
    
    ' Initialize with standard deserializers (String, TList, and TMap)
    ' Easy to add your own handlers upon calling init. Example call:
    ' json.init( [ MyFirstTypeId, MySecondTypeId ], [ jsonDeserialize1Type, jsonDeserialize2Type ] )
    Method init:jsonDeserializer( ..
        moreHandlersTypeId:TTypeId[] = Null, ..
        moreHandlersFunc:Object( obj:Object, member:TMember, controller:jsonSerializationController )[] = Null ..
    )
        addTypeHandler( StringTypeId, jsonDeserializeString )
        addTypeHandler( ListTypeId, jsonDeserializeList )
        addTypeHandler( MapTypeId, jsonDeserializeMap )
        If moreHandlersTypeId And moreHandlersFunc
            For Local i:Int = 0 Until moreHandlersTypeId.length
                addTypeHandler( moreHandlersTypeId[i], moreHandlersFunc[i] )
            Next
        EndIf
        Return Self
    End Method
    
    ' Deserialize an object
    Method deserializeObject:Object( json:jsonValue, typeid:TTypeId, parentField:TField = Null )
        If jsonValueNull( json )
            Return Null
        ElseIf typeHandlers.Contains( typeid )
            Local handler:jsonTypeHandlerFunc = jsonTypeHandlerFunc( typeHandlers.ValueForKey( typeid ) )
            Return handler.handle( json, parentField, Self )
        Else
            Local obj:Object = typeid.NewObject()
            Local dict:jsonValueDict = jsonValueDict( json )
            For Local member:TField = EachIn typeid.EnumFields()
                Local value:jsonValue = jsonValue( dict.get().ValueForKey( member.Name() ) )
                If value
                    Local p:Byte Ptr = bbRefFieldPtr( obj, member._index )
                    Local id:TTypeId = member.TypeId()
                    deserializeMember( value, p, id, member )
                EndIf
            Next
            Return obj
        EndIf
    End Method
    Method deserializeMember( value:jsonValue, p:Byte Ptr, typeid:TTypeId, parentField:TField )
        If typeid.ElementType()
            Local list:jsonValueList = jsonValueList( value )
            If list
                Local arrayLength:Int = list.get().count()
                Local arrayType:TTypeId = typeid.ElementType()
                Local array:Object = typeid.NewArray( arrayLength )
                Local arrayIndex:Int = 0
                For Local element:jsonValue = EachIn list.get()
                    Local p:Byte Ptr = bbRefArrayElementPtr( typeid.ElementType()._size, array, arrayIndex )
                    
                    deserializeMember( element, p, arrayType, parentField )
                    arrayIndex :+ 1
                Next
            Else
                Local array:Object = typeid.NewArray( 0 )
                assignObject( p, array )
            EndIf
 
        ElseIf typeid = ByteTypeId
            Local number:Byte = 0
            If jsonValueBool( value )
                number = jsonValueBool( value ).get()
            ElseIf jsonValueInt( value )
                number = jsonValueInt( value ).get()
            ElseIf jsonValueFloat( value )
                number = jsonValueFloat( value ).get()
            ElseIf jsonValueString( value )
                number = Byte jsonValueString( value ).get()
            EndIf
            (Byte Ptr p)[0] = number
        
        ElseIf typeid = ShortTypeId
            Local number:Short = 0
            If jsonValueBool( value )
                number = jsonValueBool( value ).get()
            ElseIf jsonValueInt( value )
                number = jsonValueInt( value ).get()
            ElseIf jsonValueFloat( value )
                number = jsonValueFloat( value ).get()
            ElseIf jsonValueString( value )
                number = Short jsonValueString( value ).get()
            EndIf
            (Short Ptr p)[0] = number
        
        ElseIf typeid = IntTypeId
            Local number:Int = 0
            If jsonValueBool( value )
                number = jsonValueBool( value ).get()
            ElseIf jsonValueInt( value )
                number = jsonValueInt( value ).get()
            ElseIf jsonValueFloat( value )
                number = jsonValueFloat( value ).get()
            ElseIf jsonValueString( value )
                number = Int jsonValueString( value ).get()
            EndIf
            (Int Ptr p)[0] = number
            
        ElseIf typeid = LongTypeId
            Local number:Long = 0
            If jsonValueBool( value )
                number = jsonValueBool( value ).get()
            ElseIf jsonValueInt( value )
                number = jsonValueInt( value ).get()
            ElseIf jsonValueFloat( value )
                number = jsonValueFloat( value ).get()
            ElseIf jsonValueString( value )
                number = Long jsonValueString( value ).get()
            EndIf
            (Long Ptr p)[0] = number
            
        ElseIf typeid = FloatTypeId
            Local number:Float = 0
            If jsonValueBool( value )
                number = jsonValueBool( value ).get()
            ElseIf jsonValueInt( value )
                number = jsonValueInt( value ).get()
            ElseIf jsonValueFloat( value )
                number = jsonValueFloat( value ).get()
            ElseIf jsonValueString( value )
                number = Float jsonValueString( value ).get()
            EndIf
            (Float Ptr p)[0] = number
            
        ElseIf typeid = DoubleTypeId
            Local number:Double = 0
            If jsonValueBool( value )
                number = jsonValueBool( value ).get()
            ElseIf jsonValueInt( value )
                number = jsonValueInt( value ).get()
            ElseIf jsonValueFloat( value )
                number = jsonValueFloat( value ).get()
            ElseIf jsonValueString( value )
                number = Double jsonValueString( value ).get()
            EndIf
            (Double Ptr p)[0] = number
            
        Else
            assignObject( p, deserializeObject( value, typeid, parentField ) )
            
        EndIf
    End Method
    Function assignObject( p:Byte Ptr, value:Object )
        If value
            Local id:TTypeId = TTypeId.ForObject( value )
            Local class:Int = bbRefGetObjectClass( value )
            While class And class <> id._class
                class = bbRefGetSuperClass( class )
            Wend
        EndIf
        bbRefAssignObject( p, value )
    End Function
    
    ' Determine TypeId that jsonValue should deserialize to when bmax doesn't explitly specify
    ' (e.g. objects within a list)
    Function jsonValueTypeId:TTypeId( value:jsonValue, member:TMember )
        Local jsonc:String
        If member jsonc = member.MetaData( jsonSerializeMetadataClassSub )
        If jsonc
            Return TTypeId.ForName( jsonc )
        ElseIf jsonValueString( value )
            Return StringTypeId
        ElseIf jsonValueList( value )
            Return ListTypeId
        ElseIf jsonValueDict( value )
            Return MapTypeId
        Else
            Return ObjectTypeId
        EndIf
    End Function
     
End Type

' Base json value type
Type jsonValue

    ' Get json string from object
    Method toString:String()
        Return Null
    End Method
    
    ' Get object from json string
    Function fromString:jsonValue( str:String, low:Int = 0, high:Int = 0 )
        If high = 0 high = str.length
        
        ' Get next occurence of character in same scope, considering quotes, brackets, etc.
        Function nextChar:Int( str:String, char:Int, low:Int, high:Int )
            Local nestBrace:Int = 0, nestBracket:Int = 0, inQuote:Int = False
            Local i:Int = low
            While i < high
                If nestBrace = 0 And nestBracket = 0 And inQuote = False And str[i] = char
                    Return i
                ElseIf str[i] = jsonEscapeAsc
                    i :+ 1
                ElseIf str[i] = jsonStringAsc
                    inQuote = Not inQuote
                ElseIf str[i] = jsonOpenBraceAsc
                    nestBrace :+ 1
                ElseIf str[i] = jsonOpenBracketAsc
                    nestBracket :+ 1
                ElseIf str[i] = jsonCloseBraceAsc
                    nestBrace :- 1
                ElseIf str[i] = jsonCloseBracketAsc
                    nestBracket :- 1
                EndIf
                i :+ 1
            Wend
            Return -1
        End Function
        
        ' Skip whitespace
        While str[ low ] < 32
            low :+ 1
        Wend
        
        ' Parse string
        If str[ low ] = jsonStringAsc
            Return jsonValueString.Create( jsonParseString( str, low, high ) )
            
        ' Parse dict
        ElseIf str[ low ] = jsonOpenBraceAsc
            Local dict:jsonValueDict = jsonValueDict.Create()
            Local i:Int = low+1
            While i < high-1
                Local assign:Int = nextChar( str, jsonAssignmentAsc, i, high-1 )
                Local delim:Int = nextChar( str, jsonDelimiterAsc, i, high-1 )
                Assert assign >= 0, "Syntax error: Missing assignment character"
                Local key:String = jsonParseString( str, i, assign )
                If delim >= 0
                    Local value:jsonValue = jsonValue.fromString( str, assign+1, delim )
                    dict.add( key, value )
                    i = delim+1
                Else
                    Local value:jsonValue = jsonValue.fromString( str, assign+1, high-1 )
                    dict.add( key, value )
                    Exit
                EndIf
            Wend
            Return dict
        
        ' Parse list 
        ElseIf str[ low ] = jsonOpenBracketAsc
            Local list:jsonValueList = jsonValueList.Create()
            Local i:Int = low+1
            While i < high-1
                Local delim:Int = nextChar( str, jsonDelimiterAsc, i, high-1 )
                If delim >= 0
                    list.add( jsonValue.fromString( str, i, delim ) )
                    i = delim+1
                Else
                    list.add( jsonValue.fromString( str, i, high-1 ) )
                    Exit
                EndIf
            Wend
            Return list
        
        ' Parse others
        Else
            Local sub:String = str[ low..high ]
            
            ' Parse number
            If str[ low ] >= 48 And str[ low ] <= 57
                Local isDecimal:Int = False
                For Local i:Int = low Until high
                    If str[i] = jsonDecimalAsc isDecimal = True; Exit
                Next
                If isDecimal
                    Return jsonValueFloat.Create( Double( sub ) )
                Else
                    Return jsonValueInt.Create( Long( sub ) )
                EndIf
            
            ' Parse keywords
            ElseIf sub = "null"
                Return jsonValueNull.Create()
            ElseIf sub = "true"
                Return jsonValueBool.Create( True )
            ElseIf sub = "false"
                Return jsonValueBool.Create( False )
                
            EndIf
        EndIf
        
        Throw "Syntax error: Unexpected character "+Chr( str[ low ] )
    End Function
    
    ' Utility functions for strings
    Function jsonSanitizeString:String( str:String )
        Return str.Replace( "~q", "\~q" )
    End Function
    Function jsonDesanitizeString:String( str:String )
        Return str.Replace( "\~q", "~q" )
    End Function
    Function jsonParseString:String( str:String, low:Int, high:Int )
        Local sub:String = str[ low..high ]
        sub = sub.Trim()
        Local qstart:Int = (sub[0] = jsonStringAsc)
        Local qend:Int = (sub[ sub.length-1 ] = jsonStringAsc) And (sub.length < 2 Or sub[ sub.length-2 ] <> jsonEscapeAsc)
        If qstart Or qend sub = sub[ qstart .. sub.length-qend ]
        Return jsonDesanitizeString( sub )
    End Function
    
End Type

' Null type
Type jsonValueNull Extends jsonValue
    Function Create:jsonValueNull()
        Return New jsonValueNull
    End Function
    Method toString:String()
        Return "null"
    End Method
    Method get:Object()
        Return Null
    End Method
End Type

' Boolean type
Type jsonValueBool Extends jsonValue
    Field value:Int
    Function Create:jsonValueBool( value:Int = 0:Int )
        Local this:jsonValueBool = New jsonValueBool; this.value = value; Return this
    End Function
    Method toString:String()
        If value Return "true" Else Return "false"
    End Method
    Method get:Int()
        Return value
    End Method
    Method set( value:Int )
        Self.value = value
    End Method
End Type

' Integer type
Type jsonValueInt Extends jsonValue
    Field value:Long
    Function Create:jsonValueInt( value:Long = 0:Long )
        Local this:jsonValueInt = New jsonValueInt; this.value = value; Return this
    End Function
    Method toString:String()
        Return String( value )
    End Method
    Method get:Long()
        Return value
    End Method
    Method set( value:Long )
        Self.value = value
    End Method
End Type

' Float type
Type jsonValueFloat Extends jsonValue
    Field value:Double
    Function Create:jsonValueFloat( value:Double = 0:Double )
        Local this:jsonValueFloat = New jsonValueFloat; this.value = value; Return this
    End Function
    Method toString:String()
        Return String( value )
    End Method
    Method get:Double()
        Return value
    End Method
    Method set( value:Double )
        Self.value = value
    End Method
End Type

' String type
Type jsonValueString Extends jsonValue
    Field value:String
    Function Create:jsonValueString( value:String = "" )
        Local this:jsonValueString = New jsonValueString; this.value = value; Return this
    End Function
    Method toString:String()
        Return "~q" + jsonSanitizeString( value ) + "~q"
    End Method
    Method get:String()
        Return value
    End Method
    Method set( value:String )
        Self.value = value
    End Method
End Type

' List type
Type jsonValueList Extends jsonValue
    Field value:TList
    Function Create:jsonValueList( value:TList = Null )
        If value = Null value = CreateList()
        Local this:jsonValueList = New jsonValueList; this.value = value; Return this
    End Function
    Method toString:String()
        Local content:String = "", first:Int = True
        For Local member:jsonValue = EachIn value
            If first first = False Else content :+ ","
            content :+ member.toString()
        Next
        Return "[" + content + "]"
    End Method
    Method get:TList()
        Return value
    End Method
    Method set( value:TList )
        Self.value = value
    End Method
    
    Method add:TLink( value:jsonValue )
        Return Self.value.addlast( value )
    End Method
End Type

' Dict type
Type jsonValueDict Extends jsonValue
    Field value:TMap
    Function Create:jsonValueDict( value:TMap = Null )
        If value = Null value = CreateMap()
        Local this:jsonValueDict = New jsonValueDict; this.value = value; Return this
    End Function
    Method toString:String()
        Local content:String = "", first:Int = True
        For Local key:Object = EachIn value.Keys()
            If first first = False Else content :+ ","
            Local member:Object = value.ValueForKey( key )
            content :+ "~q" + jsonSanitizeString( String( key ) ) + "~q:" + jsonValue( member ).toString()
        Next
        Return "{" + content + "}"
    End Method
    
    Method get:TMap()
        Return value
    End Method
    Method set( value:TMap )
        Self.value = value
    End Method
    Method add( key:Object, value:jsonValue )
        Self.value.insert( key, value )
    End Method
End Type

Comments

None.

Code Archives Forum