Perl JSON
last modified October 9, 2026
JSON (JavaScript Object Notation) is a lightweight data interchange format. It is
text based, language independent, and easy for humans and machines to read. In
Perl, the core module JSON::PP encodes Perl data structures into
JSON text and decodes JSON text back into Perl data structures.
The most common entry points are the functions encode_json and
decode_json, which convert between a Perl reference and a JSON
string in a single call. For finer control, the object interface
JSON::PP->new provides methods such as canonical,
pretty, utf8, and allow_nonref.
JSON::PP is a core module since Perl 5.14, so nothing has to be
installed.
In this tutorial we build, print, store, and read JSON data with
JSON::PP, including UTF-8 text, booleans, null values, and top
level scalars.
Encoding a hash to JSON
A Perl hash becomes a JSON object when a hash reference is passed to an encoder.
The function encode_json is a shorthand for
JSON::PP->new->encode and returns a compact JSON string.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use JSON::PP;
my %user = (
name => 'Alice',
age => 30,
email => 'alice@example.com',
);
my $json = JSON::PP->new->canonical->encode(\%user);
say $json;
my @tags = ('perl', 'json', 'tutorial');
say encode_json(\@tags);
my $project = { project => 'ZetCode' };
say encode_json($project);
encode_json writes a hash reference as a JSON object and an array
reference as a JSON array. The order of the keys follows the internal order of
the Perl hash, which is not stable between runs, so the object interface with
canonical is used for the multi-key hash. A hash with a single key
has only one possible order and can be encoded directly.
$ perl encode_hash.pl
{"age":30,"email":"alice@example.com","name":"Alice"}
["perl","json","tutorial"]
{"project":"ZetCode"}
Decoding JSON text
Decoding is the reverse direction. decode_json takes a JSON string
and returns a Perl reference, a hash reference for a JSON object and an array
reference for a JSON array.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use JSON::PP;
my $text = '{"name":"Alice","age":30,"roles":["admin","user"]}';
my $data = decode_json($text);
say ref $data;
say $data->{name};
say $data->{age} + 1;
say join ', ', @{ $data->{roles} };
my $nums = decode_json('[10, 20, 30]');
say ref $nums;
say scalar @$nums;
say $nums->[0] + $nums->[2];
The function always returns a reference, so ref tells which kind of
structure was produced. Object members are read through the hash reference with
{...}, and array elements through the array reference with
[...]. Numbers arrive as Perl numbers and can be used in
arithmetic.
$ perl decode_text.pl HASH Alice 31 admin, user ARRAY 3 40
Decoding an array of objects
A very common payload is a JSON array whose elements are objects. Every element
becomes a hash reference, so the array can be traversed with an ordinary loop or
with list operators such as map and grep.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use JSON::PP;
my $text = <<'JSON';
[
{ "id": 1, "name": "Alice", "city": "Bratislava" },
{ "id": 2, "name": "Bob", "city": "Kosice" },
{ "id": 3, "name": "Carol", "city": "Presov" }
]
JSON
my $people = decode_json($text);
say scalar @$people, " records";
for my $person (@$people) {
printf "%d %s (%s)\n", $person->{id}, $person->{name}, $person->{city};
}
say join '|', map { $_->{name} } @$people;
my ($match) = grep { $_->{city} eq 'Kosice' } @$people;
say "found: $match->{name}";
my $cities = join ' ', sort map { $_->{city} } @$people;
say $cities;
A here document keeps a JSON document readable inside a Perl program. Each
element of the decoded array is a hash reference, which is why
$_->{name} inside map and grep reads a
single field of one record.
$ perl decode_array.pl 3 records 1 Alice (Bratislava) 2 Bob (Kosice) 3 Carol (Presov) Alice|Bob|Carol found: Bob Bratislava Kosice Presov
Pretty printing with JSON::PP
Compact JSON saves space but is hard to read. The method pretty
enables indented output, canonical sorts the keys of every object,
and encode returns the resulting string.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use JSON::PP;
my %config = (
zebra => 1,
alpha => [ 1, 2, 3 ],
mike => { enabled => JSON::PP::true, level => 3 },
);
my $pretty = JSON::PP->new->pretty->canonical->encode(\%config);
print $pretty;
my $compact = JSON::PP->new->canonical->encode(\%config);
say $compact;
say 'indent length: ', JSON::PP->new->pretty->get_indent_length;
The pretty flag also turns on a space before and after the colon,
which is why the output contains "alpha" : [, and the indentation
length defaults to three spaces. Together with canonical the result
is byte for byte reproducible, so two hashes with the same content always
produce the same text. That matters when JSON is compared, hashed, or committed
to a version control system.
$ perl pretty.pl
{
"alpha" : [
1,
2,
3
],
"mike" : {
"enabled" : true,
"level" : 3
},
"zebra" : 1
}
{"alpha":[1,2,3],"mike":{"enabled":true,"level":3},"zebra":1}
indent length: 3
Writing JSON to a file
JSON is usually written to a file or sent over a network. A three argument
open with an explicit encoding layer writes the string as UTF-8,
and close flushes and releases the file handle.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use File::Spec::Functions qw(catfile);
use File::Temp qw(tempdir);
use JSON::PP;
my $dir = tempdir(CLEANUP => 1);
my $path = catfile($dir, 'user.json');
my $data = {
name => 'Alice',
age => 30,
active => JSON::PP::true,
};
my $json = JSON::PP->new->canonical->encode($data);
say "json: $json";
open my $fh, '>:encoding(UTF-8)', $path or die "Cannot open file: $!";
print {$fh} $json;
close $fh or die "Cannot close file: $!";
say "length: ", -s $path;
say "chars: ", length $json;
open my $in, '<:encoding(UTF-8)', $path or die "Cannot open file: $!";
my $back = do { local $/; <$in> };
close $in;
say 'roundtrip ok' if $back eq $json;
The example creates a temporary directory with
File::Temp::tempdir and the CLEANUP flag, so the
directory and its files are removed when the program ends. The JSON text never
contains a newline, therefore the file holds exactly the encoded bytes. Reading
the file back and comparing it with the original string proves that the write
succeeded.
$ perl write_file.pl
json: {"active":true,"age":30,"name":"Alice"}
length: 39
chars: 39
roundtrip ok
Reading and decoding a JSON file
To read JSON from a file, the whole file is slurped into one scalar and then
passed to decode_json. Slurping is done by setting the input record
separator to the undefined value inside a small block.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use File::Spec::Functions qw(catfile);
use File::Temp qw(tempdir);
use JSON::PP;
my $dir = tempdir(CLEANUP => 1);
my $path = catfile($dir, 'data.json');
my $source = '{"server":"zeta","ports":[80,443],"enabled":true}';
open my $out, '>:encoding(UTF-8)', $path or die "Cannot open file: $!";
print {$out} $source;
close $out or die "Cannot close file: $!";
open my $in, '<:encoding(UTF-8)', $path or die "Cannot open file: $!";
my $text = do { local $/; <$in> };
close $in;
my $data = decode_json($text);
say 'server: ', $data->{server};
say 'ports: ', join ', ', @{ $data->{ports} };
say 'enabled: ', $data->{enabled} ? 'yes' : 'no';
say 'keys: ', scalar keys %$data;
say 'slurped: ', length $text;
Setting local $/ to undef puts the file handle into
slurp mode, so a single read returns the entire content. The input layer
:encoding(UTF-8) decodes the bytes back to characters before
decode_json sees them.
$ perl read_file.pl server: zeta ports: 80, 443 enabled: yes keys: 3 slurped: 49
Handling decoding errors
Malformed input is the normal case in real programs.
decode_json and the decode method
die on invalid JSON, so the call is wrapped in eval
and the error is inspected in $@.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use JSON::PP;
my $decoder = JSON::PP->new;
my @inputs = ('{"name":"Alice"}', '{"name":"Alice",}', '[1, 2, 3');
for my $text (@inputs) {
my $data = eval { $decoder->decode($text) };
if (my $err = $@) {
$err =~ s/ at \S+ line \d+\.?\s*\z//;
say "error: $err";
next;
}
say 'ok: ', JSON::PP->new->canonical->encode($data);
}
eval { decode_json('') };
say 'empty string failed' if $@;
say 'still running after eval';
The message produced by JSON::PP names the problem and the
character offset where it was detected. The at file line N. part
appended by die is stripped here so that the output stays stable.
When eval catches the exception, $@ holds the error
text and the loop continues with the next input.
$ perl errors.pl
ok: {"name":"Alice"}
error: unexpected end of string while parsing JSON string, at character offset 17 (before "(end of string)")
error: , or ] expected while parsing array, at character offset 8 (before "(end of string)")
empty string failed
still running after eval
UTF-8 encoding and decoding
JSON text is UTF-8 by default. The utf8 flag makes the encoder
produce bytes and the decoder expect bytes, while without the flag both work
with Perl character strings.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use Encode qw(encode);
use JSON::PP;
use utf8;
my %city = ( name => 'Košice' );
my $bytes = JSON::PP->new->utf8->canonical->encode(\%city);
say 'byte length: ', length $bytes;
say 'byte hex: ', unpack 'H*', $bytes;
my $chars = JSON::PP->new->canonical->encode(\%city);
say 'char length: ', length $chars;
say 'wide chars: ', utf8::is_utf8($chars) ? 'yes' : 'no';
my $back = JSON::PP->new->utf8->decode($bytes);
say 'roundtrip: ', $back->{name} eq $city{name} ? 'ok' : 'bad';
say 're-encoded: ', (encode 'UTF-8', $chars) eq $bytes ? 'ok' : 'bad';
With utf8 enabled the encoder returns a byte string, so the
non-ASCII letter in the name occupies two bytes, and unpack 'H*'
shows the UTF-8 sequence c5a1. Without the flag the same data is a
character string of 17 characters. Encode::encode turns that
character string into exactly the same byte sequence, which confirms that both
forms describe the same text.
$ perl utf8.pl byte length: 18 byte hex: 7b226e616d65223a224b6fc5a1696365227d char length: 17 wide chars: yes roundtrip: ok re-encoded: ok
Booleans and null
JSON has true, false, and null, which are
not Perl values. JSON::PP provides the constants
JSON::PP::true and JSON::PP::false for encoding, and a
Perl undef is encoded as null.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use JSON::PP;
my $data = {
enabled => JSON::PP::true,
disabled => JSON::PP::false,
missing => undef,
count => 0,
};
my $json = JSON::PP->new->canonical->encode($data);
say $json;
my $back = decode_json($json);
say 'bool object: ', (JSON::PP::is_bool($back->{enabled}) ? 'yes' : 'no');
say 'enabled: ', ($back->{enabled} ? 'true' : 'false');
say 'disabled: ', ($back->{disabled} ? 'true' : 'false');
say 'missing: ', (defined $back->{missing} ? 'value' : 'undef');
say 'count: ', $back->{count};
my $flag = JSON::PP::true;
say 'plain true: ', ($flag ? 'true' : 'false');
say 'as json: ', JSON::PP->new->encode({ ok => $flag });
On decoding, true and false become blessed objects of
the class JSON::PP::Boolean, not plain numbers. They behave
correctly in boolean context, and JSON::PP::is_bool reports whether
a value is such an object. A decoded null is an ordinary
undef, so defined is what distinguishes it from the
number zero.
$ perl booleans.pl
{"count":0,"disabled":false,"enabled":true,"missing":null}
bool object: yes
enabled: true
disabled: false
missing: undef
count: 0
plain true: true
as json: {"ok":true}
Scalar top-level values
A JSON document does not have to be an object or an array. A bare string,
number, boolean, or null is valid JSON as well, and the option
allow_nonref controls whether such documents are accepted.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use JSON::PP;
my $lax = JSON::PP->new->allow_nonref;
my $strict = JSON::PP->new->allow_nonref(0);
say 'string: ', $lax->encode('plain text');
say 'number: ', $lax->encode(42);
say 'float: ', $lax->encode(3.5);
say 'bool: ', $lax->encode(JSON::PP::true);
say 'null: ', $lax->encode(undef);
say 'decoded string: ', $lax->decode('"hello"');
say 'decoded number: ', $lax->decode('7') + 1;
eval { $strict->decode('42') };
say 'strict scalar fails: ', ($@ ? 'yes' : 'no');
say 'strict array ok: ', (ref $strict->decode('[1, 2]') eq 'ARRAY' ? 'yes' : 'no');
The pure Perl JSON::PP enables allow_nonref by
default, unlike the JSON::XS API it emulates, where the flag is off
and a bare scalar fails to decode. Calling allow_nonref(0) restores
the strict behaviour, which is handy when a top level scalar signals a broken
protocol message.
$ perl scalar.pl string: "plain text" number: 42 float: 3.5 bool: true null: null decoded string: hello decoded number: 8 strict scalar fails: yes strict array ok: yes
Comparing with Data::Dumper
When a data structure has to be inspected while debugging,
Data::Dumper shows the Perl references and
JSON::PP->new->pretty shows the same data in the format that is
actually transferred.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use Data::Dumper;
use JSON::PP;
my $data = {
name => 'Alice',
tags => [ 'perl', 'json' ],
active => 1,
};
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Terse = 1;
say Dumper($data);
say JSON::PP->new->pretty->canonical->encode($data);
Data::Dumper prints Perl syntax with quotes and arrows, and
$Data::Dumper::Sortkeys orders the keys. The JSON view is shorter
and sorted, and it reveals the encoding decisions, for example that a Perl
number becomes a JSON number. Both forms are deterministic once key sorting is
switched on.
$ perl dumper.pl
{
'active' => 1,
'name' => 'Alice',
'tags' => [
'perl',
'json'
]
}
{
"active" : 1,
"name" : "Alice",
"tags" : [
"perl",
"json"
]
}
Best Practices
- Use canonical for stable output: sort object keys whenever JSON is compared, hashed, or stored.
- Always wrap decoding in eval: malformed input dies, so
check
$@instead of letting the program abort. - Be explicit about UTF-8: pick
utf8or plain character strings and apply the same choice to files and sockets. - Do not confuse 0 with false: use
JSON::PP::trueandJSON::PP::falsewhen the JSON value must be a boolean. - Close file handles explicitly: check the return value of
close, because a failure there means data was lost. - Prefer JSON::PP for portability: it is a core module that needs no compilation, while still implementing the JSON::XS API.
Source
JSON::PP · Perl open function · File::Temp · Encode · Perl Unicode · Data::Dumper
Author
List all Perl tutorials.