ZetCode

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.

encode_hash.pl
#!/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.

decode_text.pl
#!/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.

decode_array.pl
#!/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.

pretty.pl
#!/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.

write_file.pl
#!/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.

read_file.pl
#!/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 $@.

errors.pl
#!/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.

utf8.pl
#!/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.

booleans.pl
#!/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.

scalar.pl
#!/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.

dumper.pl
#!/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

Source

JSON::PP · Perl open function · File::Temp · Encode · Perl Unicode · Data::Dumper

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.