跳到正文
cccskills
免费在 GitHub 公开

yyjson

High-performance C JSON parsing, creation, and modification with yyjson. Use when working with yyjson documents, string copy-vs-borrow ownership, dangling c_str() bugs, yyjson_alc allocators, or writer output freeing.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md25.8 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

yyjson Usage Guide

Single-header ANSI C (yyjson.h + yyjson.c). Two data models: immutable (yyjson_doc/yyjson_val) and mutable (yyjson_mut_doc/yyjson_mut_val). Free the doc to free all values. Mutable arrays/objects are intrusive circular lists (yyjson_mut_val.next); immutable ones are contiguous 16-byte slots — either way index/key lookup is a linear search, so prefer iterators.

Ownership rule of thumb: the document owns everything it allocated, and borrows every string you hand it unless you use a cpy API. See Memory Management.

Project Notes

  • The bundled copy is in src/ext/yyjson (currently 0.13.0, YYJSON_VERSION_STRING at src/ext/yyjson/src/yyjson.h:590). The xmake option lc_yyjson_use_xrepo (xmake.lua:147, used at src/ext/xmake.lua:36) switches to the xmake-repo package; the CMake counterpart is LUISA_COMPUTE_USE_SYSTEM_YYJSON (CMakeLists.txt:186).
  • The only custom yyjson_alc in-tree is make_allocator() in src/xir/translators/xir2json.cpp:10-17: it forwards to luisa::detail::allocator_allocate/reallocate/deallocate (include/luisa/core/stl/memory.h:68-70 — all three take an explicit alignment) with 16-byte alignment and a NULL ctx.
  • yyjson_write() / yyjson_mut_write() / yyjson_val_write() allocate their output string with the default allocator. Use the _opts variants and pass an allocator if the output must use the same allocator as the document.
  • The bundled target compiles yyjson.c with /utf-8 on MSVC (xmake lc-yyjson, src/ext/xmake.lua:47-49); the CMake luisa-compute-ext-yyjson object target instead defines YYJSON_EXPORTS=1 / YYJSON_IMPORTS=1 (src/ext/CMakeLists.txt:464-470).
  • Where it is used today (the only yyjson call sites outside src/ext/yyjson; good patterns to copy): src/ast/json2ast.cpp:2406-2430 (reader + budget-limited custom yyjson_alc, frees the doc on every exit), src/xir/translators/xir2json.cpp (the only builder: literal keys, _opts writer sharing one 16-byte-aligned yyjson_alc), and the readers src/tests/unit/xir/test_xir_translators.cpp:920-939 and src/tests/unit/xir/test_ast_to_xir.cpp:201-207 (default allocator, yyjson_doc_free on every path).
  • Read Memory Management before adding or freeing anything: most yyjson bugs in this repo are borrowed-string lifetime bugs, not syntax bugs.

Read JSON

yyjson_doc *doc = yyjson_read(json, len, 0);
yyjson_doc *doc = yyjson_read_file("path", 0, NULL, &err);
yyjson_doc *doc = yyjson_read_opts((char*)dat, len, flg, alc, &err);
yyjson_doc *doc = yyjson_read_fp(fp, flg, alc, &err);

// Incremental (standard JSON only: non-standard-feature flags are ignored)
yyjson_incr_state *s = yyjson_incr_new(buf, len, flg, alc);
yyjson_doc *doc = yyjson_incr_read(s, read_len, &err);
yyjson_incr_free(s);

Read flags: YYJSON_READ_NOFLAG (RFC 8259), ALLOW_COMMENTS, ALLOW_TRAILING_COMMAS, ALLOW_INF_AND_NAN, ALLOW_INVALID_UNICODE, NUMBER_AS_RAW, BIGNUM_AS_RAW (overridden by NUMBER_AS_RAW), ALLOW_BOM, ALLOW_EXT_NUMBER, ALLOW_EXT_ESCAPE, ALLOW_EXT_WHITESPACE, ALLOW_SINGLE_QUOTED_STR, ALLOW_UNQUOTED_KEY, INSITU (modifies and aliases the input buffer — it must be padded with YYJSON_PADDING_SIZE zero bytes and outlive the doc; see Reader memory), STOP_WHEN_DONE (NDJSON), JSON5.

Error: yyjson_read_err err; → err.code, err.msg, err.pos. yyjson_locate_pos(dat, len, err.pos, &line, &col, &chr) for line/col.

Access Values

yyjson_val *root = yyjson_doc_get_root(doc);

// Type checks
yyjson_is_null(v)  yyjson_is_bool(v)  yyjson_is_uint(v)  yyjson_is_sint(v)
yyjson_is_int(v)   yyjson_is_real(v)  yyjson_is_num(v)   yyjson_is_str(v)
yyjson_is_arr(v)   yyjson_is_obj(v)   yyjson_is_raw(v)

// Getters
bool b = yyjson_get_bool(v);          uint64_t u = yyjson_get_uint(v);
int64_t s = yyjson_get_sint(v);       int i = yyjson_get_int(v);
double d = yyjson_get_real(v);        double n = yyjson_get_num(v);   // any number type
const char *str = yyjson_get_str(v);  size_t len = yyjson_get_len(v);
const char *raw = yyjson_get_raw(v);  yyjson_type t = yyjson_get_type(v);

// Array (linear by index)
size_t sz = yyjson_arr_size(arr);
yyjson_val *v = yyjson_arr_get(arr, idx);
yyjson_val *v = yyjson_arr_get_first(arr) / yyjson_arr_get_last(arr);

// Object (linear by key)
size_t sz = yyjson_obj_size(obj);
yyjson_val *v = yyjson_obj_get(obj, "key");
yyjson_val *v = yyjson_obj_getn(obj, "key", key_len);

// String compare
yyjson_equals_str(v, "abc");  yyjson_equals_strn(v, "abc", 3);

Iterate

// Array
yyjson_val *v;
yyjson_arr_iter iter = yyjson_arr_iter_with(arr);
while ((v = yyjson_arr_iter_next(&iter))) { }
// Macro:
yyjson_arr_foreach(arr, idx, max, v) { }

// Object
yyjson_val *k, *v;
yyjson_obj_iter iter = yyjson_obj_iter_with(obj);
while ((k = yyjson_obj_iter_next(&iter))) { v = yyjson_obj_iter_get_val(k); }
// Macro:
yyjson_obj_foreach(obj, idx, max, k, v) { }

Mutable: use yyjson_mut_ prefix. Inside mutable iter: yyjson_mut_arr_iter_remove(&iter) / yyjson_mut_obj_iter_remove(&iter).

Create / Modify JSON

yyjson_mut_doc *doc = yyjson_mut_doc_new(alc);
yyjson_mut_val *root = yyjson_mut_obj(doc);  // or yyjson_mut_arr(doc)
yyjson_mut_doc_set_root(doc, root);

// Scalars
yyjson_mut_null(doc)  yyjson_mut_bool(doc, true)
yyjson_mut_uint(doc, 123)  yyjson_mut_sint(doc, -123)  yyjson_mut_int(doc, 123)
yyjson_mut_real(doc, 1.5)  yyjson_mut_str(doc, "hello")       // refs caller's string
yyjson_mut_strcpy(doc, "hello")                                // copies into doc
yyjson_mut_strn(doc, "hello", 5)  yyjson_mut_strncpy(doc, "hello", 5)  // n-variants

// Array
yyjson_mut_arr_append(arr, val)  yyjson_mut_arr_prepend(arr, val)
yyjson_mut_arr_insert(arr, val, idx)  yyjson_mut_arr_replace(arr, idx, val)
yyjson_mut_arr_remove(arr, idx)  yyjson_mut_arr_remove_first/last(arr)  yyjson_mut_arr_clear(arr)
// Convenience: yyjson_mut_arr_add_int(doc, arr, 42) / _add_str / _add_arr / _add_obj
// From C array: yyjson_mut_arr_with_sint32(doc, nums, 3)

// Object
yyjson_mut_obj_add(obj, key, val)      // allows duplicates
yyjson_mut_obj_put(obj, key, val)      // removes existing first (O(n))
yyjson_mut_obj_remove(obj, key)  yyjson_mut_obj_clear(obj)
// Convenience: yyjson_mut_obj_add_str/int/arr/obj(doc, obj, "key", ...)
yyjson_mut_obj_remove_str(obj, "key")  yyjson_mut_obj_rename_key(doc, obj, "old", "new")

Important: A yyjson_mut_val can only be added to one container.

Important: yyjson_mut_str / _strn / _raw / _rawn and every object key are not copied — only the cpy variants (yyjson_mut_strcpy, _strncpy, _rawcpy, _rawncpy, obj_add_strcpy/obj_add_strncpy for the value only) allocate into the document. See Memory Management before adding any dynamic string.

Immutable ↔ Mutable

yyjson_mut_doc *mdoc = yyjson_doc_mut_copy(idoc, alc);        // imut -> mut document
yyjson_mut_val *mval = yyjson_val_mut_copy(mdoc, ival);       // imut -> mut value (into mdoc)
yyjson_mut_doc *mdoc2 = yyjson_mut_doc_mut_copy(mdoc, alc);   // mut -> mut (deep)
yyjson_mut_val *mval2 = yyjson_mut_val_mut_copy(mdoc2, mval); // mut -> mut value (into mdoc2)
yyjson_doc *idoc2 = yyjson_mut_doc_imut_copy(mdoc, alc);      // mut -> imut document
yyjson_doc *idoc3 = yyjson_mut_val_imut_copy(mval, alc);      // mut value -> *new document*

All of these deep-copy, string bytes included (STR/RAW values are re-allocated into the target's str_pool), so the source document may be freed as soon as the copy returns. They are also recursive — deep trees can overflow the stack. Note the *_imut_copy results are independent documents (own pools, free with yyjson_doc_free), not values that belong to another doc — the *_mut_copy results are values owned by the doc passed as the first argument.

Write JSON

char *json = yyjson_write(doc, flg, &len);      free(json);            // default allocator only
char *json = yyjson_mut_write(mdoc, flg, &len); free(json);            // default allocator only
char *json = yyjson_val_write(val, flg, &len);   free(json);           // default allocator only
// _opts variants allocate with the allocator you pass — release with the same one:
char *json = yyjson_mut_write_opts(mdoc, flg, &alc, &len, &err);       // alc.free(alc.ctx, json)
yyjson_write_file("out.json", doc, flg, alc, &err);        // allocates + frees internally
yyjson_mut_write_file("out.json", mdoc, flg, alc, &err);   // allocates + frees internally
yyjson_write_fp(fp, doc, flg, alc, &err);
size_t n = yyjson_write_buf(buf, buf_len, doc, flg, &err); // no allocation at all

See Writer memory — release the string with the same allocator it was allocated with: free() only matches a NULL / libc-backed yyjson_alc, and a custom alc must be released via alc.free(alc.ctx, json).

Write flags: YYJSON_WRITE_NOFLAG (minified), PRETTY (4-space), PRETTY_TWO_SPACES, ESCAPE_UNICODE, ESCAPE_SLASHES, ALLOW_INF_AND_NAN, INF_AND_NAN_AS_NULL, ALLOW_INVALID_UNICODE, LOWERCASE_HEX, NEWLINE_AT_END, FP_TO_FLOAT, FP_TO_FIXED(prec) (1..15).

JSON Pointer / Patch (RFC 6901/6902/7386)

// Query (value-level variants: yyjson_ptr_get(val, ptr) / yyjson_mut_ptr_get(mval, ptr))
yyjson_val *v = yyjson_doc_ptr_get(doc, "/users/0/name");

// Modify (mutable): set/add return bool; remove returns the removed value (or NULL)
yyjson_mut_doc_ptr_set(doc, "/a", yyjson_mut_int(doc, 9));  // replaces if the key exists
yyjson_mut_doc_ptr_add(doc, "/b/-", val);                   // "-" appends to an array
yyjson_mut_doc_ptr_remove(doc, "/b");

// n-variants: (doc, ptr, len). x-variants add (yyjson_ptr_ctx *, yyjson_ptr_err *):
//   yyjson_doc_ptr_getx(doc, ptr, len, &err)
//   yyjson_mut_doc_ptr_getx(doc, ptr, len, &ctx, &err)
//   yyjson_mut_doc_ptr_setx/addx(doc, ptr, len, new_val, bool create_parent, &ctx, &err)
//   yyjson_mut_doc_ptr_removex(doc, ptr, len, &ctx, &err)
// ctx records the parent so yyjson_ptr_ctx_remove/append/replace can undo-edit it.
// There is also a replace family (yyjson_mut_doc_ptr_replace returns the old value).

// Patch (doc is a yyjson_mut_doc *; orig/patch are immutable values;
// all-mutable inputs: yyjson_mut_patch / yyjson_mut_merge_patch)
yyjson_mut_val *out = yyjson_patch(doc, orig, patch, &err);   // RFC 6902, err: yyjson_patch_err
yyjson_mut_val *out = yyjson_merge_patch(doc, orig, patch);   // RFC 7386 (recursive: stack risk)

Memory Management

Everything below was checked against the bundled sources (src/ext/yyjson/src/yyjson.h, yyjson.c, v0.13.0).

Who owns what

  • The document owns everything. yyjson_read*() gives back a yyjson_doc holding all values and strings; yyjson_mut_doc_new() gives back a yyjson_mut_doc holding every value created through it. Values have exactly the lifetime of their document: no per-value free, no refcounting. Release with yyjson_doc_free() / yyjson_mut_doc_free() (both no-ops on NULL) and treat every yyjson_val *, yyjson_mut_val *, and every const char * returned by yyjson_get_str() / yyjson_get_raw() (mutable: yyjson_mut_get_str() / yyjson_mut_get_raw()) as dangling afterwards.
  • Immutable doc layout: one block containing the yyjson_doc header followed by all yyjson_val slots (grown with alc.realloc during the read, so don't cache yyjson_val * across a read call; stable once it returns), plus one block holding the input-text copy (doc->str_pool). yyjson_doc_free() = at most two alc.free calls.
  • Mutable doc layout: two bump arenas — val_pool (24-byte yyjson_mut_val slots: tag, uni, next) and str_pool (bytes for copied strings and raw values). Chunks double in size (start 0x100 B / 16 values, capped at 0x10000000 B / 0x1000000 values) and nothing is returned until yyjson_mut_doc_free(). *_remove, *_iter_remove, *_clear unlink values but reclaim no memory.
  • Containers (array/object) are intrusive circular lists, so a yyjson_mut_val can live in one container only; putting it in two corrupts both. To repeat content, create a fresh value or yyjson_mut_val_mut_copy(doc, val) (recursive — deep trees can blow the stack).

Borrow vs. copy (the #1 yyjson footgun)

Non-cpy creation APIs store the caller's pointer as is: the buffer must stay alive and unmodified for the whole lifetime of the document.

Copies into the docBorrows (no copy)
yyjson_mut_strcpy(doc, s)yyjson_mut_str(doc, s) (needs NUL: calls strlen)
yyjson_mut_strncpy(doc, s, n)yyjson_mut_strn(doc, s, n)
yyjson_mut_rawcpy / yyjson_mut_rawncpyyyjson_mut_raw / yyjson_mut_rawn
yyjson_mut_obj_add_strcpy(doc, o, k, v) — value onlyyyjson_mut_obj_add_str (key and value)
yyjson_mut_obj_add_strncpy(doc, o, k, v, n) — value onlyyyjson_mut_obj_add_strn, plus the key k
yyjson_mut_arr_add_strcpy / _add_strncpyyyjson_mut_arr_add_str / _add_strn
yyjson_mut_arr_with_strcpy / _with_strncpyyyjson_mut_arr_with_str / _with_strn
—all object keys, always: yyjson_mut_obj_add_func does key->uni.str = _key

The one API that does copy a key is yyjson_mut_obj_rename_key / _rename_keyn ("the new_key is copied and held by doc").

Consequences:

  • There is no copy-the-key convenience. For a dynamic key build the pair yourself: yyjson_mut_obj_add(obj, yyjson_mut_strcpy(doc, key.c_str()), value).
  • yyjson_mut_obj_add_strncpy(doc, o, "k", sv.data(), sv.size()) is the way to add a luisa::string_view / std::string that may embed no NUL terminator: length-delimited and copied.
  • Borrowed content is aliased, not snapshotted — mutating the source buffer afterwards rewrites the JSON. Worse, "does this string need escaping?" is decided and cached in the value's tag at creation (YYJSON_SUBTYPE_NOESC, decided by unsafe_yyjson_is_str_noesc; unsafe_yyjson_set_str wraps it for the NUL-terminated cases): yyjson_mut_str, yyjson_mut_strcpy, the key of every yyjson_mut_obj_add_*, and the value of yyjson_mut_obj_add_str — so mutating a borrowed string to later contain ", \ or a control character emits invalid JSON instead of escaping it. The n-family (yyjson_mut_strn, yyjson_mut_strncpy, obj_add_strn, obj_add_strncpy, obj_add_strcpy) store SUBTYPE_NONE and are re-checked at write time. Either way, don't mutate what you added.
  • yyjson_mut_str also requires a valid NUL terminator; str-family on an unterminated buffer is out-of-bounds reading, not just a wrong length.
  • Copied strings live in the document's own str_pool, len + 1 bytes (cpy helpers write the terminator, so yyjson_get_str() works even on strncpy-created values). To fill pool memory in place and skip the double copy, unsafe_yyjson_mut_str_alc(doc, len) returns raw space — but it does not null-terminate for you.

Never hand a borrow API a temporary

No diagnostic, no crash at the call site — the doc keeps a pointer that dies at the end of the full expression, and you see garbage (or a crash at write/doc_free time, when the allocator reuses the block):

// BAD: .string() is a temporary; the pointer is dead on the next line
yyjson_mut_obj_add_str(doc, root, "lightmap", cfg.output_path.filename().string().c_str());
// BAD, same class: luisa::format(...).c_str(), a local char buf[], a loop-scoped std::string
// added to an array that outlives the iteration, a std::string member written later

// GOOD: copy — the document owns the bytes
yyjson_mut_obj_add_val(doc, root, "lightmap",
                       yyjson_mut_strcpy(doc, cfg.output_path.filename().string().c_str()));
// GOOD: extend the source's lifetime past the document
auto name = cfg.output_path.filename().string();   // alive until end of scope
yyjson_mut_obj_add_str(doc, root, "lightmap", name.c_str());
// GOOD: length-delimited copy, no NUL requirement
yyjson_mut_obj_add_strncpy(doc, root, "lightmap", sv.data(), sv.size());

Safe to borrow: string literals and other static storage (keys like "schema", "version"), and buffers of objects known to outlive the document. Default to copying for anything computed — one bump-arena allocation per string is cheaper than the bug. Real example: src/xir/translators/xir2json.cpp — :40-45 borrows string literals only ("schema", "luisa.xir.debug"), :76-78 switches to yyjson_mut_obj_add_strncpy for every computed string (module->name(), the serialized text).

Reader memory

  • Default (no YYJSON_READ_INSITU): the whole input is copied into a doc-owned buffer (len + YYJSON_PADDING_SIZE, padded with 4 zero bytes) and escapes are unescaped in place there. So yyjson_get_str() stays valid until yyjson_doc_free() even if the caller's buffer is gone — this is why const_cast<char *>(sv.data()) in yyjson_read_opts is legal.
  • YYJSON_READ_INSITU: no copy; values point into your buffer, the reader rewrites it, and the buffer must be padded with at least YYJSON_PADDING_SIZE (4) zero bytes and must outlive the doc. Never on const data, string literals, or shared/pooled memory.
  • Don't return string_views into a doc you then free. src/ast/json2ast.cpp is the pattern to follow: it decodes into the AST (copying what must survive) and only then calls yyjson_doc_free(document) on every exit path.
  • Pre-sizing: yyjson_read_max_memory_usage(len, flg) = len * (12 + !INSITU) + 256; combine it with yyjson_alc_pool_init to read with zero malloc calls.

Writer memory

  • yyjson_write_opts(doc, flg, alc, &len, &err) / yyjson_mut_write_opts / yyjson_val_write_opts / yyjson_mut_val_write_opts return a NUL-terminated UTF-8 string allocated with the alc you passed (len excludes the terminator). Free it the matching way: free() for a NULL alc, otherwise alc.free(alc.ctx, json) — see write_document() in src/xir/translators/xir2json.cpp.
  • The non-_opts helpers (yyjson_write, yyjson_mut_write, yyjson_val_write) always use the library's default allocator — libc malloc/free unless YYJSON_CUSTOM_ALC is defined — so free() is the only correct release; with a custom yyjson_alc for the document, output and document then live in two different heaps (that is also where mismatched free bugs come from). Use the _opts variant to keep one allocator.
  • yyjson_write_file / yyjson_mut_write_file / yyjson_write_fp allocate their temp buffer with the passed alc, free it before returning, and report failure via bool + yyjson_write_err (YYJSON_WRITE_ERROR_MEMORY_ALLOCATION vs. I/O codes). Nothing to clean up at the call site.
  • yyjson_write_buf(buf, cap, ...) and yyjson_write_number(val, buf) allocate nothing: write_buf needs slack beyond the final JSON size (temporary space) and returns 0 if the buffer is too small; write_number needs a caller buffer of ≥ 21 bytes for integers and ≥ 40 bytes for reals, and returns NULL (never overflows) on bad input.
  • The writer only reads the document, so borrowed strings must still be alive at write time — writing after the source std::strings died is exactly the dangling case above.

yyjson_alc and the repo's allocators

// Fixed pool on the stack/scratch buffer (>= 8 words). NOT thread-safe, and designed for
// one document at a time: reuse across docs with partial frees fragments it.
char buf[128 * 1024];
yyjson_alc alc; yyjson_alc_pool_init(&alc, buf, sizeof(buf));
yyjson_doc *doc = yyjson_read_opts(dat, len, 0, &alc, NULL);  // NULL + MEMORY_ALLOCATION if full

// Growable pool: chunks from malloc, all released at once. NOT thread-safe.
yyjson_alc *alc = yyjson_alc_dyn_new();   // free with yyjson_alc_dyn_free() AFTER doc_free()

// Fully custom: signatures are (ctx,size), (ctx,ptr,old_size,size), (ctx,ptr)
yyjson_alc MY_ALC = { my_malloc, my_realloc, my_free, my_ctx };
  • Both documents and readers copy the yyjson_alc struct (doc->alc = *alc), so the struct itself may be a stack temporary, but everything it references — a pool's buffer, the ctx object — must outlive the document (and, for writers, the output string).
  • NULL alc ⇒ yyjson's internal default allocator — libc malloc/realloc/free (YYJSON_DEFAULT_ALC, a private static in src/ext/yyjson/src/yyjson.c:2242; it becomes a null allocator under YYJSON_FREESTANDING and can be replaced by defining YYJSON_CUSTOM_ALC).
  • In this repo: xir2json.cpp forwards to luisa::detail::allocator_allocate/reallocate/deallocate with 16-byte alignment; json2ast.cpp wraps libc with a YYJsonBudget ctx that returns NULL past max_parse_memory_bytes, turning a hostile/large document into a clean YYJSON_READ_ERROR_MEMORY_ALLOCATION instead of unbounded memory growth — pair it with max_document_bytes / max_string_bytes checks on the input.
  • Allocation failures are recoverable, not fatal: every yyjson_mut_* creator returns NULL and every yyjson_mut_obj_add_* / arr_add_* returns false. If unchecked, you silently lose a field rather than crash — check them when the JSON is load-bearing.

Thread safety

  • Building a yyjson_mut_doc is single-threaded: arena cursors, no locks. One document per thread.
  • yyjson_read_opts / yyjson_write_opts are thread-safe when alc is thread-safe (or NULL) and the input data isn't modified concurrently (guaranteed unless INSITU).
  • A finished yyjson_doc can be read concurrently (yyjson_get_*, iterators are stack-local state), but never mutate values from two threads (the in-place setters yyjson_set_* / yyjson_mut_set_*, e.g. yyjson_set_str_noesc, change tags) and never read while another thread frees it.
  • yyjson_alc_pool_init and yyjson_alc_dyn_new allocators are explicitly documented as not thread-safe.

C++ lifetime hygiene (Luisa side)

  • Wrap the document: std::unique_ptr<yyjson_mut_doc, void (*)(yyjson_mut_doc*)> (or a scope guard) instead of hand-writing yyjson_mut_doc_free() before each return. Manual frees are easy to miss on early returns — src/tests/unit/xir/test_xir_translators.cpp:920-939 frees on both paths, which is the fragile version.
  • LUISA_ERROR / luisa::log_error is [[noreturn]] noexcept and calls std::abort() (include/luisa/core/logging.h:110,133), so it does not unwind. Nothing else throws in the yyjson users either: xmake compiles them with exceptions="no-cxx" + _HAS_EXCEPTIONS=0 (scripts/xmake_func.lua:346-353 — only src/py opts back in, src/py/xmake.lua:23), so a failing std::string / luisa::format / vector growth aborts instead of unwinding. The real early exits are plain returns — keep the loop body between doc_new and doc_free allocation-light anyway (CMake/clang-cl does add /EHsc, src/CMakeLists.txt:34), or hold the doc in a guard.
  • Nothing in the doc needs free()-ing individually: not values, not copied strings, not keys. Mixing free() / luisa::detail::allocator_deallocate with yyjson-owned memory is a bug; only the writer's returned string (and yyjson_alc_dyn_new's allocator) is ever freed by hand.
  • yyjson_mut_doc_set_str_pool_size(doc, total_string_bytes) and yyjson_mut_doc_set_val_pool_size(doc, value_count) set the next chunk size (no immediate allocation) — worth it for large, predictable dumps (many strcpyed strings or many values) to avoid the doubling-chunk malloc churn (the pools grow by linking new chunks, never by realloc-ing the old one). Note the string pool is only used by copied strings/raw values.

Number & Compile-time

// Per-value output format (mutable)
yyjson_mut_set_fp_to_float(val, true);
yyjson_mut_set_fp_to_fixed(val, 6);

Compile-time defines (src/ext/yyjson/src/yyjson.h:35-133): YYJSON_DISABLE_READER, YYJSON_DISABLE_WRITER, YYJSON_DISABLE_INCR_READER, YYJSON_DISABLE_FILE (file/fp read+write APIs), YYJSON_DISABLE_UTILS (Pointer/Patch), YYJSON_DISABLE_FAST_FP_CONV, YYJSON_DISABLE_NON_STANDARD, YYJSON_DISABLE_UTF8_VALIDATION, YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS (auto-detected when left undefined), YYJSON_READER_DEPTH_LIMIT / YYJSON_WRITER_DEPTH_LIMIT (0 = no limit), and YYJSON_FREESTANDING (build without libc; then NULL is no longer a usable alc — pass one per call or define YYJSON_CUSTOM_ALC). None of them is set by this repo's build files.

Null Safety

All APIs null-check inputs — safe to chain: yyjson_get_str(yyjson_obj_get(root, "key")) returns NULL if missing. For hot loops, unsafe_ prefix skips checks: unsafe_yyjson_is_str(k), unsafe_yyjson_get_uint(v).

Common Pattern: Read → Modify → Write

yyjson_doc *idoc = yyjson_read_file("in.json", 0, NULL, NULL);
yyjson_mut_doc *doc = yyjson_doc_mut_copy(idoc, NULL);   // deep copy: strings are copied here
yyjson_mut_val *root = yyjson_mut_doc_get_root(doc);

yyjson_mut_val *k, *v;
yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(root);
while ((k = yyjson_mut_obj_iter_next(&iter))) {
    v = yyjson_mut_obj_iter_get_val(k);
    if (yyjson_mut_is_null(v)) yyjson_mut_obj_iter_remove(&iter);  // unlinks, frees nothing
}

yyjson_mut_write_file("out.json", doc, YYJSON_WRITE_PRETTY, NULL, NULL);
yyjson_doc_free(idoc);
yyjson_mut_doc_free(doc);

yyjson_doc_mut_copy deep-copies, so the mutable doc is independent of idoc's memory (you may free idoc immediately). The plain *_write calls above use the libc allocator → free(); if the mutable doc was built with a custom yyjson_alc, use yyjson_mut_write_opts(doc, flg, &alc, …) and alc.free(alc.ctx, json) instead.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

ast

免费

Manual AST construction with FunctionBuilder for kernels and callables without DSL sugar.

中文简介正在准备中。当前显示原文说明。

LuisaGroup/LuisaCompute10552026年10月18日 更新

Backend plugin architecture, DeviceInterface, dynamic loading, and command encoding.

中文简介正在准备中。当前显示原文说明。

LuisaGroup/LuisaCompute10552026年10月18日 更新

cmake

免费

CMake build options, custom functions, and backend patterns for LuisaCompute.

中文简介正在准备中。当前显示原文说明。

LuisaGroup/LuisaCompute10552026年10月18日 更新

cpp-style

免费

C++ naming, formatting, static analysis, and RTTI rules for LuisaCompute.

中文简介正在准备中。当前显示原文说明。

LuisaGroup/LuisaCompute10552026年10月18日 更新

debug

免费

Debug crashes and test failures via stack-traces, host/device logging, DSL buffer inspection, and add_debug_checks kernel restrict-mode guards.

中文简介正在准备中。当前显示原文说明。

LuisaGroup/LuisaCompute10552026年10月18日 更新

git

免费

Show uncommitted changes and commit history via git. Use when: (1) inspecting what changed in a file before committing, (2) checking diff of tracked/new/deleted files, (3) reviewing a specific commit's changes, (4) listing files changed in a commit.

中文简介正在准备中。当前显示原文说明。

LuisaGroup/LuisaCompute10552026年10月18日 更新

查看 LuisaGroup 的全部技能

报告此技能的问题