An R-safe profile of RFC 8259 JSON: parsing and generation backed by the 'Jansson' C library, linked as a system library where one is available and compiled from the bundled sources otherwise. The parser rejects, with classed conditions carrying line, column, and byte position: malformed or truncated input, trailing content, duplicate object keys at any depth, invalid UTF-8, escapes encoding a null character, reals overflowing double, and integer literals whose magnitude exceeds 2^53, the range within which a double represents every integer exactly. Number literals with a fraction or exponent convert by ordinary correctly rounded IEEE 754 double conversion. Objects decode to named lists in key order, arrays to unnamed lists, and scalars to length-one vectors. The encoder maps named lists to objects in insertion order, unnamed lists to arrays, guarantees that every finite double, signed zero included, round-trips to the exact same value (whole-number doubles are written as integers), and refuses values with no faithful JSON representation (NA, NaN, infinities, named atomic vectors, classed objects) instead of guessing. No R package dependencies.
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.
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]}"
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).
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.
janssonr_input_error janssonr_parse_error janssonr_encode_error
\ | /
\____ janssonr_error ___________/
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")
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).
src/jr_jansson.h); Unix links the system library when one is
present, the bundled copy compiles in otherwise (and always on
Windows), and the package code cannot tell the difference.
The real-number contract stays value-level partly because the two
linkages may carry different Jansson versions.json_object_seed(), json_set_alloc_funcs()), which is shared with
any other in-process user of libjansson.