Skip to content

zujson

0.1.0Package record

Lightweight JSON Parsing and Serialization

0packages depend
587downloads / year
30.6%test coverage
13/13flavors OK

Overview

About

Converts between JSON text and ordinary R vectors and lists through a small, predictable set of functions, backed by vendored 'yyjson' https://github.com/ibireme/yyjson and requiring no system JSON library. Parsing accepts character, raw and file input and reports failures through structured conditions; serialization writes UTF-8 bytes suitable for use directly as an HTTP request body. The type mapping is deliberately narrow and fully documented, so what goes in and what comes out are both predictable.

Changes in 0.1.0

First release. Everything below is the work that went into it, newest first.

Bug fixes

  • A data frame's row objects are now charged a nesting level in both directions. Parsing, they were not charged at all, so a document one level too deep to parse as a list parsed as a frame; writing, a list column was charged twice, so a frame whose JSON sat exactly at the 1000-level limit was rejected. zujson_info()$max_depth now means the same thing whichever options are passed.
  • Date and POSIXct outside 0000-01-01 to 9999-12-31 now raise zujson_write_error. Both are doubles in R, so they reach far past what a timestamp can be written as: 1e300 produced "2030437271-06-06T-596523:-14:-8Z", and converting it to int64_t on the way was undefined behaviour rather than merely wrong.
Show all changes in 0.1.0Hide the rest of the changes in 0.1.0
  • A string whose Encoding() is "bytes" now raises zujson_arg_error when parsing and zujson_write_error when writing, instead of the bare simpleError that Rf_translateCharUTF8() raises from inside R. Everything this package can fail with is a zujson_error again. Pass the bytes as a raw vector, which is what the raw path is for.
  • data_frame = TRUE no longer drops a value when one record carries the same key twice. It raises zujson_parse_error: a column has one cell per record, and plain parsing keeps both values, so quietly keeping one made an option about *shape* change *content*.

Data frames

  • Building a frame is now linear in the length of the input rather than quadratic in the number of distinct keys. The union of the keys is collected through a hash index instead of a scan of the keys so far, and cells are filled by one pass over the records instead of a lookup per cell that rescanned the record. A 4000 x 2000 frame went from 69s to 1.6s.
  • The size of a frame is now capped. Because the result is rectangular, its size is set by the union of the keys and not by how much JSON arrived: 5000 records sharing no keys is a 5000 x 5000 frame — 96 MB — from 72 kB of body, which is a denial-of-service path for a package meant to be pointed at untrusted bodies. A limit on the body cannot stand in for one here, because the growth is quadratic in it. More than zujson_info()$max_df_cells cells now raises zujson_limit_error, as the offending column appears rather than after the allocation. The default is 50 million cells, roughly 400 MB of doubles, plus the same order again for the pointer matrix the records are scattered through — 100k rows by 200 columns is 20 million, so a real tabular response has room to spare. options(zujson.max_df_cells = ) changes it for the session, and zujson_info()$max_df_cells reports the limit actually in force. Setting it larger than any frame that could be built switches the check off, at the price that an oversized body then raises R's own allocation error rather than a zujson_limit_error.
  • simplify = "none" takes precedence over data_frame = TRUE, and says so. That mode's promise is that every JSON array arrives as an R list; the two options are not combined.
  • A number too large for any finite double now parses as Inf instead of failing the whole read. RFC 8259 puts no limit on the magnitude of a number, so 1e309 is valid JSON, and rejecting it let one absurd value anywhere in a response body make the entire body unparseable. The value is the one as.numeric() gives the same token, so an integer wider than int64 reads as the nearest double. json_validate("1e309") is TRUE for the same reason. The bare literals Infinity, -Infinity and NaN are not JSON and are still rejected.
  • json_parse_file() on a directory now raises zujson_io_error on every platform. It already did on macOS and Windows, where fopen() refuses a directory; on Linux fopen() succeeds, so yyjson failed later with a memory allocation error and the call raised zujson_parse_error instead. The path is a directory either way, which is an io problem, not a malformed document.

Parsing

  • simplify gains named modes. "preserve" (the default, and what TRUE has always meant) keeps a mixed-kind array as a list; "coerce" promotes it to a character vector following R's own rules, so [1, "a"] becomes c("1", "a") and a JSON null becomes NA rather than the string "NA". "none" is FALSE. TRUE and FALSE keep working and mean exactly what they did, so nothing written against the old API changes meaning.
  • data_frame = TRUE turns a non-empty array whose elements are all objects into a data frame, wherever one appears. Columns are the union of the keys in first-seen order and a missing key reads as NA, so ragged records still give a rectangular result. Columns simplify with the active simplify mode. Off by default: the documented type mapping is unchanged unless you ask.

Infrastructure

  • Differential tests against jsonlite, in tests/testthat/test-interop-jsonlite.R. Two tables over JSON text: where the two packages agree, and a divergence table pinning the places they are meant not to — [] is logical(0), preserve keeps [1, "a"] a list, there is no matrix detection, and data frames are opt-in. The second table is the useful one: it fails if a change quietly makes this package more jsonlite-like than the documented mapping says. jsonlite is a suggested package and the tests skip without it.
  • Conformance against nst/JSONTestSuite, in tools/jsontestsuite.R. Every file is handed to json_validate() as raw bytes — the suite is full of deliberately invalid UTF-8 and embedded NULs, which a text read would mangle. The package accepts all 95 files it must accept and rejects all 188 it must reject; of the 35 implementation-defined files it accepts 12, the rejections being invalid UTF-8 and lone surrogates. The script pins an upstream commit so the result cannot drift under it, and needs the network only once.
  • Sanitizer and randomised-input CI. tools/sanitizer-exercise.R drives the C layer using nothing but base R, with most of its effort on the error paths, where an R error longjmps past the explicit free; hardening.yaml runs it under clang-asan, clang-ubsan and gcc-asan, and fuzzes the parser separately. tests/testthat/test-fuzz.R runs a smaller version of the same idea on every test run.

Initial feature set

The v1 work, previously labelled 0.1.0 — a version number, not a release.

  • json_parse(), json_parse_raw() and json_parse_file() turn JSON into R vectors and lists. Arrays simplify to atomic vectors only when their elements share a type, so a value's type survives the round trip; simplify = FALSE turns simplification off entirely.
  • json_write() and json_write_raw() serialize R as JSON text or as UTF-8 bytes. A fully named vector or list becomes an object, anything else becomes an array, and length-1 atomic vectors unbox unless wrapped in I().
  • json_validate() reports whether input parses, without building a result.
  • json_parse_ndjson(), json_write_ndjson() and json_write_ndjson_raw() handle application/x-ndjson (JSON Lines): one JSON value per line, parsed and written with the same type mappings as single documents. Parsing always returns a list of records and reports the line number on failure; a data frame writes one object per row. There is no pretty option, because indented JSON contains newlines and would break the framing.
  • zujson_info() reports the package version, the vendored yyjson version and the nesting limit.
  • Every failure raises a condition inheriting from zujson_error: zujson_parse_error, zujson_write_error, zujson_unsupported_type, zujson_depth_error, zujson_io_error and zujson_arg_error.
  • A leading UTF-8 byte order mark is ignored rather than rejected, and a POSIXlt is refused rather than serialized as its 11 internal fields.
  • yyjson 0.12.0 is vendored under src/vendor/yyjson; no system library is needed.

From NEWS.md in the release

Install

Health

CRAN checks
13OK
Slowest check: 2.5 min · r-devel-windows-x86_64
Status changes
  • OK13 OKNow
  • OK1 OK2026-10-02
Advisories

none known

Documentation

In release 0.1.0
READMEYes · 287 wordsVignettesNoNEWSYes · 67% structured · mentions 0.1.0Citation fileNoWebsiteYes · pkgdown

The repository also keeps web-only articles.

Help pages
6 (1 internal)includes a package overview page
Examples that run
100%5 run in a plain check
Exported functions with examples
100%
Documented parameters
100%
Return-value docs
100%
Help pages citing references
0%

Downloads

587
CRAN downloads in the past year
Rank #22,284 · ~2/day · ~49/mo
zujson
Daily download trend is not available in this view yet.
58730 days
58790 days
5871 year
Compare downloads with other packages →

Repository

1Stars
0Forks
4Open issues
0Open PRs
1Releases
33Commits
2Commit identities
33 commits · Last activity 2026-09-29
Responsiveness
How these are sampled
PRs merged (all time)
100%
Median days to close, 50 most recently updated closed issues
10
Median days to close, 50 most recently updated closed PRs
0

Releases over time

2026-10-02 · 02026-10-10 · 1
PracticesBeta

8 development-tooling and community-health practices detected across 4 families in the upstream repository

Checks run against github.com/pedrobtz/zujson on 2026-10-11.

Continuous integration (2)
GitHub ActionsR CMD check workflow

The R CMD check workflow runs on Linux

Docs source (3)
pkgdown

GitHub Pages site last deployed 2026-10-10 · The repository has a site deploy workflow

pkgdown site last built 2026-10-10

CRAN release process (2)
cran-comments.mdCRAN-SUBMISSION
Lint, format, editor (1)
RStudio project
How this is detected·Detection ruleset v3 (2026-09-26)
AI coding toolsBeta

This repository shows signs of AI coding tools.

  • Claude Codefirst seen 2026-09-07
    Seen in the repository: CLAUDE.md
    Named in .Rbuildignore: CLAUDE.md
    Pull request it wrote: opened by a project member
    Commits: at least 11 crediting it in the message
Shared conventions any agent may read, so they name no particular tool.
Seen in the repository: .agents
Models named in its commits
  • Claude Opus 59 commits · Sep 2026
  • Claude Opus 5.52 commits · Sep 2026
Counts are commits, not lines of code. Commits by a tool's accounts count once across all of its accounts. Commits crediting it are the largest count among its credit lines, a minimum.How this is detected·Detection ruleset v4 (updated 2026-09-27)

Dependencies

Declared dependencies
5 external dependencies (excludes base and recommended)

Installing zujson adds no packages beyond those that come with R.

Depends (0)
none
Imports (1)
utils
LinkingTo (0)
none
Enhances (0)
none
Reverse dependencies
0direct
0indirect

Nothing depends on this yet.

People & History

People (2)
Author, Maintainer since 0.1.0 (2026), Copyright holder
Contributor, Copyright holder
author of the bundled yyjson library
Releases (1)
  • 0.1.0Latest
    2026-10-02 · current release
  • R
    R 4.6.0 released · 2026-04-24

Package metadata

First published
2026-10-02
Total releases
1 / 1 yrs
License
MIT + file LICENSE OSI
Download size
178 KB

Cite

Cite this package

Run in R for the authors' preferred citation:

citation("zujson")
Baltazar, P., & YaoYuan. (2026). zujson: Lightweight JSON Parsing and Serialization (Version 0.1.0) [Computer software]. https://doi.org/10.32614/CRAN.package.zujson

This package has no citation file, so citation() returns this entry built from DESCRIPTION.

Cite the R Observatory

For a number measured here: a download total, a coverage figure, an archival date.

APA

Balamuta, J. J. (2026). R Observatory: Metrics for zujson version 0.1.0 [Data set]. HJJB, LLC. Data release v2026-10-19. https://doi.org/10.5281/zenodo.21843040

From data release v2026-10-19, which the citation names so these numbers can be found later. More on citing and the projects behind them.

Report a problem with this page →

Privacy choices

These apply to this browser and are stored on this device only. Nothing about your choice is sent to us.

Read the privacy policy