Thin Nim bindings for yyjson, with a small idiomatic wrapper for fast DOM-style JSON parsing, navigation, mutation, and writing.
This package is not a drop-in replacement for std/json. It is meant for code
that wants yyjson's speed and memory profile without writing C-style Nim FFI at
every call site.
yyjson is usable as a 1.0.x package. The public API focuses on:
- parsing JSON from strings and files
- object and array access
- typed value access for strings, numbers, booleans, nulls, raw numbers
- zero-copy
cstringaccess when you want it - JSON Pointer get/set/add/replace/remove helpers
- JSON Patch and JSON Merge Patch support
- JSON writing to strings and files
- mutable JSON document construction and editing
- ARC/ORC-friendly ownership
The wrapper intentionally does not expose every low-level yyjson C helper as a
public Nim API. The lower-level yyjson/private module exists for the wrapper
and tests, but it is not the package's stable user-facing contract.
After the package is published to Nimble:
nimble install yyjsonFrom a local checkout:
nimble installThen:
import yyjsonNo system yyjson library is required. The package vendors yyjson.c and
yyjson.h and compiles yyjson automatically.
JsonDoc and JsonMutDoc own yyjson documents. They are move-only handles.
Keep them in var variables and call close() exactly once, usually with
defer:
proc loadName() =
var doc = readJson("""{"name":"redis"}""")
defer:
doc.close()
echo doc.root()["name"].str()Values such as JsonVal and JsonMutVal borrow from their owning document.
Do not use them after the document has been closed.
import yyjson
proc main() =
var doc = readJson("""{"name":"redis","tags":["7.4","latest"],"enabled":true}""")
defer:
doc.close()
let root = doc.root()
echo root["name"].str()
echo root["enabled"].bool()
for tag in root["tags"].items:
echo tag.str()
when isMainModule:
main()let root = doc.root()
if root.hasKey("name"):
echo root["name"].str()
if "name" in root:
echo root.getStr("name")
for key, value in root.pairs:
echo key, " = ", value.kind()Missing keys and out-of-range indexes return nil values:
if root["missing"].isNil:
echo "not found"echo root["name"].str()
echo root["enabled"].bool()
echo root["count"].int()
echo root["count"].uint64()
echo root["ratio"].float()String access methods:
cstr()returns a zero-copycstringstr()copies to a NimstringgetCStr(key)returns a zero-copy field valuegetStr(key)copies a field value
Zero-copy pointers are valid only while the owning document is alive.
let value = doc.root().pointer("/metadata/name")
if not value.isNil:
echo value.str()Strict pointer helpers raise JsonPointerError with yyjson's error code and
position:
try:
discard doc.root().pointerStrict("/missing/value")
except JsonPointerError as e:
echo e.code, " at ", e.poslet compact = doc.writeJson()
let pretty = doc.writeJson(YYJSON_WRITE_PRETTY_TWO_SPACES)
writeJsonFile("out.json", doc)
writeJsonFile("tags.json", doc.root()["tags"])Useful write flags include:
YYJSON_WRITE_PRETTYYYJSON_WRITE_PRETTY_TWO_SPACESYYJSON_WRITE_NEWLINE_AT_ENDYYJSON_WRITE_ESCAPE_UNICODEYYJSON_WRITE_ESCAPE_SLASHESYYJSON_WRITE_ALLOW_INF_AND_NANYYJSON_WRITE_INF_AND_NAN_AS_NULL
proc buildJson() =
var mutDoc = newJsonMutDoc()
defer:
mutDoc.close()
let root = mutDoc.newObject()
let tags = mutDoc.newArray()
tags.add(mutDoc.newString("7.4"))
tags.add(mutDoc.newString("latest"))
root.add("name", mutDoc.newString("redis"))
root.add("enabled", mutDoc.newBool(true))
root.add("tags", tags)
mutDoc.setRoot(root)
echo mutDoc.writeJson()Mutable arrays and objects support add, replace, remove, clear, indexing, iteration, and JSON Pointer mutation helpers.
Parsing, writing, and strict pointer operations raise package-specific exceptions:
JsonReadErrorJsonWriteErrorJsonPointerError
These exceptions expose yyjson's error code and a human-readable reason.
try:
discard readJson("[1,]")
except JsonReadError as e:
echo e.code, " at byte ", e.pos, ": ", e.reasonnim c -r -p:src examples/parse.nim
nim c -r -p:src examples/harbor.nim -- vulnerabilities.jsonThe harbor example scans a Harbor vulnerabilities JSON export without
building a std/json.JsonNode tree.
nimble testThe test suite contains focused Nim API tests plus upstream-inspired yyjson
tests and fixtures under tests/upstream and tests/data/yyjson.
The fixture data is copied from yyjson's upstream test/data corpus. The tests
cover behavior that matters to this Nim package: parsing flags, writing flags,
errors, strings, numbers, allocators, JSON Pointer, JSON Patch, JSON Merge
Patch, mutable DOM operations, and selected low-level FFI boundaries.
The suite does not try to port every C-only upstream test. Internal tag/subtype checks, stack-allocated C value tests, and helpers that are not part of the public Nim API are intentionally left outside the 1.0.x scope.
The package vendors:
src/yyjson/vendor/yyjson.c
src/yyjson/vendor/yyjson.h
src/yyjson/private.nim compiles the vendored yyjson.c automatically. If the
vendored yyjson version is updated, run nimble test before publishing a new
package version.
std/json is convenient and universal, but it builds a generic JsonNode tree.
That is not ideal when documents are large and only selected fields are needed.
yyjson is intended for cases like exporters, crawlers, scanners, API
gateways, and CLI tools where:
- JSON payloads are large
- only selected fields are needed
- memory pressure matters
- zero-copy string access is useful
On one real Harbor vulnerabilities JSON file of about 10 MiB, peak RSS was:
| Parser | Peak RSS |
|---|---|
std/json.parseJson |
~82 MiB |
std/parsejson token parser |
~34 MiB |
gemmaJSON |
~38 MiB |
| yyjson C API | ~16 MiB |
These numbers are illustrative, not a benchmark guarantee. They depend on the compiler, allocator, OS, yyjson version, and input shape.
For the initial Nimble release, the package is intentionally conservative:
- public API first
- upstream fixture coverage for behavior that affects Nim users
- private FFI bindings only where needed by the wrapper or tests
- deeper low-level C API parity can be added in later versions
MIT for the Nim wrapper. Vendored yyjson keeps its upstream license.