Betont schlank

Der Hype um Ajax ist im vollen Gange, aber wer sich beim Einsatz dieser Technik strikt an XML hält, vergeudet in vielen Fällen Entwicklungsressourcen. Das Datenaustauschformat JSON bietet eine betont einfache Alternative.

In Pocket speichern vorlesen Druckansicht 4 Kommentare lesen
Lesezeit: 7 Min.
Von
  • Sven Neuhaus
Inhaltsverzeichnis

Es muss nicht immer XML sein. Zwar versprechen Techniken wie das gerade vieldiskutierte Ajax elegante Lösungen mit Javascript, aber manches ließe sich beispielsweise mit JSON einfacher umsetzen.

Bei JSON (Javascript Object Notation, ausgesprochen wie der englische Vorname Jason) handelt es sich um ein schlankes Datenaustauschformat, das - wie der Name nahe legt - auf einer Untermenge der Programmiersprache Javascript basiert; Javascript- und Python-Entwickler müssen daher fast nichts Neues lernen. In der Notation lassen sich Listen, Strings, Zahlen und Objekte darstellen. Unter Objekten versteht man hier assoziative Arrays (Hashes). Diese Konstrukte sind in den meisten zurzeit verwendeten Programmiersprachen in identischer oder ähnlicher Form vorhanden, was die Verwendung von JSON weiter vereinfacht.

Mehr Infos

JSON-Syntax

Objekt
{ Attribute }
{}
Attribute
String: Wert
Attribute, String: wert
Liste
[ Elemente ]
[]
Elemente
Wert
Elemente, Wert
Wert
String
Zahl
Objekt
Liste
true
false
null

Im Gegensatz zu XML ist JSON nicht erweiterbar. Der „Erfinder“ des Formats, Douglas Crockford, vertritt die Ansicht, dass Erweiterungen unnötig seien. Dazu ist allerdings zu sagen, dass die Exponential-Notation von Zahlen in einer früheren Version des Standards noch fehlte.

Als Kodierung verwendet JSON Unicode. In Zeichenketten haben einige aus der C-Sprachfamilie bekannte Sequenzen wie \n, \t und \uXXXX (für Unicode-Zeichen) eine besondere Bedeutung. Die Syntax (siehe den gleichnamigen Kasten) entspricht Javascript und ist außerdem fast 100 %ig Python-kompatibel. Die JSON-Webseite kennt zurzeit Implementierungen von JSON-Parsern und -Generatoren in 16 verschiedenen Programmiersprachen (siehe die Liste auf der JSON-Homepage).

Mehr Infos

Einer der Hauptnachteile bei der Verwendung von XML ist die umständliche Art des Zugriffs auf die übergebenen Daten über die DOM-API. E4X (ECMAScript for XML, siehe [2]) dürfte hier früher oder später für Abhilfe sorgen, ist aber am Markt noch fast gar nicht verfügbar. Da der Client (Browser) den vom Webserver gelieferten Daten in der Regel vertrauen kann, beschränkt sich die Analyse der durch JSON serialisierten Objekte auf einen Aufruf von eval(), und sie stehen unmittelbar als native Javascript-Objekte zur Verfügung.

Zur Benutzung in Perl bietet das CPAN unter anderem das Modul JSON (siehe „Online-Ressourcen“). Es stellt die Funktionen objToJson und jsonToObj zur Verfügung. Außerdem existiert ein OOP-Interface. Das Perl-Script in Listing 1 wandelt einen Datensatz für einen Konferenz-Besucher in JSON sowie XML um - Letzteres durch die Funktion XMLout(). Listing 2 enthält das Ergebnis beider Aufrufe.

Mehr Infos

Listing 1: Perl mit JSON-Modul

use JSON qw(objToJson);
use XML::Simple qw(XMLout);
my $besucher = {
id => 481048,
name => 'Peter Müller',
email => 'pm@example.com',
gebuchte_kurse => [ 5, 21, 22, 23, 40, 44 ],
};
print "Besucher in JSON:\n" .
objToJson($besucher, { pretty => 1}) . "\n";
print "Besucher in XML:\n" .
XMLout($besucher, NoAttr => 1, RootName => 'besucher');
Mehr Infos

Listing 2: Ausgabe von Listing 1

Besucher in JSON:
{
"id" : 481048,
"name" : "Peter Müller",
"email" : "pm@example.com",
"gebuchte_kurse" : [
5,
21,
22,
23,
40,
44
],
}
________________________________________

Besucher in XML:
<besucher>
<id>481048</id>
<name>Peter Müller</name>
<email>pm@example.com</email>
<gebuchte_kurse>5</gebuchte_kurse>
<gebuchte_kurse>21</gebuchte_kurse>
<gebuchte_kurse>22</gebuchte_kurse>
<gebuchte_kurse>23</gebuchte_kurse>
<gebuchte_kurse>40</gebuchte_kurse>
<gebuchte_kurse>44</gebuchte_kurse>
</besucher>

Um bei der Übergabe als XML in Javascript (ohne E4X) auf diese gebuchten Kurse zugreifen zu können, wäre nach dem Parsing des XML ein Stück Code erforderlich, wie er in Listing 3 zu sehen ist. Dieser Ansatz verkompliziert sich weiter, wenn Tags mit identischen Namen in verschiedenen Hierarchiestufen zum Einsatz kommen, da getElementsByTagName() in diesem Fall nicht mehr ohne weiteres benutzbar ist. Mit JSON steht die gewünschte Liste sofort nach dem var besucher = eval(jsonstring) direkt in besucher.gebuchte_kurse zur Verfügung.

Mehr Infos

Listing 3

var gebuchte_kurse = new Array();
var kurse = xmldoc.getElementsByTagName("gebuchte_kurse");
for (var i=0; i < kurse.length; i++) {
gebuchte_kurse.push(kurse[i].firstChild.data);
}

Analog zu XML-RPC wurde eine Schnittstelle für Remote Procedure Calls mit JSON definiert (siehe „Online-Ressourcen“), damit eine Kommunikation über Rechnergrenzen realisierbar ist. Hier haben die Entwickler ebenfalls besonderen Wert auf Einfachheit gelegt. Die Spezifikation beschränkt sich bislang auf die (wohl häufigste) Verwendung von XMLHttpRequest, für HTTP-Anfragen, die Programmierer aus HTML-Formularen heraus abschicken (beispielsweise für ältere Browser ist sie noch unvollständig). Die JSON-RPC-Homepage listet schon Implementierungen in zehn verschiedenen Programmiersprachen auf.

Für Anfragen sieht die Spezifikation drei Attribute vor:

  • method: Name der Methode
  • params: Liste von Objekten, die an die Methode übergeben werden
  • id: Request-ID, mit der asynchron eintreffende Antworten den Requests zugeordnet werden können

Für Antworten gibt es ebenfalls drei Attribute:

  • result: Ergebnis-Objekt. null, falls ein Fehler auftrat.
  • error: Fehler-Objekt. null, falls kein Fehler auftrat.
  • id: Request-Id

Zusätzlich sind noch so genannte „notifications“ vorgesehen - Requests, auf die keine Antwort erfolgt. Diese unterscheiden sich von gewöhnlichen Requests lediglich durch den Wert „null“ im Feld id.

Anhand einer von Webforen bekannten „Shoutbox“ (eine Art schwarzes Brett, auf der Besucher Grüße hinterlassen können) sei im Folgenden die Verwendung des Perl-Moduls JSONRPC::Transport::HTTP vorgestellt. Die Nachrichten werden in diesem Falle in einer SQLite-Datenbank gespeichert. Diese enthält lediglich eine Tabelle „nachrichten“ mit den Spalten „datum“ und „nachricht“.

Listing 4 bietet eine Funktion update_box() an, die zwei Parameter erwartet: einen neuen (optionalen) Text, der in der Datenbank zu speichern ist, sowie den Zeitpunkt des letzten Zugriffs, anhand dessen man neue Nachrichten aus der Datenbank abrufen kann. Das Script verwendet dabei Perls Taint-Modus (-T-Flag in der ersten Zeile) der erzwingt, dass es Daten aus nicht vertrauenswürdigen Quellen (wie aus dem Netz) vor potenziell gefährlicher Verwendung per Pattern-Match prüfen muss. Vergisst der Entwickler dies, beendet der Perl-Interpreter das Programm zur Laufzeit. Das bei der DBI::connect_cached angegebene Flag TaintIn legt fest, dass für Datenbank-Operationen gesäuberte Daten Voraussetzung sind. Sicherheitslöcher infolge von Flüchtigkeitsfehlern, bei denen Daten nicht bereinigt wurden, lassen sich so ausschließen. Die dazugehörige HTML/Javascript-Seite zeigt Listing 5.

Mehr Infos

Listing 4: json-cgi.pl

#!/usr/bin/perl -wT
use JSONRPC::Transport::HTTP;
JSONRPC::Transport::HTTP::CGI->dispatch_to('Shoutbox')->handle;
package Shoutbox;
use strict;
use Time::HiRes qw(time); # Uhrzeit auf 1/100 genau
use DBI;
use HTML::Entities qw(encode_entities);
my $Dbh;
sub update_box { # Nachricht speichern und neu ausliefern
my ($server, @params) = @_; # $server is JSONRPC oject
$Dbh = DBI->connect_cached('dbi:SQLite2:dbname=/tmp/shoutbox', '', '',
{ TaintIn => 1 }) # nur untainted Daten duerfen in die DB###
or die DBI::errstr;
if ($params[0] ne '') { # neue Nachricht vorhanden
my $msg = encode_entities($params[0]); # Entities statt Control-Chars
$msg =~ /(.*)/; # jetzt harmlos, Pattern-Match uer
$msg = $1; # untaint
$Dbh->do(q{ INSERT INTO msg (datum, nachricht) VALUES (?,?) },
undef, int(time() * 1000), $msg); # in DB speichern
}
# Alte Nachrichten holen
my $msgs = $Dbh->selectall_arrayref(q{ SELECT datum, nachricht
FROM msg WHERE datum >= ? ORDER BY datum }, undef,
$params[1]); # Zeitstempel vom Client
return { now => int(time() * 1000), msgs => $msgs };
}
Mehr Infos

Listing 5: shoutbox.html

<!DOCTYPE HTML PUBLIC 
"-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title> JSON-RPC Shoutbox Demo </title>

<script type="text/javascript" src="json.js"></script>
<script type="text/javascript" src="HTTP/Request.js"></script>
<script type="text/javascript">
var req, id = 0, since_msg = new Date();
// vor 5 Stunden
since_msg.setTime(new Date().getTime() - 1000 * 60 * 60 * 5);

function aktualisieren () {
var txt = document.getElementById('eingabe');
req = new HTTP.Request({
uri: '/perl/json',
postbody: JSON.stringify( {
method : 'update_box',
id : id++,
params : [ txt.value, since_msg.getTime() ]
}),
onSuccess: function (trans) {
var data;
try {
var data = eval('('+trans.responseText+')'); // JSON "parsen"
} catch(e) {
alert('eval: Ungültiges JSON: ' + e);
return;
}
// hier kann ggf. noch auf data.error geprüft werden
nachrichten(data.result);
}
});
txt.value = ''; // Text im Eingabefeld nach Versand löschen
txt.focus(); // Benutzer kann weiter Text eingeben
}

function nachrichten (result) {
var ausgabe = document.getElementById('ausgabe'); // DIV fuer Text
if (result && result.msgs) { // Antwort auf update_box
since_msg.setTime(result.now); // Neuen Zeitstempel merken
for (var i = 0; i < result.msgs.length; i++) {
var when = new Date();
when.setTime(result.msgs[i][0]); // Zeitstempel fuer Ausgabe
ausgabe.innerHTML += when + ': ' + result.msgs[i][1] + "<br>\n";
}
}
}

</script>
</head>
<body onload="aktualisieren();">
<noscript><h1>JavaScript ist deaktiviert,
wird jedoch benötigt!</h1>
</noscript>
<form action="#" onsubmit="aktualisieren();">
Ihr Text: <input type="text" id="eingabe" value="">
<input type="button" value="senden + empfangen"
onclick="aktualisieren();">
</form>
<div id="ausgabe"></div>
</body>
</html>

Hier kommt das vom OpenJSAN-Projekt bereitgestellte Modul HTTP-Request zur Verwendung, es dient der Kaschierung von Browser-Inkompatibilitäten im Zusammenhang mit XMLHttpRequest. Das Modul verspricht in einer späteren Version gar die transparente Unterstützung von älteren Browsern ohne diese Funktionen durch iframe und andere Workarounds. Die asynchron vom Server geschickten Antworten arbeitet eine anonyme Callback-Funktion ab. Interessant ist in diesem Zusammenhang, dass der responseText mit den in JSON serialisierten Objekten einfach an eval() übergeben wird und man anschließend direkt auf das (Javascript-)Objekt zugreifen kann.

JSON vermeidet konsequent die vielen, zur reinen Übergabe von Datenobjekten nicht benötigten Features von XML, die dessen Spezifikation auf mehr als 50 Seiten aufblähen. Die Lösung kann dank schon vorliegender Implementierungen in zahlreichen Programmiersprachen sofort für Ajax-Projekte eingesetzt werden. Erst wenn E4X eines Tages auf breiter Basis verfügbar sein sollte, dürfte ein Hauptgrund für JSONs Existenzberechtigung wegfallen .

Sven Neuhaus
programmiert seit elf Jahren Webapplikationen und ist einer der Software-Entwickler im Team von heise online.

[1] Stefan Mintert; Webentwicklung; Zwei Helden; Ajax: die nächste Generation der Web-Anwendungen; iX 11/2005, S. 56

[2] Stefan Mintert; Scriptsprachen; Ein E für ein X; E4X: Ecmascript/Javascript for XML; iX 11/2005, S. 60

Außerdem lesen Sie im aktuellen Heft Artikel zu Scriptsprachen (Ruby, Python) und ihrem professionellen Einsatz sowie zu Rhino, einem Stand-alone-Java-Interpreter. (hb)