After reading this post, you will be able to understand, work and test JSON code with PHP.
There are already lots of tutorials out there
on handling JSON with PHP, but most of them don't go much deeper than
throwing an array against
json_encode
and hoping for the best. This article aims to be a solid introduction
into JSON and how to handle it correctly in combination with PHP. Also,
readers who don't use PHP as their programming language can benefit from
the first part that acts as a general overview on JSON.
JSON (JavaScript Object Notation) is a data exchange format that is
both lightweight and human-readable (like XML, but without the bunch of
markup around your actual payload). Its syntax is a subset of the
JavaScript language that was standardized in
1999. If you want to find out more, visit the
official website.
The cool thing about JSON is that you can handle it natively in
JavaScript, so it acts as the perfect glue between server- and
client-side application logic. Also, since the syntactical overhead
(compared to XML) is very low, you need to transfer less bytes of ther
wire. In modern web stacks, JSON has pretty much replaced
XML as the de-factor payload format (aside from the Java world I suppose) and client side application frameworks like
backbone.js make heavy use of it inside the model layer.
Before we start handling JSON in PHP, we need to take a short look at
the JSON specification to understand how it is implemented and what's
possible.
Introducing JSON
Since JSON is a subset of the JavaScript language, it shares all of
its language constructs with its parent. In JSON, you can store
unordered key/value combinations in objects or use arrays to preserve
the order of values. This makes it easy to parse and to read, but also
has some limitations. Since JSON only defines a small amount of data
types, you can't transmit types like dates natively (you can, but you
need to transform them into a string or a unix timestamp as an integer).
So, what datatypes does JSON support? It all boils down to strings,
numbers, booleans and null. Of course, you can also supply objects or
arrays as values.
Here's a example JSON document:
{
"title": "A cool blog post",
"clicks": 4000,
"children": null,
"published": true,
"comments": [
{
"author": "Mister X",
"message": "A really cool posting"
},
{
"author": "Misrer Y",
"message": "It's me again!"
}
]
}
It contains basically everything that you can express through JSON.
As you can see no dates, regexes or something like that. Also, you need
to make sure that your whole JSON document is encoded in
UTF-8. We'll see later how to ensure that in PHP. Due to this shortcomings (and for other good reasons)
BSON(Binary
JSON) was developed. It was designed to be more space-efficient and
provides traversability and extensions like the date type. Its most
prominent use case is
MongoDB, but
honestly I never came across it somewhere else. I recommend you to take
a short look at the specification if you have some time left, since I
find it very educating.
Since PHP has a richer type handling than JSON, you need to prepare
yourself to write some code on both ends to transform the correct
information apart from the obligatory encoding/decoding step. For
example, if you want to transport date objects, you need to think if you
can just send a unix timestamp over the wire or maybe use a
preformatted date string (like
strftime).
Encoding JSON in PHP
Some years ago, JSON support was provided through the
json pecl extension. Since PHP 5.2, it is included in the core directly, so if you use a recent PHP version you should have no trouble using it.
Note: If you run an older version of PHP than 5.3, I recommend you to
upgrade anyway. PHP 5.3 is the oldest version that is currently
supported and with the latest PHP
security bugs found I would consider it critical to upgrade as soon as possible.
Back to JSON. With
json_encode,
you can translate anything that is UTF-8 encoded (except resources)
from PHP into a JSON string. As a rule of thumb, everything except pure
arrays (in PHP this means arrays with an ordered, numerical index) is
converted into an object with keys and values.
The method call is easy and looks like the following:
json_encode(mixed $value, int $options = 0);
An integer for options you might ask? Yup, that's called a
bitmask.
We'll cover them in a separate part a little bit later. Since these
bitmask options change the way the data is encoded, for the following
examples assume that we use defaults and don't provide custom params.
Let's start with the basic types first. Since its so easy to grasp, here's the code with short comments on what was translated:
<?php
// Returns: ["Apple","Banana","Pear"]
json_encode(array("Apple", "Banana", "Pear"));
// Returns: {"4":"four","8":"eight"}
json_encode(array(4 => "four", 8 => "eight"));
// Returns: {"apples":true,"bananas":null}
json_encode(array("apples" => true, "bananas" => null));
?>
How your arrays are translated depends on your indexes used. You can also see that
json_encode
takes care of the correct type conversion, so
booleans
and
null
are not transformed into strings but use their correct type. Let's now look into objects:
<?php
class User {
public $firstname = "";
public $lastname = "";
public $birthdate = "";
}
$user = new User();
$user->firstname = "foo";
$user->lastname = "bar";
// Returns: {"firstname":"foo","lastname":"bar"}
json_encode($user);
$user->birthdate = new DateTime();
/* Returns:
{
"firstname":"foo",
"lastname":"bar",
"birthdate": {
"date":"2012-06-06 08:46:58",
"timezone_type":3,
"timezone":"Europe\/Berlin"
}
}
*/
json_encode($user);
?>
Objects are inspected and their public attributes are converted. This
happens recursively, so in the example above the public attributes of
the
DateTime
object are also translated into JSON. This is a handy trick if you want
to easly transmit datetimes over JSON, since the client-side can then
operate on both the actual time and the timezone.
<?php
class User {
public $pub = "Mister X.";
protected $pro = "hidden";
private $priv = "hidden too";
public $func;
public $notUsed;
public function __construct() {
$this->func = function() {
return "Foo";
};
}
}
$user = new User();
// Returns: {"pub":"Mister X.","func":{},"notUsed":null}
echo json_encode($user);
?>
Here, you can see that only public attributes are used. Not initialized variables are translated to
null
while closures that are bound to a public attribute are encoded with an
empty object (as of PHP 5.4, there is no option to prevent public
closures to be translated).
The $option bitmasks
Bitmasks are used to set certain flags on or off in a function call.
This language pattern is commonly used in C and since PHP is written in C
this concept made it up to some PHP function arguments as well. It's
easy to use: if you want to set an option, just pass the constant as an
argument. If you want to combine two or more options, combine them with
the bitwise OR operation
|
. So, a call to json_encode may look like this:
<?php
// Returns: {"0":"Starsky & Hutch","1":123456}
json_encode(array("Starsky & Hutch", "123456"), JSON_NUMERIC_CHECK | JSON_FORCE_OBJECT);
?>
JSON_FORCE_OBJECT
forces the array to be translated into an object and
JSON_NUMERIC_CHECK
converts string-formatted numbers to actual numbers. You can find all bitmasks (constants)
here.
Note that most of the constants are available since PHP 5.3 and some of
them were added in 5.4. Most of them deal with how to convert
characters like
< >
,
&
or
""
. PHP 5.4 provides a
JSON_PRETTY_PRINT
constant that may you help during development since it uses whitespace
to format the output (since it adds character overhead, I won't enable
it in production of course).
Decoding JSON in PHP
Decoding JSON is as simple as encoding it. PHP provides you a handy
json_decode function that handles everything for you. If you just pass a valid JSON string into the method, you get an object of type
stdClass
back. Here's a short example:
<?php
$string = '{"foo": "bar", "cool": "attr"}';
$result = json_decode($string);
// Result: object(stdClass)#1 (2) { ["foo"]=> string(3) "bar" ["cool"]=> string(4) "attr" }
var_dump($result);
// Prints "bar"
echo $result->foo;
// Prints "attr"
echo $result->cool;
?>
If you want to get an associative array back instead, set the second parameter to true:
<?php
$string = '{"foo": "bar", "cool": "attr"}';
$result = json_decode($string, true);
// Result: array(2) { ["foo"]=> string(3) "bar" ["cool"]=> string(4) "attr" }
var_dump($result);
// Prints "bar"
echo $result['foo'];
// Prints "attr"
echo $result['cool'];
?>
If you expect a very large nested JSON document, you can limit the recursion depth to a certain level. The function will return
null
and stops parsing if the document is deeper than the given depth.
<?php
$string = '{"foo": {"bar": {"cool": "value"}}}';
$result = json_decode($string, true, 2);
// Result: null
var_dump($result);
?>