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

359 lines
9.0 KiB
Markdown

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