# Five JSON — Harbour Compatible + Go-Native Extensions > Go's `encoding/json` + `net/http` power in Harbour syntax Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com). All rights reserved. ## Overview Five provides full Harbour JSON compatibility (`hb_jsonEncode`/`hb_jsonDecode`) plus nine Go-native extension functions that go far beyond what Harbour can do. These extensions leverage Go's standard library for JSONPath queries, HTTP integration, file I/O, validation, and deep merge — all impossible in stock Harbour without external C libraries. ## Why Five's JSON Is Better | Capability | Harbour | Five | |-----------|---------|------| | Basic encode/decode | `hb_jsonEncode()` / `hb_jsonDecode()` | Same API, compatible | | Pretty print | Not available | `JsonPretty(xValue)` | | JSONPath query | Not available | `JsonPath(xVal, "$.user.name")` | | Deep merge | Not available | `JsonMerge(hDest, hSrc)` | | Validate syntax | Not available | `JsonValid(cJSON)` | | Detect type | Not available | `JsonType(cJSON)` | | File read/write | Manual `MemoRead` + encode/decode | `JsonTo()` / `JsonFrom()` | | HTTP GET + JSON | External library required | `JsonHttpGet(cURL)` | | HTTP POST + JSON | External library required | `JsonHttpPost(cURL, xBody)` | | Unicode support | Limited by codepage | Full UTF-8 via Go | | Large file streaming | Memory-limited | Go's streaming decoder | | Concurrent encoding | Thread-unsafe | Goroutine-safe | ### What Harbour Cannot Do ```harbour // Harbour: requires hbcurl + manual JSON parsing + error handling // Approximately 30+ lines of code with external dependencies // Five: one line, zero dependencies result := JsonHttpGet("https://api.github.com/repos/user/repo") ? JsonPath(result, "$.body") ``` ## Function Reference ### Harbour Compatible #### hb_jsonEncode(xValue [, lHumanReadable]) → cJSON Converts any Five value to a JSON string. ```harbour ? hb_jsonEncode({"name" => "Five", "version" => 1}) // → {"name":"Five","version":1} ? hb_jsonEncode({"a" => {1,2,3}}, .T.) // pretty // → { // "a": [1, 2, 3] // } ``` **Supported types:** - String → `"string"` - Numeric (int) → `123` - Numeric (float) → `3.14` - Logical → `true` / `false` - NIL → `null` - Array → `[1, 2, 3]` - Hash → `{"key": "value"}` - Nested structures → fully recursive #### hb_jsonDecode(cJSON) → xValue Parses a JSON string into Five values. ```harbour result := hb_jsonDecode('{"users":[{"name":"Kim"},{"name":"Lee"}]}') ? result["users"][1]["name"] // → "Kim" ``` **Type mapping:** - `"string"` → Five String - `123` → Five Int - `3.14` → Five Double - `true`/`false` → Five Logical - `null` → Five NIL - `[...]` → Five Array - `{...}` → Five Hash ### Five Extensions (Go-Native) #### JsonPretty(xValue [, cIndent]) → cJSON Formats JSON with indentation for human readability. ```harbour h := {"name" => "Five", "features" => {"goroutine", "FRB", "Rushmore"}} ? JsonPretty(h) // { // "name": "Five", // "features": [ // "goroutine", // "FRB", // "Rushmore" // ] // } ? JsonPretty(h, "\t") // tab-indented ``` #### JsonPath(xValue, cPath) → xResult Queries nested JSON structures using dot-notation path syntax. ```harbour data := hb_jsonDecode('{"user":{"name":"Charles","scores":[100,95,88]}}') ? JsonPath(data, "$.user.name") // → "Charles" ? JsonPath(data, "$.user.scores[0]") // → 100 ? JsonPath(data, "$.user.scores[2]") // → 88 ? JsonPath(data, "$.missing.key") // → NIL ``` **Path syntax:** - `$.key` — root-level key - `$.key.subkey` — nested key - `$.array[0]` — array index (0-based) - `$.key.array[1].name` — mixed nesting #### JsonMerge(hDest, hSrc) → hMerged Deep merges two hashes. Source keys overwrite destination keys. ```harbour defaults := {"host" => "localhost", "port" => 5432, "ssl" => .F.} override := {"port" => 3306, "ssl" => .T., "db" => "myapp"} config := JsonMerge(defaults, override) ? hb_jsonEncode(config) // → {"host":"localhost","port":3306,"ssl":true,"db":"myapp"} ``` #### JsonValid(cJSON) → lValid Validates JSON syntax without decoding. ```harbour ? JsonValid('{"name":"Five"}') // → .T. ? JsonValid('{broken json') // → .F. ? JsonValid('') // → .F. ``` Uses Go's `json.Valid()` — faster than full decode for validation-only checks. #### JsonType(cJSON) → cType Detects the top-level JSON type without decoding. ```harbour ? JsonType('{"a":1}') // → "object" ? JsonType('[1,2,3]') // → "array" ? JsonType('"hello"') // → "string" ? JsonType('42') // → "number" ? JsonType('true') // → "boolean" ? JsonType('null') // → "null" ? JsonType('{bad') // → "invalid" ``` #### JsonTo(xValue, cFile) → lSuccess Writes a value as formatted JSON to a file. ```harbour config := {"host" => "db.example.com", "port" => 5432} JsonTo(config, "config.json") // File contents: // { // "host": "db.example.com", // "port": 5432 // } ``` #### JsonFrom(cFile) → xValue Reads and parses a JSON file. ```harbour config := JsonFrom("config.json") ? config["host"] // → "db.example.com" ? config["port"] // → 5432 ``` #### JsonHttpGet(cURL [, nTimeout]) → hResult Performs an HTTP GET request and returns the result as a hash. ```harbour result := JsonHttpGet("https://api.github.com/repos/user/repo") ? result["status"] // → 200 ? result["error"] // → "" (empty if no error) // Parse JSON body data := hb_jsonDecode(result["body"]) ? JsonPath(data, "$.full_name") // → "user/repo" ``` **Result hash:** - `status` — HTTP status code (200, 404, etc.) - `body` — response body as string - `error` — error message (empty if success) **Timeout:** Default 30 seconds. Override with second parameter. #### JsonHttpPost(cURL, xBody [, nTimeout]) → hResult Performs an HTTP POST with JSON body. ```harbour // Post a hash — automatically serialized to JSON result := JsonHttpPost("https://api.example.com/users", ; {"name" => "Charles", "email" => "charles@example.com"}) ? result["status"] // → 201 // Post raw JSON string result := JsonHttpPost("https://api.example.com/data", ; '{"raw":"json string"}') ``` **Content-Type:** Automatically set to `application/json`. ## Use Cases ### REST API Client ```harbour // Complete REST API client in Five — impossible in stock Harbour // GET users := hb_jsonDecode(JsonHttpGet("https://api.example.com/users")["body"]) FOR EACH user IN users ? JsonPath(user, "$.name"), JsonPath(user, "$.email") NEXT // POST result := JsonHttpPost("https://api.example.com/users", ; {"name" => "New User", "role" => "admin"}) IF result["status"] = 201 ? "User created!" ENDIF ``` ### Configuration File ```harbour // Load config with defaults + override defaults := JsonFrom("defaults.json") local_config := JsonFrom("local.json") config := JsonMerge(defaults, local_config) ? "Database:", JsonPath(config, "$.database.host") ``` ### Data Validation ```harbour cInput := GetUserInput() IF !JsonValid(cInput) ? "Invalid JSON!" RETURN ENDIF IF JsonType(cInput) != "object" ? "Expected JSON object!" RETURN ENDIF data := hb_jsonDecode(cInput) ``` ### Database Export to JSON ```harbour USE "customers" LOCAL aRecords := {} GO TOP DO WHILE !Eof() AAdd(aRecords, {"id" => FieldGet(1), "name" => AllTrim(FieldGet(2))}) SKIP ENDDO JsonTo(aRecords, "customers.json") ? "Exported", Len(aRecords), "records" ``` ### Goroutine + JSON API (Five exclusive) ```harbour // Parallel API calls — impossible in Harbour ch := Channel(3) Go({|c| ChSend(c, JsonHttpGet("https://api1.example.com/data"))}, ch) Go({|c| ChSend(c, JsonHttpGet("https://api2.example.com/data"))}, ch) Go({|c| ChSend(c, JsonHttpGet("https://api3.example.com/data"))}, ch) // Collect results FOR i := 1 TO 3 result := ChReceive(ch) ? "API", i, "status:", result["status"] NEXT ``` ## Verified Test Results ``` === Five JSON (Go-native extensions) === 1. hb_jsonEncode: {"features":["goroutine","FRB","Rushmore"],"name":"Five","version":1} 2. JsonPretty: { "features": ["goroutine","FRB","Rushmore"], "name": "Five", "version": 1 } 3. JsonPath: $.user.name: Charles $.user.scores[1]: 95 4. JsonMerge: {"x":1,"y":99,"z":3} 5. JsonValid: {"ok":true} → .T. {broken → .F. 6. JsonType: {"a":1} → object [1,2,3] → array "hello" → string 42 → number 7. JsonTo/JsonFrom: Loaded name: Five ``` ## Migration from Harbour | Harbour | Five | Notes | |---------|------|-------| | `hb_jsonEncode(x)` | `hb_jsonEncode(x)` | 100% compatible | | `hb_jsonDecode(s)` | `hb_jsonDecode(s)` | 100% compatible | | `hb_jsonEncode(x)` + manual indent | `JsonPretty(x)` | One function | | `MemoWrit(f, hb_jsonEncode(x))` | `JsonTo(x, f)` | One function | | `hb_jsonDecode(MemoRead(f))` | `JsonFrom(f)` | One function | | Not possible | `JsonPath(x, "$.a.b[0]")` | Five exclusive | | Not possible | `JsonMerge(h1, h2)` | Five exclusive | | Not possible | `JsonHttpGet(url)` | Five exclusive | | Not possible | `JsonHttpPost(url, body)` | Five exclusive | | hbcurl + manual parsing | `JsonHttpGet()` | Zero dependencies |