[bmx] Convert an Object to JSON by N [ 1+ years ago ]

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

Previous topic - Next topic

BlitzBot

Title : Convert an Object to JSON
Author : N
Posted : 1+ years ago

Description : This does about what it says, using reflection.  The only thing that doesn't really work too well is multidim arrays, because those are grade-A insanity to work with.  Objects that have already been converted are referenced only by their handle (which is, to be perfectly honest, just the their memory address, but it works well enough) - no type name is provided for them, since they've already been mentioned at least once before the current object/field/what have you in the JSON.

To use it, you call ObjectToJSON(obj, map) where obj is the object you want to convert to JSON and map is a TMap you'll use to store which objects have already been converted to JSON.  If objects have dependencies on one-another or reference each other, you should try to create one TMap for that group of objects so as to reduce the size of the JSON output.


Code :
Code (blitzmax) Select
SuperStrict

Private
Function _objtoptr:Byte Ptr(obj:Byte Ptr)
Return obj
End Function
Global ObjToPtr:Byte Ptr(obj:Object)=Byte Ptr(_objtoptr)

Function FieldToJSON:String(fid:TField, forObject:Object, map:TMap)
Select fid.TypeId()
Case IntTypeId,ShortTypeId,ByteTypeId
Return String(fid.GetInt(forObject))
Case LongTypeId
Return String(fid.GetLong(forObject))
Case DoubleTypeId
Return String(fid.GetDouble(forObject))
Case FloatTypeId
Return String(fid.GetFloat(forObject))
Default
Return ObjectToJSON(fid.Get(forObject), map)
End Select
End Function

Public

' The map argument is used to record what objects have already been put in a prior part of any JSON string
Function ObjectToJSON:String(obj:Object, map:TMap)
Assert map Else "Inspection map not present"

Local name$ = Int(ObjToPtr(obj))
Local result$

Local tid:TTypeId = TTypeId.ForObject(obj)

If tid = Null Then
Return "null"
EndIf

If tid <> StringTypeId And tid._class <> ArrayTypeId._class And map.Contains(name) Then
Return "{~qhandle~q: "+name+"}"
EndIf

map.Insert(name,name)

If tid._class = ArrayTypeId._class Then
If tid.Name().StartsWith("Null[") Then
Return "null"
EndIf

result = "{~qhandle~q: "+name+", ~qtype~q: ~q"+tid.Name()+"~q"
Local elemt:TTypeId = tid.ElementType()
If elemt Then
Local dimensions:Int = tid.ArrayDimensions(obj)
result :+ ", dimensions: ["

Local dimString$ = ""
Local last:Int = 1
For Local dim:Int = dimensions-1 To 0 Step -1
Local dimLength:Int = tid.ArrayLength(obj, dim)

If dim < dimensions-1 Then
dimString = ", " + dimString
EndIf

dimString = (dimLength / last) + dimString
last = dimLength
Next
result :+ dimString
result :+ "]"

Local elems:String[] = New String[tid.ArrayLength(obj, 0)]
For Local i:Int = 0 Until elems.Length
Select elemt
Case IntTypeId, ShortTypeId, ByteTypeId, LongTypeId, DoubleTypeId, FloatTypeId
elems[i] = String(tid.GetArrayElement(obj, i))
Default
elems[i] = ObjectToJSON(tid.GetArrayElement(obj, i), map)
End Select
Next

result :+ ", ~qcontent~q: ["+(", ".Join(elems))+"]}"
Else
result :+ ", ~qcontent~q: []}"
EndIf
ElseIf tid = StringTypeId
If String(obj) = Null Then
Return "~q~q"
EndIf
result = "~q" + String(obj).Replace("~q", "~q").Replace("~n", "
").Replace("~t", " ").Replace("~r", "").Replace("~0", "") + "~q"
Else
If Not obj Then
Return "null"
EndIf

result = "{~qhandle~q: "+name+", ~qtype~q: ~q"+tid.Name()+"~q"

Local fields:TList = tid.EnumFields()
Local enum:TListEnum = fields.ObjectEnumerator()

While enum.HasNext()
Local fid:TField = TField(enum.NextObject())
result :+ ", ~q."+fid.Name()+"~q: "+FieldToJSON(fid, obj, map)
Wend

result :+ "}"
EndIf

Return result
End Function


Comments :


N(Posted 1+ years ago)

 Updated to have slightly better multidimensional array support.  An array of integers called 'dimensions' will precede the 'content' field for storing array data.  This contains the length of each array dimension (for some reason brl.Reflection's TTypeID#ArrayLength reports the length of a dimension in terms of every element accessible from a dimension onward, so this only includes the size of each dimension on its own).