JSON can be a bit a learn curve initially at least. This is Serialiser for Arrays works a treat. I still use and can also can be read natively by PHP. Enough examples in the code... Enjoy
ACCESS Array2PHPString AS STRING PASCAL CLASS Convert2PHPStringArray
RETURN SELF:cPHPString
CLASS Convert2PHPStringArray
PROTECT aProcessed := {} AS ARRAY // Phil McGuinness - APRIL, 2006
PROTECT cPHPString := "" AS STRING // cPHPString := Convert2PHPString{ aArray }:Array2PHPString
PROTECT nLenArrayLevel1 AS DWORD
PROTECT nLenArrayLevel2 AS DWORD
PROTECT aDeserialized := {} AS ARRAY
//
DECLARE METHOD DataType // Build aProcessed ARRAY with substrings for PHPString output
DECLARE ACCESS Array2PHPString // Return the PHPString [ ie a:2:{i:0;s:1:"a";i:1;a:3:{i:0;s:1:"c";i:1;s:1:"d";i:2;s:4:"e";}} ]
// Deserialization - Ales, 01/02/2013
DECLARE ACCESS PHPString2Array // Return the VO array
DECLARE METHOD DeserializePHPArray // Called internally if the argument in Init() is a string
DECLARE METHOD ExtractTextBetweenMarkers
DECLARE METHOD ExtractPHPArrayString
//
METHOD DataType( uWorking AS USUAL, nElement AS DWORD, nArrayElements AS DWORD, symLevel AS SYMBOL) AS VOID PASCAL CLASS Convert2PHPStringArray
LOCAL cSubstring, cType := [s:] AS STRING
LOCAL cPreFix := "", cPostFix := "" AS STRING
LOCAL nItems AS DWORD
DO CASE
CASE IsLogic( uWorking )
cType := [b:] + IIF(uWorking, "1","0")
//
CASE IsString( uWorking )
// IF SLen(uWorking) <= 10 .AND. CToD(uWorking) != NULL_DATE // 02:10 PM passes as CTOD() date, and the date s:10:"02/01/0349"
IF SLen(uWorking) <= 10 .AND. CToD(uWorking) != NULL_DATE .AND. Occurs(uWorking, "/") = 2 // Change PMG 05/09/2014
cSubstring := DToC(CToD(uWorking))
ELSE
cSubstring := AllTrim(StrTran(uWorking, ["], _CHR(32) )) // Stringify result
ENDIF
cType := [s:] + NTrim(SLen(cSubstring)) + [:"] + cSubstring + ["]
//
CASE IsNumeric( uWorking )
cSubstring := AllTrim(AsString(uWorking)) // Stringify result
cType := IIF( Frac(uWorking) != 0, [d:] , [i:] ) + cSubstring
//
CASE IsDate( uWorking )
cSubstring := DToS(uWorking)
cType := [s:] + NTrim(SLen(cSubstring)) + [:"] + cSubstring + ["]
//
OTHERWISE
cSubstring := AllTrim(AsString(uWorking)) // Stringify result
ENDCASE
//
IF InList( symLevel, #LEVEL1, #LEVEL2 )
//
DO CASE
CASE symLevel = #LEVEL1 ; nItems := SELF:nLenArrayLevel1
CASE symLevel = #LEVEL2 ; nItems := SELF:nLenArrayLevel2
ENDCASE
//
IF nElement = 1
cPreFix := "i:" + AsString( nArrayElements - 1 ) + ";a:" + AsString( nItems ) + ":{"
//
ELSEIF nElement = nItems
cPostFix := "}"
ENDIF
ENDIF
//
AAdd( SELF:aProcessed, cPreFix + [i:] + AsString(nElement-1) + ";" + cType + ";" + cPostFix )
//
RETURN
METHOD Init( uUserData ) CLASS Convert2PHPStringArray
LOCAL nLenArray, xx, yy, zz AS DWORD // Allow for Single Dimension and up to 3 dimension array.
IF IsArray( uUserData )
nLenArray := ALen( uUserData )
SELF:cPHPString := [a:] + AsString( nLenArray ) + [:] // a:6:
SELF:cPHPString += '{'
//
FOR xx := 1 TO nLenArray // ?? elements
IF IsArray( uUserData[xx] )
SELF:nLenArrayLevel1 := ALen( uUserData[xx] )
//
FOR yy := 1 TO SELF:nLenArrayLevel1 // ?? elements
SELF:DataType( uUserData[xx][yy], yy, xx, #LEVEL1 )
IF IsArray( uUserData[xx][yy] )
SELF:nLenArrayLevel2 := ALen( uUserData[xx][yy] )
//
FOR zz := 1 TO SELF:nLenArrayLevel2 // ?? elements
SELF:DataType( uUserData[xx][yy][zz], zz, yy, #LEVEL2 )
NEXT
SELF:nLenArrayLevel2 := 0
ENDIF
NEXT
SELF:nLenArrayLevel1 := 0
ELSE
SELF:DataType( uUserData[xx], xx, xx, #LEVEL0 )
ENDIF
NEXT
//
FOR xx := 1 TO ALen( SELF:aProcessed )
SELF:cPHPString += SELF:aProcessed[xx]
NEXT
SELF:cPHPString += '}'
SELF:aProcessed := {}
//
ELSEIF IsString( uUserData )
SELF:aDeserialized := SELF:DeserializePHPArray(AsString(uUserData))
ENDIF
//
RETURN SELF
// FUNCTION Start() AS STRING
// LOCAL aArray AS ARRAY
// aArray := { 1, "2", "2/03/2006", 1.234 }
// aArray := { {"a","b","c"},{"d","e","f"}}
// aArray := { "a",{ "c","d","e" } }
//
// cPHPString := Convert2PHPStringArray{ aArray }:Array2PHPString
// ==========================
// aArray := { 1, "2", "2/03/2006", 1.234 } // INPUT
// a:4:{i:0;i:1;i:1;s:1:"2";i:2;s:9:"2/03/2006";i:3;d:1.24;} // OUTPUT
// ==========================
// aArray := { {"a","b","c"},{"d","e","f"}} // INPUT
// a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}} // OUTPUT
// ==========================
// aArray := { { "a",{ "c","d","e" }} } // INPUT
// a:2:{i:0;s:1:"a";i:1;a:3:{i:0;s:1:"c";i:1;s:1:"d";i:2;s:4:"e";}} // OUTPUT
// ==========================
// MemoWrit( "c:" + 'phptest.txt', cPhpString)
// ShellExecute( NULL, String2Psz("open"), String2Psz("notepad.exe"), String2Psz("c:" + 'phptest.txt'), NULL, SW_SHOWNORMAL )
//
// aArray := xConvert2PHPStringArray{ 'a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}}' }:PHPString2Array
//
// RETURN NIL
// But this works:
//x := xConvert2PHPStringArray{ 'a:4:{i:0;i:1;i:1;s:1:"2";i:2;s:9:"2/03/2006";i:3;d:1.24;}' }
//x := xConvert2PHPStringArray{ 'a:2:{i:0;s:2:"AB";i:1;s:2:"CD";}' }
//x := xConvert2PHPStringArray{ 'a:6:{i:0;i:33;i:1;d:12.53999999999999914734871708787977695465087890625;i:2;s:4:"strA";i:3;s:3:"q"q";i:4;s:3:"w"w";i:5;s:6:"abcdef";}' }
//x := xConvert2PHPStringArray{ 'a:2:{i:0;a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}i:1;a:3:{i:0;s:1:"d";i:1;s:1:"e";i:2;s:1:"f";}}' }
// aArray := x:PHPString2Array
// aArray := { 2, 3, 4 }
// aArray := { 1, "2", "2/03/2006", 1.234 }
// aArray := { {"a","b","c"},{"d","e","f"}}
// aArray := { "a",{ "c","d","e" } }
//
METHOD DeserializePHPArray(cPHPString AS STRING) AS ARRAY PASCAL CLASS Convert2PHPStringArray
LOCAL aOut := { } AS ARRAY
LOCAL nArrlen, nLen, nNEXT, nIndex, nValue, nStrLen AS DWORD
LOCAL iValue AS LONGINT
LOCAL rValue AS REAL8
LOCAL lTypeExpected := FALSE AS LOGIC
LOCAL lIsOK := TRUE AS LOGIC
LOCAL cTmp AS STRING
LOCAL dtValue AS DATE
IF SubStr(cPHPString, 1, 2) == "a:" // Validation if the PHP array really starts with "a:"
nLen := SLen(cPHPString)
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ":{", 1, @nNEXT)
//
IF SLen(cTmp) > 0 // Notice my paranoia
nArrlen := DWORD(Val(cTmp)) // Getting the number of a PHP array members
aOut := ArrayCreate(nArrlen)
//
DO WHILE nNEXT <= nLen .AND. lIsOK
//
IF lTypeExpected == FALSE
IF SubStr(cPHPString, nNEXT, 1) == "}" // End of current array - stop parsing
lIsOK := FALSE
//
ELSEIF SubStr(cPHPString, nNEXT, 2) == "i:" // Index
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
nIndex := DWORD(Val(cTmp)) + 1 // PHP index starts with 0, VO starts with 1
lTypeExpected := TRUE
ELSE
lIsOK := FALSE // Can't get an index
ENDIF
ELSE
lIsOK := FALSE // Something's not right, stop parsing
ENDIF
ELSE // This branch deals with types
DO CASE
CASE SubStr(cPHPString, nNEXT, 2) == "i:" // Integer or DWORD
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
iValue := LONGINT(Val(cTmp))
IF iValue < 0 // If it's less than zero, store it as LONGINT, otherwise as DWORD
aOut[nIndex] := iValue
ELSE
nValue := DWORD(iValue)
aOut[nIndex] := nValue
ENDIF
lTypeExpected := FALSE
ELSE
lIsOK := FALSE // Something's not right, stop parsing
ENDIF
// ==========
CASE SubStr(cPHPString, nNEXT, 2) == "d:" // Double
cTmp := SELF:ExtractTextBetweenMarkers(cPHPString, ":", ";", nNEXT, @nNEXT)
IF SLen(cTmp) > 0
rValue := Val(cTmp)
aOut[nIndex] := rValue // Store as REAL8
lTypeExpected := FALSE
ELSE
lIsOK := FALSE // Somethi