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_json compiled as custom Perl ops via the keyword plugin, 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_json for medium/large documents. Modification attempts croak.

When imported, these names are registered as Perl keywords that compile to custom ops, bypassing the normal function dispatch.

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, allow_nonref, allow_unknown, allow_blessed, convert_blessed, max_depth.

encode($perl_value)

Encode to JSON string.

decode($json_string)

Decode from JSON string.

decode_doc($json_string)

Decode to a JSON::YY::Doc handle (mutable document, no Perl materialization). Can then use Doc API keywords on the result.

utf8, pretty, allow_nonref, allow_unknown, allow_blessed, convert_blessed

Boolean setters, return $self for 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.

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).

jgetp $doc, $path

Get value materialized to Perl (string, number, hashref, arrayref, etc.). Alias: jdecode.

jset $doc, $path, $value

Set value at path. $value can 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 undef if path not found.

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.

jfind $doc, $array_path, $key_path, $match_value

Find the first element in an array where the value at $key_path equals $match_value. Returns the matching element as a Doc, or undef if not found.

my $bob = jfind $doc, "/users", "/name", "Bob";

Patching

jpatch $doc, $patch_doc

Apply RFC 6902 JSON Patch. $patch_doc must be a Doc containing a patch array. Modifies $doc in-place.

jmerge $doc, $patch_doc

Apply RFC 7386 JSON Merge Patch. Modifies $doc in-place.

Comparison

jeq $doc_a, $doc_b

Deep equality comparison. Returns boolean.

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.

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 vs JSON::XS (higher is better):

small  (38B):   YY 16-27% faster
medium (11KB):  YY 8-12% faster
large  (806KB): YY 50-170% faster

Decode vs JSON::XS:

small:  YY ~10% slower (SV allocation overhead)
medium: decode_json_ro 25% faster
large:  decode_json_ro ~16% faster

Doc API vs Perl decode-modify-encode:

read single value:  Doc ~equal
modify + serialize: Doc 30% faster (small), 540% faster (medium)
read from large:    Doc 380% faster (no materialization)

LIMITATIONS

  • canonical mode is accepted but not yet implemented (yyjson has no sorted-key writer).

  • NaN and Infinity values cannot be encoded (croaks).

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)
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.