NAME
JSON::YY - Fast JSON encoder/decoder with document manipulation API, backed by yyjson
SYNOPSIS
# functional API (fastest for simple encode/decode)
use JSON::YY qw(encode_json decode_json);
my $json = encode_json { foo => 1, bar => [1, 2, 3] };
my $data = decode_json '{"foo":1}';
# OO API (configurable)
my $coder = JSON::YY->new(utf8 => 1, pretty => 1);
my $json = $coder->encode($data);
my $data = $coder->decode($json);
# zero-copy readonly decode (fastest for read-only access)
use JSON::YY qw(decode_json_ro);
my $data = decode_json_ro $json; # readonly, zero-copy strings
# Doc API (manipulate JSON without full Perl materialization)
use JSON::YY ':doc';
my $doc = jdoc '{"users":[{"name":"Alice","age":30}]}';
jset $doc, "/users/0/age", 31;
my $name = jgetp $doc, "/users/0/name"; # "Alice"
print jencode $doc, ""; # serialize
DESCRIPTION
JSON::YY is a JSON module backed by yyjson 0.12.0, a high-performance JSON library written in ANSI C. It provides three API layers:
- Functional/Keyword API -
encode_json/decode_jsoncompiled as custom Perl ops via XS::Parse::Keyword, eliminating function call overhead. - OO API - JSON::XS-compatible interface with chaining setters.
- Doc API - Operate directly on yyjson's mutable document tree using path-based keywords. Avoids full Perl materialization for surgical JSON edits.
FUNCTIONAL API
use JSON::YY qw(encode_json decode_json decode_json_ro);
- encode_json $perl_value
-
Encode a Perl value to a UTF-8 JSON string. Equivalent to
JSON::YY->new->utf8->encode($value)but faster (no object overhead). - decode_json $json_string
-
Decode a UTF-8 JSON string to a Perl value.
- decode_json_ro $json_string
-
Decode to a deeply readonly structure with zero-copy strings. String SVs point directly into yyjson's parsed buffer. Faster than
decode_jsonfor medium/large documents. Modification attempts croak.
When imported via qw(), these compile to custom ops via XS::Parse::Keyword, bypassing normal function dispatch. Keywords are lexically scoped. The -flag import style installs pre-configured closures instead (not compiled as keywords).
OO API
my $coder = JSON::YY->new(utf8 => 1, pretty => 1);
my $coder = JSON::YY->new->utf8->pretty; # chaining style
- new(%options)
-
Create a new encoder/decoder. Options:
utf8,pretty,canonical,allow_nonref,allow_unknown,allow_blessed,convert_blessed,max_depth. (canonicalis accepted for JSON::XS compatibility but is currently a no-op; see "LIMITATIONS".)By default
allow_nonrefis on and every other flag is off, so a fresh coder produces character-mode output; passutf8 => 1for UTF-8 byte strings (as theencode_jsonfunction always does). - encode($perl_value)
-
Encode to JSON string.
- decode($json_string)
-
Decode from JSON string.
- decode_doc($json_string)
-
Decode to a
JSON::YY::Dochandle (mutable document, no Perl materialization). Can then use Doc API keywords on the result. - utf8, pretty, canonical, allow_nonref, allow_unknown, allow_blessed, convert_blessed
-
Boolean setters, return
$selffor chaining. - max_depth($n)
-
Set maximum nesting depth (default 512).
DOC API
use JSON::YY ':doc';
The Doc API operates on yyjson's internal mutable document tree, using JSON Pointer (RFC 6901) paths for addressing. All keywords compile to custom ops for maximum performance.
Unless documented otherwise, path keywords croak when the path is missing or the value has the wrong type for the operation. The exceptions return a soft value instead: jgetp, jtype, jdel, and jfind return undef; jhas and the jis_* predicates return false.
Document creation
- jdoc $json_string
-
Parse JSON into a mutable document handle (
JSON::YY::Doc). - jfrom $perl_value
-
Create a document from a Perl value (hash, array, scalar).
Value constructors
Create typed JSON values for use with jset:
- jstr $value - JSON string (ensures string type, e.g.
jstr "007") - jnum $value - JSON number
- jbool $value - JSON true/false
- jnull - JSON null
- jarr - empty JSON array
- jobj - empty JSON object
Path operations
All path arguments use JSON Pointer syntax: /key/0/nested. Use "" for root. Use /arr/- to append to an array.
- jget $doc, $path
-
Get a subtree reference (returns a Doc that shares the parent's tree). Croaks if path not found. Use
jhasto check first, orjgetpfor undef-on-missing behavior. - jgetp $doc, $path
-
Get value materialized to Perl (string, number, hashref, arrayref, etc.). Alias:
jdecode. - jset $doc, $path, $value
-
Set value at path.
$valuecan be a scalar (auto-typed), Perl ref (recursively converted), or another Doc (deep-copied). Returns$doc. - jdel $doc, $path
-
Delete value at path. Returns the removed subtree as an independent Doc, or
undefif path not found. Croaks on an empty path (the root cannot be deleted). - jhas $doc, $path
-
Check if path exists. Returns boolean.
- jclone $doc, $path
-
Deep copy subtree into a new independent document.
Serialization
- jencode $doc, $path
-
Serialize document or subtree to compact JSON bytes.
- jpp $doc, $path
-
Serialize to pretty-printed JSON (indented with 4 spaces).
- jraw $doc, $path, $json_fragment
-
Insert a raw JSON string at path without Perl roundtrip. The fragment is parsed by yyjson and inserted directly into the document tree.
Inspection
- jtype $doc, $path
-
Returns type string:
"object","array","string","number","boolean","null". - jlen $doc, $path
-
Array length, object key count, or string byte length.
- jkeys $doc, $path
-
Object keys as a list of strings.
- jvals $doc, $path
-
Object values as a list of Doc handles.
Iteration
Pull-style iterators for arrays and objects:
my $it = jiter $doc, "/users";
while (defined(my $elem = jnext $it)) {
my $name = jgetp $elem, "/name";
my $key = jkey $it; # for objects: current key
}
- jiter $doc, $path - create iterator
- jnext $iter - advance, returns Doc or undef
- jkey $iter - current key (objects only)
File I/O
- jread $filename
-
Read a JSON file and return a Doc handle.
- jwrite $doc, $filename
-
Write a Doc to a file (pretty-printed).
Path enumeration
- jpaths $doc, $path
-
Enumerate all leaf paths under the given path. Returns a list of JSON Pointer strings. Keys containing
~or/are escaped per RFC 6901.
Search
- jfind $doc, $array_path, $key_path, $match_value
-
Find the first element in an array where the value at
$key_pathequals$match_value. Returns the matching element as a Doc, orundefif no element matches (also if$array_pathis missing or does not point to an array).my $bob = jfind $doc, "/users", "/name", "Bob";Integer fields are compared as 64-bit integers and real fields as doubles (so values above 2^53 do not collide). To match a JSON
true,false, ornullfield, pass the corresponding string"true","false", or"null"as$match_value.
Patching
- jpatch $doc, $patch_doc
-
Apply RFC 6902 JSON Patch.
$patch_docmust be a Doc containing a patch array. Modifies$docin-place.$docmust be an owned document; croaks on a borrowed subtree (fromjget) --jcloneit first. - jmerge $doc, $patch_doc
-
Apply RFC 7386 JSON Merge Patch. Modifies
$docin-place. As withjpatch,$docmust be an owned document, not a borrowed subtree.
Comparison
Type predicates
All return boolean. Return false for missing paths.
- jis_obj $doc, $path
- jis_arr $doc, $path
- jis_str $doc, $path
- jis_num $doc, $path
- jis_int $doc, $path
- jis_real $doc, $path
- jis_bool $doc, $path
- jis_null $doc, $path
Overloading
JSON::YY::Doc objects support:
"$doc" # stringify to JSON
if ($doc) # always true
$a eq $b # deep equality
$a ne $b # deep inequality
IMPORT FLAGS
use JSON::YY -utf8, -pretty;
Imports encode_json/decode_json with the specified flags pre-configured. (decode_json_ro is only available via the qw() import, not the flag form.)
JSON POINTER (RFC 6901)
Paths use JSON Pointer syntax:
"" root value
/key object key
/0 array index 0
/a/b/0/c nested path
/arr/- append to array (jset/jraw only)
/k~0ey key containing ~ (escaped as ~0)
/k~1ey key containing / (escaped as ~1)
EXAMPLES
# surgical edit of large document
use JSON::YY ':doc';
my $doc = jdoc $large_json;
jset $doc, "/config/timeout", 30;
my $json = jencode $doc, "";
# extract fields without full decode
my $doc = jdoc $api_response;
my $status = jgetp $doc, "/status";
my $count = jlen $doc, "/data/items";
# type-safe value insertion
jset $doc, "/active", jbool 1; # true, not 1
jset $doc, "/id", jstr "007"; # "007", not 7
# iterate without materializing
my $it = jiter $doc, "/users";
while (defined(my $u = jnext $it)) {
say jgetp $u, "/name" if jis_str $u, "/name";
}
# apply RFC 6902 patch
my $patch = jdoc '[{"op":"replace","path":"/v","value":2}]';
jpatch $doc, $patch;
# apply RFC 7386 merge patch
jmerge $doc, jdoc '{"debug":null,"version":"2.0"}';
# OO decode directly to Doc
my $coder = JSON::YY->new(utf8 => 1);
my $doc = $coder->decode_doc($json);
# insert raw JSON without Perl roundtrip
jraw $doc, "/blob", '[1,2,{"nested":true}]';
# deep compare
say "equal" if jeq $doc_a, $doc_b;
say "equal" if $doc_a eq $doc_b; # overloaded
PERFORMANCE
Encode (ops/sec, higher is better)
JSON::XS JSON::YY delta
small (38B) 6.4M 6.7M +4%
medium (11KB) 26.8K 27.3K +2%
large (806KB) 153 234 +53%
Decode (ops/sec, higher is better)
JSON::XS JSON::YY delta
small (38B) 4.2M 3.5M -17%
medium (11KB) 16.9K 14.1K -16%
large (806KB) 249 267 +8%
Encode is consistently faster, especially on large payloads where yyjson's optimized serializer dominates. Decode is slightly slower on small/medium payloads due to Perl SV allocation overhead.
Doc API vs decode-modify-encode cycle
Perl Doc speedup
read one value 3.0M/s 3.1M/s ~equal
modify + serialize 1.6M/s 2.2M/s +42%
read from large doc 14.6K/s 73.7K/s +405%
modify large + encode 7.4K/s 47.3K/s +536%
clone subtree 15.0K/s 75.2K/s +400%
type/length check 14.4K/s 74.6K/s +418%
The Doc API avoids full Perl materialization, providing 4-5x speedup for surgical operations on medium/large documents.
LIMITATIONS
canonicalmode is accepted but not yet implemented (yyjson has no sorted-key writer).NaN and Infinity values cannot be encoded (croaks).
Decoding or enumerating extremely deeply nested JSON (thousands of levels) recurses on the C stack and can crash on pathological input; bound the size of untrusted JSON before decoding. Encoding is bounded by
max_depth.JSON
true/falsedecode to the Perl scalars1/0(correct in boolean context), not to overloaded boolean objects. To encode a JSON boolean, pass a scalar ref (\1for true,\0for false) or usejboolin the Doc API. Consequentlyencode_json(decode_json('[true,false]'))yields[1,0], not[true,false].
COOKBOOK
Read config, modify, write back
use JSON::YY ':doc';
my $config = jread "config.json";
jset $config, "/database/host", "newhost";
jwrite $config, "config.json";
Extract fields from large API response
my $doc = jdoc $response_body;
my $status = jgetp $doc, "/status";
my $count = jlen $doc, "/data/items";
my $first = jgetp $doc, "/data/items/0/name";
Find user by name in array
my $user = jfind $doc, "/users", "/name", "Alice";
say jgetp $user, "/email" if defined $user;
Build document from scratch
my $doc = jfrom {};
jset $doc, "/name", "My App";
jset $doc, "/version", jnum 1;
jset $doc, "/features", jarr;
jset $doc, "/features/-", "auth";
jset $doc, "/features/-", "logging";
jset $doc, "/debug", jbool 0;
jwrite $doc, "output.json";
Apply incremental updates (merge patch)
my $doc = jread "state.json";
jmerge $doc, jdoc $incoming_patch_json;
jwrite $doc, "state.json";
Debug: show all paths
my @paths = jpaths $doc, "";
say "$_ = ", jencode $doc, $_ for @paths;
Type-safe assertions
die "expected array" unless jis_arr $doc, "/items";
die "expected string" unless jis_str $doc, "/name";
Compare two documents
die "configs differ" if $prod ne $staging; # overloaded
# or explicitly:
die "differ" unless jeq $prod, $staging;
CHEATSHEET
# --- Import ---
use JSON::YY qw(encode_json decode_json); # functional
use JSON::YY ':doc'; # Doc API keywords
# --- Encode/Decode ---
encode_json $data decode_json $json
$coder->encode($data) $coder->decode($json)
decode_json_ro $json # zero-copy readonly
# --- Doc lifecycle ---
jdoc $json # parse JSON string -> Doc
jfrom $perl_data # Perl data -> Doc
jread $file # read JSON file -> Doc
jwrite $doc, $file # Doc -> write JSON file
jencode $doc, $path # Doc -> JSON string
jpp $doc, $path # Doc -> pretty JSON string
jgetp $doc, $path # Doc -> Perl value
$coder->decode_doc($json) # OO: JSON -> Doc
# --- Read ---
jget $doc, $path # -> Doc subtree ref (shared)
jgetp $doc, $path # -> Perl value (materialized)
jdecode $doc, $path # alias for jgetp
jhas $doc, $path # -> bool
jfind $doc, $arr, $k, $v # -> Doc (first match) or undef
# --- Write ---
jset $doc, $path, $val # set (scalar/ref/Doc)
jdel $doc, $path # delete -> Doc (removed)
jraw $doc, $path, $json # insert raw JSON fragment
# --- Copy ---
jclone $doc, $path # deep copy -> independent Doc
# --- Inspect ---
jtype $doc, $path # "object"|"array"|"string"|...
jlen $doc, $path # array/object/string length
jkeys $doc, $path # object keys (list)
jvals $doc, $path # object values (list of Doc)
jpaths $doc, $path # all leaf paths (list)
# --- Type predicates ---
jis_obj jis_arr jis_str jis_num jis_int jis_real jis_bool jis_null
# --- Value constructors ---
jstr $v jnum $v jbool $v jnull jarr jobj
# --- Iterate ---
my $it = jiter $doc, $path;
while (defined(my $v = jnext $it)) { jkey $it; ... }
# --- Patch ---
jpatch $doc, $patch # RFC 6902
jmerge $doc, $patch # RFC 7386
# --- Compare ---
jeq $a, $b # deep equality
$a eq $b # overloaded
"$doc" # overloaded stringify
# --- Path syntax (JSON Pointer RFC 6901) ---
"" root /key object key
/0 array[0] /arr/- append to array
/k~0ey key with ~ /k~1ey key with /
SEE ALSO
JSON::XS, Cpanel::JSON::XS, JSON::PP
yyjson: https://github.com/ibireme/yyjson
AUTHOR
vividsnow
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
yyjson is included under the MIT License.