janssonr

Strict JSON for R, backed by the system Jansson C library. Two functions, zero R package dependencies.

janssonr implements an R-safe profile of RFC 8259: RFC-conformant parsing plus deliberate extra restrictions, so that what parses maps predictably into R (integer literals are exact or refused; fraction and exponent literals convert by correctly rounded double conversion) and everything that encodes is faithfully representable in JSON. Where common JSON packages guess (collapse duplicate keys, truncate at NUL, round big integers, stringify NA), janssonr refuses with a classed condition.

API

from_json('{"a": 1, "b": [true, null]}')
#> $a
#> [1] 1
#> $b
#> $b[[1]]
#> [1] TRUE
#> $b[[2]]
#> NULL

to_json(list(a = 1L, b = list(TRUE, NULL)))
#> [1] "{\"a\":1,\"b\":[true,null]}"

Decoding (from_json)

JSON R
object named list, key order preserved ({} keeps a character(0) names attribute)
array unnamed list, never an atomic vector ([] has NULL names)
string character(1), always UTF-8
integer in int range integer(1) (-2147483648, R’s NA sentinel, becomes double)
other number double(1)
true / false logical(1)
null NULL

Rejected, with a classed error: malformed or truncated input, trailing content, duplicate object keys at any depth (including inside arrays of objects), invalid UTF-8, lone surrogates, NUL escapes, reals overflowing double (1e999), and integer literals whose magnitude exceeds 2^53 (no silent precision loss). The exactness guarantee is for integer literals; number literals with a fraction or exponent (1.5, 9007199254740993.0, 1e-999) use ordinary correctly rounded double conversion, so writing .0 after a big integer opts into rounding. Nesting is capped at 1024 containers by janssonr’s own guard, independent of the Jansson version underneath.

Errors carry line, column, position (byte offset) and a stable code string ("duplicate_key", "invalid_utf8", …). Every integer-form refusal is "integer_precision": detected post-parse (beyond 2^53) it carries an RFC 6901 JSON Pointer in path; detected by the lexer (beyond the parser’s integer range) it carries source coordinates instead. "numeric_overflow" is real-form overflow (1e999).

Encoding (to_json)

R JSON
list with names object, insertion order ("" is a valid key; duplicates refuse)
unnamed list array (a length-1 list stays a 1-element array)
length-1 unnamed atomic bare scalar
other atomic array of scalars
integral double, magnitude <= 2^53 integer spelling (1, not 1.0)
-0 -0.0 (the sign bit survives the round-trip)
other double 17 significant digits: the exact value round-trips
NULL null

Output is compact UTF-8, one line, no trailing newline. Refused: NA of any type, NaN, infinities, named atomic vectors, attributes other than list names, classed objects, duplicate or NA keys, raw, complex, functions, environments.

The double round-trip guarantee is value-level: from_json(to_json(x)) returns bit-identical doubles, but the lexical spelling of non-integral reals may differ across Jansson versions.

Conditions

janssonr_input_error   janssonr_parse_error   janssonr_encode_error
                \             |                   /
                 \____ janssonr_error ___________/

Installation

janssonr needs R >= 4.4. On Unix it links against the system Jansson library when a usable one is found, version 2.11 or newer (Ubuntu 20.04+, Debian bullseye+, RHEL 8+, current Homebrew all qualify):

# Debian/Ubuntu          # Fedora/RHEL              # macOS
apt install libjansson-dev   dnf install jansson-devel   brew install jansson

Without a system jansson the package compiles its bundled Jansson 2.15.1 sources (src/jansson/) instead, so installation works on machines with no jansson at all — CRAN’s macOS builders among them. Install the development package first if you want the system-linked build. On Windows the bundled copy is always used: Rtools carries no jansson.

remotes::install_github("cornball-ai/janssonr")

From the apt repository (Ubuntu, binary)

Prebuilt r-cornball-janssonr packages are published to https://cornball-ai.github.io/janssonr/, built per suite so a client is never offered a build compiled against a different release. The repository is unsigned, so clients opt in with Trusted: yes. Set Suites to your release codename, noble for 24.04:

sudo tee /etc/apt/sources.list.d/janssonr.sources > /dev/null <<'EOF'
Types: deb
URIs: https://cornball-ai.github.io/janssonr
Suites: noble
Components: main
Trusted: yes
Enabled: yes
EOF

sudo apt update
sudo apt install r-cornball-janssonr

The binary needs only the Jansson runtime (libjansson4), never a compiler or libjansson-dev. Debs are built by tools/build-deb.sh and indexed by tools/mkrepo.sh (rapt’s repository pattern).

Design notes