JSONFox is a free JSON / XML parser for Visual FoxPro 9.0
Formas de apoyar:
Irwin Rodríguez (Toledo, Spain)
If you want to speed up the lexing process then you should use the JSONFoxHelper dll built in C# that scans the tokens faster. In order to use this scanner instead of the native one you just need to activate the NetScanner property like the example below:
// activate the property before using any routine from json class.
_Screen.Json.netScanner = .T.
_Screen.Json.Parse(myJSONString)I did a timing parsing a json file which size was 8mb. The native scanner lasts more than 15 minutes and the new scanner just 93 seconds. Pretty amazing isn't it?
Remember register the JSONFoxHelper.dll before using it. Check this article if you got stuck: https://stackoverflow.com/questions/7092553/turn-a-simple-c-sharp-dll-into-a-com-interop-component
- Faster Lexical Analizer
- I've optimised the string recogniser which is faster now.
- Special characters and Hex values should be faster
- Faster Parser
- The whole algorith has been rewriten and now creates a real array instead an object.
- Faster DATE and DATETIME recogniser.
- Faster JSON to cursor conversion
- Now it used sorted dictionaries instead of plain arrays
* Now you can use JSONFox as a compiled App...
Do LocFile("JSONFox", "app")
* Parse a string into an object.
MyObj = _Screen.Json.Parse('{"foo": "bar"}')
?MyObj.foo
**New** you may also parse any valid JSON string.
?_Screen.Json.Parse('"bar"') && bar
?_Screen.Json.Parse('true') && .T.
?_Screen.Json.Parse('false') && .F.
?_Screen.Json.Parse('null') && .NULL.
?_Screen.Json.Parse('1985') && 1985
* Convert Cursor into JSON string.
Create Cursor cGames (game c(25), launched i(4))
Insert into cGames Values('Pac-Man', 1980)
Insert into cGames Values('Super Mario Bros', 1985)
Insert into cGames Values('Space Invaders', 1978)
Insert into cGames Values('The Legend of Zelda', 1986)
?_Screen.Json.CursorToJson('cGames')
* Convert any cursor structure into JSON
?_Screen.Json.CursorStructure('cGames')If you prefer a lightweight, silent solution, use JsonFox.fxp. A single compiled file containing the entire JSON pipeline — no external dependencies, no _Screen pollution, no DLL registration needed.
Why it exists: perfect for API integrations where you need to parse/stringify JSON in the background — inside a stored procedure, a VFP service, or any context where loading a full APP is overkill.
* Instantiate — one file, nothing else
oJson = NEWOBJECT("JSONFox", "JsonFox.fxp")
* ── Same API as the APP version ──
* Parse
loObj = oJson.Parse('{"name":"JSONFox","version":13}')
?loObj.name && JSONFox
?loObj.version && 13
* Root values
?oJson.Parse('true') && .T.
?oJson.Parse('null') && .NULL.
?oJson.Parse('"Hello"') && Hello
?oJson.Parse('2025') && 2025
* Stringify (pretty-print)
?oJson.Stringify('{"compact":true,"items":[1,2,3]}')
* Serialize a VFP object to JSON
loVfp = CREATEOBJECT("Empty")
ADDPROPERTY(loVfp, "lang", "VFP")
ADDPROPERTY(loVfp, "year", 1992)
?oJson.Stringify(@loVfp)
* Cursor → JSON
CREATE CURSOR qProds (id I, name C(20))
INSERT INTO qProds VALUES (1, "Widget")
INSERT INTO qProds VALUES (2, "Gadget")
?oJson.CursorToJSON("qProds")
* JSON array → Cursor
oJson.JSONToCursor('[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]', "qUsers")
* Error handling (silent, no UI prompts)
IF oJson.lError
?oJson.cLastError
ENDIF
* Multiple independent instances (each with its own datasession)
oApi1 = NEWOBJECT("JSONFox", "JsonFox.fxp")
oApi2 = NEWOBJECT("JSONFox", "JsonFox.fxp")Full API: Parse, Stringify, CursorToJSON, JSONToCursor, CursorToJSONObject, CursorStructure, MasterDetailToJSON, ArrayToXML, XMLToJson.
Properties: lError, cLastError, UseArrayObjects (.T. by default), version.
_Screen.Json.CursorToJSON(tcCursor As String [, tbCurrentRow, tnDataSession, tbJustArray, tbParseUTF8, tbTrimChars])
tcCursor: the name of your cursor.
tbCurrentRow: ¿Would you like to serialize the current row? .F. as default.
tnDataSession: Provide this parameter if you're working in a private session.
tbJustArray: if is set to .T. then you get just an array with the data, otherwise you'll get an object containing both the cursor name and the array data.
tbParseUTF8: kept for compatibility. Since 13.2 every character outside printable ASCII is written as \uXXXXof its Unicode code point, whatever this parameter says. Eg: 'é' => '\u00E9', '€' => '\u20AC'
tbTrimChars: if is set to .T. then all right blank spaces will be trimed.
| Field | CursorToJSON writes | JSONToCursor reads it back as |
|---|---|---|
| C, V, M | string; non-ASCII as \uXXXX of the Unicode character (€ is \u20AC), control characters as \u00XX |
C (M past 254) |
| N, F, Y | number with the field's decimals (1234.50) |
N(w,d) when every row has the same decimals |
| I | integer (42) |
I (N(w,0) past 2147483647) |
| B | the shortest decimal that reads back as the same Double (0.30000000000000004, 0.3333333333333333) |
B, bit for bit |
| D, T | ISO 8601 ("1990-05-17", "2026-10-30T10:20:30"); empty: null |
D, T |
| L | true / false |
L |
| Q, W | base64 ("QUIAQw==") |
C with the base64 text: decode it with STRCONV(x, 14) |
| NULL | null |
NULL |
JSONToCursor puts a number in a B field when it has an exponent (1e-7), more than 15 significant digits or more than 20 characters, and when the rows of a column have different decimals (3.14159 and 0). So CursorToJSON → JSONToCursor → CursorToJSON gives the same JSON. The conversion to and from \uXXXX uses CPCURRENT(); a character the code page does not have comes back as ?.
ES. Desde la 13.2, CursorToJSON escribe Varbinary y Blob en base64, cada carácter fuera del ASCII como \uXXXX de su código Unicode (el € es \u20AC) y un Double con el decimal más corto que vuelve al mismo Double. JSONToCursor manda a un campo B los números con exponente, con más de 15 cifras significativas o de más de 20 de ancho, y las columnas con decimales distintos en cada fila: la ida y la vuelta dan el mismo JSON.
DE. Seit 13.2 schreibt CursorToJSON Varbinary und Blob als Base64, jedes Zeichen außerhalb von ASCII als \uXXXX seines Unicode-Codepoints (das € ist \u20AC) und ein Double mit der kürzesten Dezimalzahl, die wieder denselben Double ergibt. JSONToCursor legt Zahlen mit Exponent, mit mehr als 15 signifikanten Stellen oder mehr als 20 Zeichen sowie Spalten mit unterschiedlichen Nachkommastellen in einem B-Feld an: Hin- und Rückweg ergeben dasselbe JSON.
* Sample 1: Serialize JSON String
Do LocFile("JSONFox", "app")
* Parse from string
Text To lcJsonStr NoShow
{
"name":"Irwin",
"surname": "Rodriguez",
"birth":"1985-11-15",
"current_year": 2019,
"wife": "Serelys Fonseca",
"music_band":"The Beatles",
"size": 1.79,
"isgamer": false,
"isprogrammer": true,
"hascar": null
}
EndText
obj = _Screen.Json.Parse(lcJsonStr)
* Don't forget check the LastErrorText
If _Screen.Json.lError
MessageBox(_Screen.Json.LastErrorText, 48, "Something went wrong")
Return
EndIf
?obj.name
?obj.size
* Deserialize and Indent
cJSONStr = _Screen.Json.Stringify(obj)
?cJSONStr
* Sample 2: JSONArray to Cursor
Text To lcStr NoShow
{
"status": "success",
"data": [
{
"id": 2,
"email": "rodriguez.irwin@gmail.com",
"name": "Irwin1985",
"lastName": "Rodriguez",
"sex": "1",
"salary": 2278.45,
"profesion_id": 1,
"birthDate": "1985-11-15",
"createdAt": "2019-03-31",
"single": true
},
{
"id": 3,
"email": "oscaraguero85@gmail.com",
"name": "oscar",
"lastName": "aguero",
"sex": "1",
"salary": 1500.45,
"profesion_id": 1,
"birthDate": "1985-06-18",
"createdAt": "2021-10-11",
"single": true
}
],
"code": 200,
"message": "success"
}
ENDTEXT
obj = _Screen.Json.Parse(lcStr)
* Make a copy of the internal array from obj.data
Acopy(obj.data, aEmployeeList)
* Now pass the aEmployeeList by reference with '@'
lcJsonArray = _Screen.Json.Encode(@aEmployeeList)
* Convert the JSONArray into VFP CURSOR **(this is cool)**
_Screen.Json.JSONToCursor(lcJsonArray, "qEmployees")
* Modify some fields
Select qEmployees
Replace salary With 5.000 In qEmployees
* Now serialize the cursor to JSON
cJson = _Screen.json.CursorToJson("qEmployees")
?cJson
* Sample 3: Stringify Example
cJson = '{"age":45,"birthdate":"1985-11-15","created":"2020-07-28 09:29:41 PM","fullname":"Jhon Doe","gender":"Male","married":true,"soports":["running","swiming","basket-ball"]}'
?_Screen.Json.Stringify(cJson)
Use Home(2) + "NorthWind\Customers.dbf"
?_Screen.Json.CursorStructure('Customers', Set("Datasession"), .T.)
// Function Signature
// CursorStructure(tcAlias, tnDataSessionID, tlCopyExtended)
// 1. tcAlias is the alias given.
// 2. tnDataSessionID is the current Datasession where Alias() lives (for private data sessions).
// 3. tlCopyExtended if .F. then copy FIELD_NAME, FIELD_TYPE, FIELD_LENGTH and FIELD_DECIMAL_PLACES.
// if is .T. then parse all the alias data structure. Text To lcStr NoShow
{
"array": [
1,
2,
3
],
"boolean": false,
"color": "gold",
"null": null,
"number": 123,
"object": null,
"string": "Hello World"
}
ENDTEXT
_Screen.Json.JSONViewer(lcStr)SIN GARANTÍA
EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A LAS GARANTÍAS DE COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DE DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGÚN RECLAMO, DAÑO U OTRA RESPONSABILIDAD, YA SEA EN UNA ACCIÓN CONTRACTUAL, AGRAVIO O DE OTRA MANERA, QUE SURJA DE, FUERA DE O EN RELACIÓN CON EL SOFTWARE O EL USO U OTRAS NEGOCIOS EN EL SOFTWARE.
JSONFox is released under the MIT License.



