NAME
mb::JSON - JSON encode/decode for multibyte (UTF-8) strings
VERSION
Version 0.07
SYNOPSIS
use mb::JSON;
# decode: JSON text -> Perl data
my $decoded = mb::JSON::decode("{\"name\":\"\\u7530\\u4e2d\",\"age\":30}");
# parse: alias for decode()
my $parsed = mb::JSON::parse("{\"key\":\"value\"}");
# encode: Perl data -> JSON text
my $json = mb::JSON::encode({ name => 'Tanaka', age => 30 });
# -> '{"age":30,"name":"Tanaka"}'
# stringify: alias for encode()
my $text = mb::JSON::stringify({ name => 'Tanaka', age => 30 });
# -> '{"age":30,"name":"Tanaka"}'
# Boolean values
my $flags = mb::JSON::encode({
active => mb::JSON::true,
locked => mb::JSON::false,
});
# -> '{"active":true,"locked":false}'
# null
my $empty = mb::JSON::encode({ value => undef });
# -> '{"value":null}'
TABLE OF CONTENTS
DESCRIPTION
mb::JSON is a simple, dependency-free JSON encoder and decoder designed for Perl 5.005_03 and later. It handles UTF-8 multibyte strings correctly, making it suitable for environments where standard JSON modules requiring Perl 5.8+ are unavailable.
mb::JSON provides two pairs of symmetric functions: decode()/parse() convert JSON text to Perl data, and encode()/stringify() convert Perl data to JSON text. Within each pair, both names are aliases and produce identical output.
The decoder scans its input with pos() and the \G anchor and never modifies the buffer, so decoding time is linear in the length of the input. See "PERFORMANCE".
FUNCTIONS
decode( $json_text )
Converts a JSON text string to a Perl data structure. If the argument is omitted or undefined, $_ is used. A leading UTF-8 byte order mark is ignored.
my $data = mb::JSON::decode($json_text);
parse( $json_text )
Alias for decode(). Both names are interchangeable.
my $data = mb::JSON::parse($json_text);
encode( $data )
Converts a Perl data structure to a JSON text string. Returns a byte string encoded in UTF-8. If called with no argument at all, $_ is used; note that encode(undef) still returns null.
my $json = mb::JSON::encode($data);
stringify( $data )
Alias for encode(). Both names are interchangeable. Mirrors the JSON.stringify() function in JavaScript.
my $json = mb::JSON::stringify($data);
true
Returns the singleton mb::JSON::Boolean object representing JSON true. Numifies to 1, stringifies to "true".
false
Returns the singleton mb::JSON::Boolean object representing JSON false. Numifies to 0, stringifies to "false".
CONFIGURATION
Two package variables tune the behaviour of the module. Both are ordinary globals, so local works as expected.
$mb::JSON::MAX_DEPTH
Maximum nesting depth accepted by decode() and produced by encode(). The default is 512. Deeply nested input would otherwise exhaust the Perl stack, so the limit matters when decoding untrusted data. Setting the variable to a false value disables the check entirely.
The limit counts objects and arrays, not values, and both directions stop at the same number of them, so any document decode() accepts can be handed straight back to encode().
local $mb::JSON::MAX_DEPTH = 64;
my $data = mb::JSON::decode($untrusted);
$mb::JSON::STRICT
When false (the default), decode() copies string bytes through unchanged: raw control characters are accepted and UTF-8 is not validated. This keeps the module usable as a byte-transparent filter.
When true, decode() rejects a raw control character U+0000-U+001F inside a string, and rejects a string whose bytes are not well-formed UTF-8.
local $mb::JSON::STRICT = 1;
my $data = mb::JSON::decode($untrusted);
BOOLEAN VALUES
Perl has no native boolean type. To represent JSON true and false unambiguously, mb::JSON provides two singleton objects:
mb::JSON::true -- stringifies as "true", numifies as 1
mb::JSON::false -- stringifies as "false", numifies as 0
Use these when encoding a boolean value:
my $json = mb::JSON::encode({ flag => mb::JSON::true });
# -> '{"flag":true}'
A plain 1 or 0 encodes as a JSON number, not a boolean:
my $json = mb::JSON::encode({ count => 1 });
# -> '{"count":1}'
When decoding, JSON true and false are returned as mb::JSON::Boolean objects, which behave as 1 and 0 in numeric and boolean context.
stringify() behaves identically to encode() for boolean values.
ENCODING RULES
Applies to both encode() and stringify().
- undef -> null
- mb::JSON::true -> true, mb::JSON::false -> false
- Number
-
A scalar matching the JSON number pattern is encoded as a bare number.
InfandNaNhave no JSON representation and are rejected with an error rather than silently emitted as the strings"Inf"and"NaN". - String
-
Encoded as a double-quoted JSON string. UTF-8 multibyte bytes are output as-is (not
\uXXXX-escaped). Control characters U+0000-U+001F are escaped. - ARRAY reference -> JSON array
[...] - HASH reference -> JSON object
{...} -
Hash keys are sorted alphabetically for deterministic output.
- Nesting
-
A structure nested deeper than
$mb::JSON::MAX_DEPTHis rejected, and a reference that contains itself is reported as a circular reference instead of recursing forever.
DECODING RULES
Applies to both decode() and parse().
- null -> undef
- true -> mb::JSON::Boolean (numifies to 1)
- false -> mb::JSON::Boolean (numifies to 0)
- Number -> Perl number
- String -> Perl string (\uXXXX converted to UTF-8)
-
A
\uXXXXescape in the Basic Multilingual Plane is converted to its UTF-8 byte sequence. A UTF-16 surrogate pair (\uD800-\uDBFFfollowed by\uDC00-\uDFFF) is combined into the single code point it represents and emitted as 4-byte UTF-8, so characters above U+FFFF (e.g. emoji) decode correctly. - Object -> hash reference
-
When the same key appears more than once, the last occurrence wins.
- Array -> array reference
- Byte order mark
-
A leading UTF-8 byte order mark (
EF BB BF) is skipped before parsing. - Whitespace
-
Space, tab, line feed and carriage return are allowed between tokens, which is exactly the set RFC 8259 defines. Form feed and vertical tab are not whitespace and are reported as an unexpected token. The set is spelled out rather than written as perl's
\s, so it is the same on every supported perl;\smatches form feed everywhere and, from perl 5.18, vertical tab as well. - Nesting
-
Input nested deeper than
$mb::JSON::MAX_DEPTHis rejected. The limit counts containers, so a scalar inside the deepest allowed object or array is not a further level.encode()applies the same rule, and therefore can always write back out whateverdecode()accepted.
LIMITATIONS
References other than ARRAY and HASH (e.g. code references, blessed objects other than
mb::JSON::Boolean) are stringified rather than raising an error. A blessed hash or array reference is stringified too, becauseref()returns the class name rather thanHASHorARRAY.A scalar that matches the JSON number pattern is encoded as a number even when it was intended as a string, so
"30"becomes30. Leading-zero strings such as"007"are preserved as strings because they do not match the JSON number pattern.Numbers are decoded through Perl's own numeric conversion, so an integer wider than the platform's floating point mantissa loses precision, and
1.0or1e2is re-encoded as1or100.By default the decoder does not validate UTF-8 and accepts raw control characters inside strings. Set
$mb::JSON::STRICTto enable those checks.InfandNaNare recognised by how they print, so a string that spells exactly"Inf","-Inf","Infinity","NaN"or one of the"1.#INF"forms is rejected as well, even when it was meant as text. Longer words are unaffected:"Info"and"nano"encode normally. Perl offers no portable way to tell a numericInffrom a string spelling it -- from perl 5.22 even"Info"numifies toInf-- so the module prefers a rule that behaves identically on every supported perl.
DIAGNOSTICS
mb::JSON::decode: unexpected end of input-
The JSON text ended before a complete value was parsed.
mb::JSON::decode: unexpected token: <text>-
An unrecognised token was encountered while parsing.
mb::JSON::decode: expected string key in object-
An object key was not a quoted string.
mb::JSON::decode: expected ':' after key '<key>'-
The colon separator was missing after an object key.
mb::JSON::decode: expected ',' or '}' in object-
A JSON object was not properly terminated or separated.
mb::JSON::decode: expected ',' or ']' in array-
A JSON array was not properly terminated or separated.
mb::JSON::decode: unterminated string-
A JSON string was not closed with a double-quote.
mb::JSON::decode: invalid escape sequence: <text>-
A backslash inside a string was followed by a character that is not a recognized escape (
\",\\,/,\b,\f,\n,\r,\t) or a\uXXXXsequence. mb::JSON::decode: trailing garbage: <text>-
Extra text was found after a successfully parsed top-level value.
mb::JSON::decode: lone high surrogate in Unicode escape sequence-
A high surrogate
\uD800-\uDBFFwas not followed by a\uXXXXescape. mb::JSON::decode: invalid low surrogate in Unicode escape sequence-
A high surrogate was followed by a
\uXXXXescape that was not a low surrogate\uDC00-\uDFFF. mb::JSON::decode: lone low surrogate in Unicode escape sequence-
A low surrogate
\uDC00-\uDFFFappeared without a preceding high surrogate. mb::JSON::decode: raw control character in string-
$mb::JSON::STRICTis set and an unescaped character in the range U+0000-U+001F appeared inside a string. mb::JSON::decode: malformed UTF-8 in string-
$mb::JSON::STRICTis set and a decoded string is not a well-formed UTF-8 byte sequence. mb::JSON::decode: number out of range: <text>-
A JSON number literal is finite JSON syntax but outside the range of this perl's floating point type. Too large and converting it would silently produce
Inf,-Inf, orNaN; too small and it would silently produce0, having lost every significant digit the literal carried.decode()rejects both instead, mirroringencode()'s refusal to write a non-finite value out (seemb::JSON::encode: cannot encode Inf or NaNabove). Where that range ends depends on how perl was built: an ordinary double runs out around1e308, so1e400and1e-400are rejected there, while a perl built-Duselongdoubleor-Dusequadmathaccepts both and rejects only far more extreme literals. A literal that is genuinely zero is unaffected on every build: only the mantissa is examined, so0,0.000and0e-400all decode to0. mb::JSON::decode: nesting too deep (max <N>)-
The JSON text nests objects or arrays deeper than
$mb::JSON::MAX_DEPTH. mb::JSON::encode: nesting too deep (max <N>)-
The Perl data structure nests deeper than
$mb::JSON::MAX_DEPTH. mb::JSON::encode: circular reference detected-
The Perl data structure contains a reference to one of its own ancestors, which has no JSON representation.
mb::JSON::encode: cannot encode Inf or NaN-
A numeric value was
Inf,-Inf, orNaN. JSON has no syntax for these; encode them asnullor as a string yourself if you need them.
INCOMPATIBLE CHANGES
These changes in 0.07 can alter the behaviour of code written against 0.06.
decode()/parse()reject an unrecognized backslash escape inside a string withmb::JSON::decode: invalid escape sequence. Earlier releases passed the backslash through literally.encode()croaks onInf/NaN, and on a string spelling one of them exactly, rather than emitting"Inf"or"NaN"as a JSON string. See "LIMITATIONS".encode()croaks on a circular reference instead of recursing until the process runs out of stack.decode()andencode()croak on nesting deeper than$mb::JSON::MAX_DEPTH, which defaults to 512. Earlier releases had no limit.decode()skips a leading UTF-8 byte order mark instead of reporting an unexpected token.encode()called with no argument at all encodes$_instead of returningnull. An explicitencode(undef)still returnsnull.decode()/parse()croak on a number literal outside perl's floating point range instead of silently converting it. Too large (1e400) used to returnInf, whichencode()would then refuse to re-encode; too small (1e-400) used to return0.decode()/parse()accept only the four whitespace bytes RFC 8259 allows between tokens. Earlier releases used perl's\s, which also accepted form feed on every perl and vertical tab on perl 5.18 and later -- so a document containing one of those decoded on some perls and not on others.encode()/stringify()apply$mb::JSON::MAX_DEPTHto objects and arrays only, and stop at the same countdecode()does. Earlier releases counted leaf scalars as a level of their own and allowed one container more thandecode()would read back.
PERFORMANCE
Up to 0.06 the decoder consumed its input by repeatedly deleting the front of the buffer, which copies the remainder of the text on every token and makes decoding quadratic in the input length. Since 0.07 the scanner advances pos() instead, so decoding is linear. On one reference machine an 800 KB document took over 12 seconds to decode with the old scanner and about 0.2 seconds with the new one. Documents of a few kilobytes are too small for the difference to matter.
parse() and stringify() hand their argument straight to decode() and encode(). Up to 0.06 each copied it into a lexical first, so calling the alias rather than the primary name duplicated the whole document for nothing.
encode() is roughly 20% slower than before. That is the cost of tracking the current path so that a circular reference can be reported instead of recursing until the process runs out of stack.
SEE ALSO
BUGS
Please report bugs to ina.cpan@gmail.com.
AUTHOR
INABA Hitoshi <ina.cpan@gmail.com>
COPYRIGHT AND LICENSE
Copyright (c) 2021, 2022, 2026 INABA Hitoshi <ina.cpan@gmail.com>
This software is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.