Files
five/docs/json.md
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

9.0 KiB

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: 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.

? 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.

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.

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.

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.

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.

? 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.

? 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.

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.

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.

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.

// 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

// 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

// 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

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

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)

// 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