POC
FiveWin
Harbour
What it does
Recursively walks a JSON tree, flattens it into rows (PATH, TYPE, VALUE), sorts by PATH,
and shows the result in XBROWSER()
. The loader strips UTF-8 BOM and trailing control characters (incl. 0x1A
/^Z
) and can handle a JSON string that itself contains JSON (double-encoded).
Usage
- Adjust the path:
c:\fwh\samples\sample.json
(or changecJsonFile
). - Compile with your regular FWH build (e.g.
buildh.bat
/hbmk2
). - Run the EXE → an XBROWSER window opens with the flattened rows.
Tip: end the file with
}
or ]
. If your writer appends ^Z
(0x1A
), the loader removes it.
Features
- Recursive flattening to PATH / TYPE / VALUE
- XBROWSER table view
- UTF-8 BOM / trailing control-char tolerant
- Optional double-decode for JSON-in-JSON strings
- Copy-ready source below
JsonFlattenBrowse.prg
/* JsonFlattenBrowse.prg – JSON rekursiv in 2D-Array für XBROWSER (FiveWin) */
#include "fivewin.ch"
#include "fileio.ch"
PROCEDURE Main()
LOCAL cJsonFile := "c:\fwh\samples\sample.json"
LOCAL uRoot, aRows := {}
IF ! LoadJsonTolerant( cJsonFile, @uRoot )
MsgStop( "JSON decode failed: " + cJsonFile )
RETURN
ENDIF
// Rekursiv auflösen → aRows füllen { PATH, TYPE, VALUE }
JsonFlattenToRows( uRoot, "", @aRows )
// Optional: sortieren nach PATH
ASort( aRows, , , {|a,b| a[1] < b[1] } )
// Anzeigen (FWH helper)
XBROWSER( aRows ) // Hinweis: in FWH heißt der Helper üblicherweise XBROWSER, nicht XBROWSE
InKey(0)
RETURN
/* Rekursiv: füllt aRows mit { cPath, cType, cValue } */
FUNCTION JsonFlattenToRows( x, cPath, aRows )
LOCAL i, cKey, cNextPath
DO CASE
CASE ValType( x ) == "H" // Hash/Object
FOR EACH cKey IN hb_HKeys( x )
cNextPath := IIF( Empty( cPath ), cKey, cPath + "." + cKey )
JsonFlattenToRows( x[ cKey ], cNextPath, aRows )
NEXT
CASE ValType( x ) == "A" // Array
FOR i := 1 TO Len( x )
cNextPath := cPath + "[" + LTrim(Str(i)) + "]"
JsonFlattenToRows( x[i], cNextPath, aRows )
NEXT
OTHERWISE // Skalar
AAdd( aRows, { cPath, ValType( x ), JF_ToString( x ) } )
ENDCASE
RETURN NIL
/* Werte hübsch in String wandeln (Skalare); Strukturen kompakt als JSON */
FUNCTION JF_ToString( v )
LOCAL t := ValType( v )
DO CASE
CASE t == "C" ; RETURN v
CASE t == "N" ; RETURN hb_ntos( v )
CASE t == "D" ; RETURN DToC( v )
CASE t == "L" ; RETURN IIF( v, ".T.", ".F." )
CASE t == "U" ; RETURN ""
CASE t $ "HA" ; RETURN hb_jsonEncode( v ) // falls hier doch Struktur ankommt
OTHERWISE ; RETURN hb_ValToExp( v )
ENDCASE
RETURN ""
/* ---- Robust JSON load: handles BOM, trailing control chars, and differing
hb_jsonDecode() return conventions (logical OR numeric). ---- */
FUNCTION LoadJsonTolerant( cFile, uOut )
LOCAL c := FileReadRaw( cFile )
LOCAL cInner, vRet
IF Empty( c )
RETURN .F.
ENDIF
c := StripBom( c ) // remove EF BB BF if present
c := RTrimCtl( c ) // strip trailing control chars (incl. 0x1A/^Z, CR/LF, NUL, DEL)
/* 1) normal decode */
vRet := hb_jsonDecode( c, @uOut )
IF __JsonOk( vRet, uOut, c )
RETURN .T.
ENDIF
/* 2) double-encoded case: file contains a JSON *string* which itself is JSON */
cInner := NIL
vRet := hb_jsonDecode( c, @cInner )
IF __JsonOk( vRet, cInner, c ) .AND. HB_ISCHAR( cInner )
vRet := hb_jsonDecode( cInner, @uOut )
IF __JsonOk( vRet, uOut, cInner )
RETURN .T.
ENDIF
ENDIF
RETURN .F.
/* Decide “success” across Harbour variants:
- logical TRUE → ok
- numeric > 0 (bytes parsed) → ok
- uVal got filled (some builds return NIL but fill @uVal) → ok
- literal "null" input → ok (maps to NIL) */
STATIC FUNCTION __JsonOk( vRet, uVal, cIn )
LOCAL lOk := .F.
IF HB_ISLOGICAL( vRet )
lOk := vRet
ELSEIF HB_ISNUMERIC( vRet )
lOk := ( vRet > 0 )
ELSEIF !HB_ISNIL( uVal )
lOk := .T.
ELSEIF Upper( AllTrim( cIn ) ) == "NULL"
lOk := .T.
ENDIF
RETURN lOk
/* --- helpers from your existing file (unchanged) --- */
FUNCTION FileReadRaw( cFile )
LOCAL nH := FOpen( cFile, FO_READ ), c := "", nSize
IF nH < 0
RETURN c
ENDIF
nSize := FSeek( nH, 0, FS_END )
FSeek( nH, 0, FS_SET )
IF nSize > 0
c := Space( nSize )
FRead( nH, @c, nSize )
ENDIF
FClose( nH )
RETURN c
FUNCTION StripBom( c )
IF Len( c ) >= 3 .AND. SubStr( c, 1, 3 ) == Chr(239)+Chr(187)+Chr(191)
RETURN SubStr( c, 4 )
ENDIF
RETURN c
FUNCTION RTrimCtl( c )
LOCAL n := Len( c ), k
DO WHILE n > 0
k := Asc( SubStr( c, n, 1 ) )
IF ( k >= 0 .AND. k <= 31 ) .OR. k == 127
n--
ELSE
EXIT
ENDIF
ENDDO
IF n < Len( c )
RETURN Left( c, n )
ENDIF
RETURN c
sample.json
{
"Mensaje": {
"Body": {
"Pedidos": {
"Detalles": [
{ "pedaux": 1, "pedcpr": "0007019", "pedctd": 12 },
{ "pedaux": 2, "pedcpr": "0007020", "pedctd": 8 }
]
}
}
}
}