JSON-YY

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

        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
    *   "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;

YY.xs  view on Meta::CPAN

        if (SvIsUV(sv))
            buf_cat_uv(aTHX_ buf, SvUVX(sv));
        else
            buf_cat_iv(aTHX_ buf, SvIVX(sv));
        return;
    }

    if (SvNOK(sv)) {
        NV nv = SvNVX(sv);
        if (Perl_isnan(nv) || Perl_isinf(nv))
            croak("cannot encode NaN or Infinity as JSON");
        buf_cat_nv(aTHX_ buf, nv);
        return;
    }

    if (SvPOK(sv)) {
        STRLEN len;
        const char *str = SvPV(sv, len);
        buf_cat_escaped_str(aTHX_ buf, str, len);
        return;
    }

YY.xs  view on Meta::CPAN

    /* SvIOK first for speed */
    if (SvIOK(sv)) {
        if (SvIsUV(sv))
            return yyjson_mut_uint(doc, (uint64_t)SvUVX(sv));
        return yyjson_mut_sint(doc, (int64_t)SvIVX(sv));
    }

    if (SvNOK(sv)) {
        NV nv = SvNVX(sv);
        if (Perl_isnan(nv) || Perl_isinf(nv))
            croak("cannot encode NaN or Infinity as JSON");
        return yyjson_mut_real(doc, nv);
    }

    if (SvPOK(sv)) {
        STRLEN len;
        const char *str = SvPV(sv, len);
        return yyjson_mut_strncpy(doc, str, len);
    }

    return yyjson_mut_null(doc);

lib/JSON/YY.pm  view on Meta::CPAN

The Doc API avoids full Perl materialization, providing 4-5x speedup
for surgical operations on medium/large documents.

=head1 LIMITATIONS

=over 4

=item * C<canonical> mode is accepted but not yet implemented (yyjson
has no sorted-key writer).

=item * NaN and Infinity values cannot be encoded (croaks).

=back

=head1 COOKBOOK

=head2 Read config, modify, write back

    use JSON::YY ':doc';
    my $config = jread "config.json";
    jset $config, "/database/host", "newhost";

t/07_edge_cases.t  view on Meta::CPAN

    my $t = decode_json_ro('true');
    ok $t, 'decode_json_ro scalar true root';

    my $f = decode_json_ro('false');
    ok !$f, 'decode_json_ro scalar false root';

    my $u = decode_json_ro('null');
    ok !defined $u, 'decode_json_ro scalar null root';
}

# --- NaN/Inf encoding croaks ---
{
    eval { encode_json(9**9**9) };
    like $@, qr/NaN|Inf/i, 'encode Inf croaks';

    eval { encode_json(-9**9**9) };
    like $@, qr/NaN|Inf/i, 'encode -Inf croaks';

    eval { encode_json(9**9**9 - 9**9**9) };
    like $@, qr/NaN|Inf/i, 'encode NaN croaks';
}

# --- error paths ---
{
    eval { decode_json('not json') };
    like $@, qr/decode error/i, 'invalid JSON croaks';

    eval { decode_json('') };
    like $@, qr/decode error/i, 'empty JSON croaks';

t/15_minor_gaps.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use File::Temp qw(tempfile);
use JSON::YY ':doc';
use JSON::YY qw(encode_json);

# NaN/Inf via OO pretty path
{
    my $c = JSON::YY->new(utf8 => 1, pretty => 1);
    eval { $c->encode(9**9**9) };
    like $@, qr/NaN|Inf/i, 'OO pretty encode Inf croaks';
    eval { $c->encode(-9**9**9) };
    like $@, qr/NaN|Inf/i, 'OO pretty encode -Inf croaks';
}

# jwrite on subtree Doc
{
    my ($fh, $tmp) = tempfile(SUFFIX => ".json");
    close $fh;
    my $doc = jdoc '{"a":{"x":1},"b":2}';
    my $sub = jget $doc, "/a";
    jwrite $sub, $tmp;
    my $back = jread $tmp;

yyjson.c  view on Meta::CPAN

#undef  U32_SAFE_DIG
#define U32_SAFE_DIG    9   /* u32 max is 4294967295, 10 digits */
#undef  U64_SAFE_DIG
#define U64_SAFE_DIG    19  /* u64 max is 18446744073709551615, 20 digits */
#undef  USIZE_SAFE_DIG
#define USIZE_SAFE_DIG  (sizeof(usize) == 8 ? U64_SAFE_DIG : U32_SAFE_DIG)

/* Inf bits (positive) */
#define F64_BITS_INF U64(0x7FF00000, 0x00000000)

/* NaN bits (quiet NaN, no payload, no sign) */
#if defined(__hppa__) || (defined(__mips__) && !defined(__mips_nan2008))
#define F64_BITS_NAN U64(0x7FF7FFFF, 0xFFFFFFFF)
#else
#define F64_BITS_NAN U64(0x7FF80000, 0x00000000)
#endif

/* maximum significant digits count in decimal when reading double number */
#define F64_MAX_DEC_DIG 768

/* maximum decimal power of double number (1.7976931348623157e308) */

yyjson.c  view on Meta::CPAN

            val->uni.str = (const char *)hdr;
        } else {
            val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL;
            val->uni.u64 = f64_bits_inf(sign);
        }
        return true;
    }
    return false;
}

/** Read `NaN` literal (ignoring case). */
static_inline bool read_nan(u8 **ptr, u8 **pre,
                            yyjson_read_flag flg, yyjson_val *val) {
    u8 *hdr = *ptr;
    u8 *cur = *ptr;
    u8 **end = ptr;
    bool sign = (*cur == '-');
    if (*cur == '+' && !has_allow(EXT_NUMBER)) return false;
    cur += char_is_sign(*cur);
    if (char_to_lower(cur[0]) == 'n' &&
        char_to_lower(cur[1]) == 'a' &&

yyjson.c  view on Meta::CPAN

            val->uni.str = (const char *)hdr;
        } else {
            val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL;
            val->uni.u64 = f64_bits_nan(sign);
        }
        return true;
    }
    return false;
}

/** Read `Inf`, `Infinity` or `NaN` literal (ignoring case). */
static_inline bool read_inf_or_nan(u8 **ptr, u8 **pre,
                                   yyjson_read_flag flg, yyjson_val *val) {
    if (read_inf(ptr, pre, flg, val)) return true;
    if (read_nan(ptr, pre, flg, val)) return true;
    return false;
}

/** Read a JSON number as raw string. */
static_noinline bool read_num_raw(u8 **ptr, u8 **pre, yyjson_read_flag flg,
                                  yyjson_val *val, const char **msg) {

yyjson.c  view on Meta::CPAN

        byte_copy_4(buf, "null");
        return buf + 4;
    }
    if (has_allow(INF_AND_NAN)) {
        if (sig_raw == 0) {
            buf[0] = '-';
            buf += sign;
            byte_copy_8(buf, "Infinity");
            return buf + 8;
        } else {
            byte_copy_4(buf, "NaN");
            return buf + 3;
        }
    }
    return NULL;
}

/**
 Write a float number (requires 40 bytes buffer).
 We follow the ECMAScript specification for printing floating-point numbers,
 similar to `Number.prototype.toString()`, but with the following changes:

yyjson.c  view on Meta::CPAN

    if (unlikely(!char_is_digit(*cur))) {
        /* nan, inf, or bad output */
        if (has_flg(INF_AND_NAN_AS_NULL)) {
            byte_copy_4(buf, "null");
            return buf + 4;
        } else if (has_allow(INF_AND_NAN)) {
            if (*cur == 'i') {
                byte_copy_8(cur, "Infinity");
                return cur + 8;
            } else if (*cur == 'n') {
                byte_copy_4(buf, "NaN");
                return buf + 3;
            }
        }
        return NULL;
    } else {
        /* finite number */
        u8 *end = buf + len, *dot = NULL, *exp = NULL;

        /*
         The snprintf() function is locale-dependent. For currently known

yyjson.h  view on Meta::CPAN

static const yyjson_read_flag YYJSON_READ_STOP_WHEN_DONE            = 1 << 1;

/** Allow single trailing comma at the end of an object or array,
    such as `[1,2,3,]`, `{"a":1,"b":2,}` (non-standard). */
static const yyjson_read_flag YYJSON_READ_ALLOW_TRAILING_COMMAS     = 1 << 2;

/** Allow C-style single-line and mult-line comments (non-standard). */
static const yyjson_read_flag YYJSON_READ_ALLOW_COMMENTS            = 1 << 3;

/** Allow inf/nan number and literal, case-insensitive,
    such as 1e999, NaN, inf, -Infinity (non-standard). */
static const yyjson_read_flag YYJSON_READ_ALLOW_INF_AND_NAN         = 1 << 4;

/** Read all numbers as raw strings (value with `YYJSON_TYPE_RAW` type),
    inf/nan literal is also read as raw with `ALLOW_INF_AND_NAN` flag. */
static const yyjson_read_flag YYJSON_READ_NUMBER_AS_RAW             = 1 << 5;

/** Allow reading invalid unicode when parsing string values (non-standard).
    Invalid characters will be allowed to appear in the string values, but
    invalid escape sequences will still be reported as errors.
    This flag does not affect the performance of correctly encoded strings.

yyjson.h  view on Meta::CPAN


/** Allow object keys without quotes (non-standard), such as `{a:1,b:2}`.
    This extends the ECMAScript IdentifierName rule by allowing any
    non-whitespace character with code point above `U+007F`. */
static const yyjson_read_flag YYJSON_READ_ALLOW_UNQUOTED_KEY        = 1 << 13;

/** Allow JSON5 format, see: [https://json5.org].
    This flag supports all JSON5 features with some additional extensions:
    - Accepts more escape sequences than JSON5 (e.g. `\a`, `\e`).
    - Unquoted keys are not limited to ECMAScript IdentifierName.
    - Allow case-insensitive `NaN`, `Inf` and `Infinity` literals. */
static const yyjson_read_flag YYJSON_READ_JSON5 =
    (1 << 2)  | /* YYJSON_READ_ALLOW_TRAILING_COMMAS */
    (1 << 3)  | /* YYJSON_READ_ALLOW_COMMENTS */
    (1 << 4)  | /* YYJSON_READ_ALLOW_INF_AND_NAN */
    (1 << 9)  | /* YYJSON_READ_ALLOW_EXT_NUMBER */
    (1 << 10) | /* YYJSON_READ_ALLOW_EXT_ESCAPE */
    (1 << 11) | /* YYJSON_READ_ALLOW_EXT_WHITESPACE */
    (1 << 12) | /* YYJSON_READ_ALLOW_SINGLE_QUOTED_STR */
    (1 << 13);  /* YYJSON_READ_ALLOW_UNQUOTED_KEY */

yyjson.h  view on Meta::CPAN


/** Write JSON pretty with 4 space indent. */
static const yyjson_write_flag YYJSON_WRITE_PRETTY                  = 1 << 0;

/** Escape unicode as `uXXXX`, make the output ASCII only. */
static const yyjson_write_flag YYJSON_WRITE_ESCAPE_UNICODE          = 1 << 1;

/** Escape '/' as '\/'. */
static const yyjson_write_flag YYJSON_WRITE_ESCAPE_SLASHES          = 1 << 2;

/** Write inf and nan number as 'Infinity' and 'NaN' literal (non-standard). */
static const yyjson_write_flag YYJSON_WRITE_ALLOW_INF_AND_NAN       = 1 << 3;

/** Write inf and nan number as null literal.
    This flag will override `YYJSON_WRITE_ALLOW_INF_AND_NAN` flag. */
static const yyjson_write_flag YYJSON_WRITE_INF_AND_NAN_AS_NULL     = 1 << 4;

/** Allow invalid unicode when encoding string values (non-standard).
    Invalid characters in string value will be copied byte by byte.
    If `YYJSON_WRITE_ESCAPE_UNICODE` flag is also set, invalid character will be
    escaped as `U+FFFD` (replacement character).

yyjson.h  view on Meta::CPAN


/** Invalid parameter, such as NULL document. */
static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_PARAMETER     = 1;

/** Memory allocation failure occurs. */
static const yyjson_write_code YYJSON_WRITE_ERROR_MEMORY_ALLOCATION     = 2;

/** Invalid value type in JSON document. */
static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_VALUE_TYPE    = 3;

/** NaN or Infinity number occurs. */
static const yyjson_write_code YYJSON_WRITE_ERROR_NAN_OR_INF            = 4;

/** Failed to open a file. */
static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_OPEN             = 5;

/** Failed to write a file. */
static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_WRITE            = 6;

/** Invalid unicode in string. */
static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_STRING        = 7;



( run in 0.855 second using v1.01-cache-2.11-cpan-39bf76dae61 )