JSON und PHP – das dynamische Duo
Kommentare

Im Web finden sich bereits zahlreiche Tutorials über den Umgang mit JSON und PHP, jedoch gehen die meisten nicht tiefer als dass die json_encode()-Funktion mit einem Array von Daten befeuert wird, und man hofft, dass der korrekte JSON-String zurückkommt. Dieser Artikel versucht diesen Umstand zu beheben und führt zuerst in die JSON-Notation ein und beschreibt danach das korrekte Handling mit PHP. Auch jene Leser, welche nicht ausschließlich mit PHP arbeiten, sollten also im ersten Teil auf ihre Kosten kommen.

JSON (für JavaScript Object Notation) ist ein Datenaustauschformat, welches sowohl kompakt, als auch einfach lesbar ist (wie XML, aber ohne so viel zusätzliches Markup um die eigentlichen Nutzdaten). Die Syntax selbst ist eine Teilmenge von JavaScript, welche im Jahr 1999 standardisiert wurde. Wer mehr darüber erfahren möchte, kann sich auf der offiziellen Webseite darüber informieren.

Eine weitere positive Eigenschaft von JSON ist, dass mit seinen Objekten direkt in JavaScript gearbeitet werden kann, sodass es den perfekten Klebstoff zwischen Server und Client darstellt. Da überflüssige Zeichen (der Overhead) gering gehalten sind, müssen weniger davon übertragen werden und helfen so dabei, Bandbreite (und damit meistens Kosten) zu sparen. In modernen Webarchitekturen hat JSON bereits XML (außer wahrscheinlich in der Java-Welt) überholt und wird in clientseitigen Frameworks wie backbone.js vorwiegend eingesetzt.

Bevor wir uns an das Handling von JSON in PHP wagen, machen wir einen kurzen Abstecher in die JSON-Spezifikation um zu sehen was damit alles angestellt werden kann.

Eine Einführung in JSON

Da JSON eine Untermenge von JavaScript darstellt, teilt es alle Sprachkonstrukte mit seinem Elternteil. In JSON können Key/Value-Kombinationen entweder geordnet in Arrays, oder ungeordnet in Objekten abgelegt werden. Da JSON nur eine kleine Menge an Datentypen definiert, können komplexere nicht direkt übertragen werden, sondern müssen in eine geeignete Form umgewandelt werden. Zum Beispiel kann ein DateTime-Objekt aus PHP nicht direkt übertragen werden, sondern muss zunächst in einen Unix-Timestamp umgewandelt werden. Wie es einfacher geht, zeigt der Artikel etwas später.

Welche Datentypen werden nun von JSON unterstützt? Einfach gesagt gibt es: Strings, Nummern, Booleans und NULL. Des Weiteren können auch Arrays und Objekte als Werte verwendet werden. So sieht ein typisches JSON-Dokument aus:

{
"title": "Ein toller Artikel",
"clicks": 4000,
"children": null,
"published": true,
"comments": [
{
"author": "Mister X",
"message": "Ein toller Beitrag"
},
{
"author": "Misrer Y",
"message": "Ich schon wieder!"
}
]
}

Dieses Dokument enthält fast alle möglichen Datentypen, welche mit JSON ausgedrückt werden können. Wie man sieht, gibt es also keine Datumsobjekte, reguläre Ausdrücke oder ähnliches. Des Weiteren muss sichergestellt werden, dass das gesamte JSON Dokument in UTF-8 encodiert wird (wir werden etwas später sehen, wie wir das in PHP gewährleisten können). Aufgrund dieser Limitierungen (und aufgrund von anderen guten Gründen) wurde BSON (Binary JSON) entwickelt. Es ist speicherplatzeffizienter als JSON und erlaubt es, komplexere Objekte wie Datentypen zu transferieren. Der bekannteste Benutzer von BSON ist MongoDB, aber ehrlich gesagt konnte ich es noch nicht woanders im Einsatz beobachten. Trotz alledem würde ich ein kurzes Schmökern in der Spezifikation empfehlen, es lohnt sich!

Da PHP weitaus komplexere Datentypen als JSON erlaubt, muss zwangsweise eine Art der Transformierung stattfinden (neben dem zwangsläufigen Aufruf von json_encode() oder json_decode()). Wenn zum Beispiel Datumsobjekte transportiert werden sollen, muss man sich überlegen ob es ausreicht, wenn man diese in einen Unix-Timestamp umwandelt, oder vielleicht doch einen formatierten String (zum Beispiel durch die strftime()-Funktion) einsetzt.

JSON Encodieren in PHP

Noch vor einigen Jahren wurde die JSON-Unterstützung durch die json-pecl-Extension gewährleistet. Seit PHP 5.2 befindet sich der entsprechende Code direkt im Kern von PHP, sodass kein Modul extra dazuinstalliert werden muss.

Anmerkung: Sollte eine PHP Version älter als 5.3 im Einsatz sein, dann würde ich generell ein Upgrade empfehlen. PHP 5.3 ist die älteste Version, welche offiziell noch mit Updates versorgt wird und da in der letzten Zeit häufig Sicherheitslücken bekannt wurden würde ich ein Upgrade als kritisch betrachten.

Zurück zu JSON und PHP. Mit json_encode() können alle Datentypen (außer Ressourcen) nach JSON konvertiert werden, vorausgesetzt sie sind mittels UTF-8 codiert. „Klassische“ Arrays, also jene mit aufsteigendem numerischen Index, werden in JSON Arrays (mit eckigen Klammern) umgewandelt. Alle anderen Varianten (vor allem assoziative Arrays) werden in Objekte umgewandelt.

Der Funktionsaufruf ist einfach und sieht so aus:

json_encode(mixed $value, int $options = 0);

Ein numerischer Wert statt einem Array als Argument für Optionen scheint auf den ersten Blick ungewöhnlich. Tatsächlich handelt es sich dabei um das – hauptsächlich in C gebräuchliche – Sprachkonstrukt einer Bitmaske. Diese werden etwas später ausführlicher behandelt. Da sie allerdings die Art des Encodings beeinflussen, gehen wir einfach von den Standardeinstellungen aus und geben keine speziellen Parameter an.

Sehen wir uns zuerst die grundlegenden Datentypen an. Die Ausgaben befinden sich direkt über den entsprechenden Aufrufen:

 "four", 8 => "eight"));

// Ergibt: {"apples":true,"bananas":null}
json_encode(array("apples" => true, "bananas" => null));
?>

Wie das Array in PHP umgewandelt wird hängt also von den verwendeten Indizes ab. Des Weiteren sieht man, dass json_encode() die korrekten Datentypen verwendet, sodass boolsche Werte oder NULL nicht etwa zu einem String umgewandelt werden. Nun zu den Objekten:

firstname = "foo";
$user->lastname = "bar";

// Ergibt: {"firstname":"foo","lastname":"bar"}
json_encode($user);

$user->birthdate = new DateTime();

/* Ergibt:
{
"firstname":"foo",
"lastname":"bar",
"birthdate": {
"date":"2012-06-06 08:46:58",
"timezone_type":3,
"timezone":"Europe/Berlin"
}
}
*/
json_encode($user);
?>

Die Objekte werden analysiert und alle sichtbaren („public“) Attribute werden für die Umwandlung herangezogen. Dies passiert auch rekursiv, sodass im obigen Beispiel auch die Attribute des DateTime-Objekts umgewandelt werden. Dies ist eine einfache Möglichkeit, Datumsobjekte als JSON zu übertragen ohne sich viel mit der Konvertierung beschäftigen zu müssen.

func = function() {
return "Foo";
};
}
}

$user = new User();

// Ergibt: {"pub":"Mister X.","func":{},"notUsed":null}
echo json_encode($user);
?>

Dieses Beispiel verdeutlicht, dass nur nach außen hin sichtbare Attribute für die Konvertierung herangezogen werden. Nicht initialisierte Variablen werden als NULL dargestellt, während anonyme Funktionen (Closures) als leeres Objekt dargestellt werden (in der aktuellen Version von PHP ist es nicht möglich das umwandeln von Closures zu unterbinden).

[ header = Seite 2: Die $option Bitmasken ]

Die $option Bitmasken

Bitmasken werden dazu verwendet, einzelne Flags zu aktivieren oder zu deaktivieren. Dieses Sprachkonstrukt findet sich häufig in C und da PHP in C geschrieben ist, wurde es für einige Funktionen übernommen. Die Benutzung ist einfach: Man verwendet Konstanten, um die Argumente anzugeben. Und falls es mehr als eines ist wird es mit dem „bitweisen Oder“ `|` konkateniert. Ein solcher Aufruf könnte wie folgt aussehen:


Durch JSON_FORCE_OBJECT wird nun jede Form des Arrays in ein Objekt umgewandelt, während mittels JSON_NUMERIC_CHECK Nummern, welche als String vorliegen, in den korrekten Datentyp konvertiert werden. Zu beachten ist, dass die meisten Konstanten erst in PHP 5.3 enthalten sind und manche erst seit 5.4. Die meisten der Konstanten verändern die Art, mit der bestimmte Zeichen codiert werden (hauptsächlich Zeichen mit speziellen Bedeutungen wie „, `&` oder `““`). Seit PHP 5.4 gibt es auch eine JSON_PRETTY_PRINT-Konstante, welche während der Entwicklung sehr hilfreich sein kann, in einer Produktionsumgebung jedoch deaktiviert werden sollte. Mit dieser Option werden zusätzlich Leerzeichen eingefügt, sodass die Lesbarkeit auf der Konsole oder im Browser deutlich gesteigert wird.

JSON decodieren in PHP

Decodieren ist so einfach wie codieren. PHP enthält analog zur json_encode()-Funktion auch eine mit dem Namen json_decode(). Dieser wird ein JSON String übergeben, welcher ohne weitere Parameter ein Objekt vom Typ stdClass zurückgibt. Hier ist ein kurzes Beispiel:

 string(3) "bar" ["cool"]=> string(4) "attr" }
var_dump($result);

// Gibt "bar" aus.
echo $result->foo;

// Gibt "attr" aus.
echo $result->cool;
?>

Wenn stattdessen ein assoziatives Array zurückgegeben werden soll, kann der zweite Parameter auf true gesetzt werden:

 string(3) "bar" ["cool"]=> string(4) "attr" }
var_dump($result);

// Gibt "bar" aus.
echo $result['foo'];

// Gibt "attr" aus.
echo $result['cool'];
?>

Sollte ein potentiell zu tief geschachteltes JSON-Dokument decodiert werden, kann man mit dem Limit Argument eine maximale Parsingtiefe einstellen. Falls diese überschritten wird, gibt die Funktion NULL zurück.


Das letzte Argument funktioniert genau so wie json_encode(), allerdings wird aktuell nur eine Konstante unterstützt (welche es erlaubt, große Integerzahlen in Strings umzuwandeln). Diese funktioniert allerdings erst mit PHP 5.4.

Bis jetzt haben wir uns ausschließlich mit validen Dokumenten beschäftigt (außer im letzten Beispiel). Der nächste Teil zeigt, wie man mit Fehlern kontrolliert umgehen kann.

Error-Handling und Testing

Falls das JSON Dokument nicht gelesen werden kann oder die Tiefe zu hoch ist, dann gibt die json_encode()-Funktion NULL zurück. Für uns als Entwickler steht jedoch eine weitere Funktion zur Verfügung, nämlich json_last_error()

Diese enthält den Fehlercode des letzten Aufrufs und kann somit abgefangen werden. Die entsprechenden Konstanten sind hier zu finden.

  • JSON_ERROR_NONE: Kein Fehler ist aufgetreten.
  • JSON_ERROR_DEPTH: Die maximale Parsingtiefe wurde erreicht.
  • JSON_ERROR_STATE_MISMATCH: Ungültiges JSON-Dokument.
  • JSON_ERROR_CTRL_CHAR: „Control Character“-Fehler, möglicherweise falsches Encoding.
  • JSON_ERROR_SYNTAX: Syntax Error.
  • JSON_ERROR_UTF8: UTF-8 Fehler, möglicherweise falsches Encoding. (seit PHP 5.3.3).

Mit diesen Informationen ist es uns nun möglich, eine Klasse zu schreiben, welche eine aussagekräftige Exception wirft, falls ein Fehler aufgetreten ist.

 'No error has occurred',
JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
JSON_ERROR_SYNTAX => 'Syntax error',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
);

public static function encode($value, $options = 0) {
$result = json_encode($value, $options);

if($result || json_last_error() == JSON_ERROR_NONE) {
return $result;
}

throw new RuntimeException(static::$_messages[json_last_error()]);
}

public static function decode($json, $assoc = false) {
$result = json_decode($json, $assoc);

if($result || json_last_error() == JSON_ERROR_NONE) {
return $result;
}

throw new RuntimeException(static::$_messages[json_last_error()]);
}

}
?>

Mit einer Hilfsfunktion können die Exceptions nun elegant getestet werden:

// Gibt "Correctly thrown" aus.
assertException("Syntax error", function() {
$string = '{"foo": {"bar": {"cool": NONUMBER}}}';
$result = JsonHandler::decode($string);
});

Seit PHP 5.3.3 gibt es einen JSON_ERROR_UTF8 Fehler, welcher auftritt, falls das falsche Encoding verwendet wurde. Um zu verifizieren, dass ein UTF-8 String vorliegt, kann die utf8_encode()-Funktion verwendet werden. Diese konvertiert den String in UTF-8.


Ich habe dies öfters verwendet, um die gelieferten Daten einer alten MSSQL Datenbank zu konvertieren, bevor sie an den Browser gesendet wurden.

Fazit

JSON stellt ein kompaktes und leicht lesbares Format zum Datenaustausch zwischen Endpunkten dar. Es scheint, als würde es XML als de-facto Standard im Web den Rang ablaufen. PHP liefert von Haus aus alle notwendigen Werkzeuge um mit JSON effektiv umgehen zu können und bietet (seit PHP 5.3) zahlreiche Optionen an um das Verhalten weiter zu anzupassen.

Viel Erfolg beim Datenschaufeln!

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -