XPath 3.0, XPath 2.0, XPath 1.0 / XPath-Funktionen / XPath: Sequenz-Funktionen / JSON

JSON

JSON

➪ JSON, die JavaScript-Object-Notation, ist ein textbasiertes Datenformat, das XML an Flexibilität am nächsten kommt. Zahlreiche REST-Webservices bieten neben XML auch JSON zur freien Auswahl an (auch CSV oder Text). Daher ist die Konvertierung von XML nach JSON eine häufige Aufgabe.

Auf dieser Seite:

JSON ist die Abkürzung von Java Script Object Notation. JSON bietet die Möglichkeit, JavaScript-Objekte in einem String abzubilden.

{ ... } deklariert ein Objekt.
{ "property":"value" } deklariert ein Feld "property" innerhalb eines Objekts und weist ihm den Wert "value" zu. Die "" besagen, dass es sich dabei um einen String handelt.
{ "zahl": 9 } deklariert ein Feld "zahl" innerhalb eines Objekts und weist ihm den Wert 9 zu. Da die Hochkommata "" fehlen, handelt es sich um eine Zahl.
{ "stimmt": true } deklariert ein Feld "stimmt" innerhanl eines Objekts und weist ihm den Wert true zu. Da die Hochkommata "" fehlen, handelt es sich um einen Boolean-Wert.
[ ... ] deklariert ein leeres Array.
[ {...}, {...} ] deklariert ein Array mit zwei Objekten.

Ein JSON-Beispiel sieht so aus:


[{"id":"1","name":"Holzflos","vorname":"Hugo"},
{"id":"2","name":"Sagblos","vorname":"Stefan"},
{"id":"8","name":"Rhodos","vorname":"Rudi"},
{"id":"15","name":"Kolos","vorname":"Karl"},
{"id":"19","name":"Lustlos","vorname":"Ludwig"},
{"id":"10","name":"Ruhelos","vorname":"Rita"},
{"id":"11","name":"Schlaflos","vorname":"Susi"},
{"id":"12","name":"Rielos","vorname":"Lotte"},
{"id":"13","name":"Muehelos","vorname":"Martin"},
{"id":"14","name":"Leinenlos","vorname":"Liane"}]

Bereits mit XSLT 1.0 ist es möglich, aus XML den vorstehenden JSON-String zu generieren.


<xsl:stylesheet version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="/">
<xsl:text>[</xsl:text>
  <xsl:for-each 
       select="Orte/Ort/Mensch[Gehalt &lt; 500]">
    <xsl:text>{</xsl:text>
    <xsl:for-each select="child::*[position() &lt; 4]">
      <xsl:text>"</xsl:text>
      <xsl:value-of select="local-name()"/>
      <xsl:text>":"</xsl:text>
      <xsl:value-of select="."/>
      <xsl:text>"</xsl:text>
      <xsl:if test="position() != last()">
      <xsl:text>,</xsl:text>
      </xsl:if>
    </xsl:for-each>    
    <xsl:text>}</xsl:text>
    <xsl:if test="position() != last()">
      <xsl:text>,
</xsl:text>
    </xsl:if>
  </xsl:for-each>  
<xsl:text>]</xsl:text>
  </xsl:template>
</xsl:stylesheet>

XML oder JSON?

Ein häufig bemängelter Nachteil von XML ist dessen Wortreichtum: JSON sieht wesentlich schlanker, damit ressourcenschonender aus. Ich kann das Argument nur bedingt nachvollziehen.

Vergleichen Sie ein XML-Dokument (in einem konkreten Beispiel 8871 Zeichen ohne Prolog), das so weit wie möglich nicht in der Element-, sondern in der Attributschreibweise geschrieben ist und in dem alle unnötigen Whitespaces entfernt sind, mit einem analogen JSON-Dokument desselben Inhalts (8511 Zeichen), so liegt der XML-"Overhead" in diesem Beispiel gerade einmal bei vier Prozent (360 Zeichen). Umfangreichere Studien belegen, dass gezipptes XML und JSON etwa dieselbe Dateigröße ergibt.

Wer freilich die Elementschreibweise wählt, wo jedes Start-Tag notwendigerweise ein Ende-Tag haben muss, und obendrein eine für das menschliche Auge leicht lesbare Darstellung mit zahlreichen Whitespaces nutzt, der kommt schnell auf den mehr als doppelten Umfang.

Ein zweites Argument besagt, dass JSON-Strings unmittelbar in Objekte konvertiert werden können. Dieses Argument stimmt für Sprachen wie JavaScript und PHP, die ein dynamisches Objektmodell aufweisen: Hier können Objekten zur Laufzeit Properties hinzugefügt werden, ohne dass dafür eine Klasse vorhanden sein muss.

Die Aussage stimmt jedoch nicht bei Sprachen wie Java, C++, C#.NET, denen ein Template-basiertes Objektmodell zugrunde liegt: Bevor ein Objekt instanziiert werden kann, muss die Klasse bereits vorliegen. Daher ist die Objektkonvertierung von JSON-Strings in Java-, C++, C#-Objekte keineswegs eine triviale Sache. Siehe dazu weiter unten.

XML bietet den großen Vorteil, präzise versionierte Namensräume mit klar dokumentierbaren Definitionen der Einzelbegriffe vorhalten zu können. JSON bietet hier im Draft das Konzept des lightweight Linked Data format (JSON-LD) an.

In XML-Schema ist jede komplexe Struktur mit jedem einzelnen Datenfeld hinsichtlich seines Namensraums und Datentyps klar definierbar. Beides bietet die Grundlage der Validierung der Einzeldokumente und der zuverlässigen Programmierung der Konvertierungslogiken. In JSON ist derzeit JSON-Schema (http://json-schema.org/) im Draft-Modus verfügbar. Dessen Autoren haben viel Arbeit investiert, einen Entwurf für JSON Schema Core, JSON Schema Validation, JSON Hyper-Schema sowie relative JSON Pointers vorzulegen.

Richtig ist die Notwendigkeit sauberer Struktur- und Typdefinitionen in grundsätzlich allen Formaten, auch bei XML und JSON, denn hier wie dort steht der Progammierer vor der Aufgabe, eine hohe Datenqualität absichern zu müssen, um Logiken schreiben zu können, die einen Output ohne Informationsverlust garantieren. Die Tools stehen also zur Verfügung: Werden sie auch genutzt?

In XML-Technologien kommt alles aus einer Hand: Detaildokumente, Instrumente zu deren Strukturdefinition, Dokumentation und Validierung, Transformation, Testing.

JSON-Auswertung mit JavaScript

JSON findet beispielsweise Anwendung im Datenaustausch zwischen Client und Server im Rahmen dynamischer Webseiten (client-/browserseitige DOM-Programmierung, häufig im Zusammenhang mit AJAX). Der Vorzug liegt darin, dass der JavaScript-Client in der Lage ist, den JSON-String umgehend in ein Array mehrerer JavaScript-Objekte zu konvertieren, die im weiteren Programmverlauf unmittelbar ausgewertet werden können.

Das folgende Beispiel zeigt, wie JavaScript-Code in einer HTML-Seite wirken kann.


<html>
<head>
  <script language="javascript" type="text/javascript">
// <!CDATA[
  var myarray= [{"id":"1","name":"Holzflos","vorname":"Hugo"},
  // ... Array wie oben ...
  {"id":"14","name":"Leinenlos","vorname":"Liane"}];
  function Button1_onclick() {
   try {
    for (var i = 0; i < myarray.length; i++) {
     var r = myarray[i];
     document.writeln("Mensch: " 
                      + r.vorname 
                      + " " 
                      + r.name 
                      + "<br/>");
    }
    // nehme eines der Obekte aus dem Array
    var obj3 = myarray[2];
    document.writeln("obj3: " 
                     + obj3.vorname 
                     + " " 
                     + obj3.name 
                     + "<br/>");
    // hier werden die Felder von obj3 überschrieben
    obj3.vorname="Resi";
    obj3.name="Denzschlos";
    document.writeln("obj3: " 
                     + obj3.vorname 
                     + " " 
                     + obj3.name 
                     + "<br/>");
    }
    catch (e) {
     document.writeln("Fehler: " + e);
   }
   return true;
  }
// ]]>
  </script>
</head>
<body>
  <p>
    <input  id="Button1" 
            type="button" 
            value="click here" 
            onclick="return Button1_onclick()"
     />
  </p>
</body>
</html>

Bitte beachten Sie, dass in der Zeile "var obj3 = myarray[2];" ein Objekt aus dem Array gewählt wird, dessen Felder in den folgenden Zeilen neu zugewiesen werden. Das ist bemerkenswert, weil das nicht in allen Programmiersprachen so einfach möglich ist.


obj3.vorname="Resi";
obj3.name="Denzschlos";

Die Ausgabe im Browser sieht so aus:


Mensch: Hugo Holzflos
Mensch: Stefan Sagblos
Mensch: Rudi Rhodos
Mensch: Karl Kolos
Mensch: Ludwig Lustlos
Mensch: Rita Ruhelos
Mensch: Susi Schlaflos
Mensch: Lotte Rielos
Mensch: Martin Muehelos
Mensch: Liane Leinenlos
obj3: Rudi Rhodos
obj3: Resi Denzschlos

JSON-Auswertung mit JavaScript in XSL

Neben den neueren Standard-Verfahren , und bieten spezielle Prozessoren auch die Möglichkeit, JavaScript direkt zu verarbeiten und damit auf JSON zuzugreifen.


<xsl:stylesheet version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
     xmlns:jscript="http://jscript.org">
<xsl:output 
     method="xml" 
     omit-xml-declaration="yes" 
     indent="yes" 
     exclude-result-prefixes="msxsl jscript"/>
<msxsl:script 
       language="JavaScript" 
       implements-prefix="jscript">
<![CDATA[
var vjson=[ 
            {"NN":"Rielos", "VN":"Lotte"}, 
            {"NN":"Holzflos", "VN":"Hugo"}, 
            {"NN":"Denzschlos", "VN":"Resi"}
          ];
public function getJSONasString()
{
  // Deklariere eine Stringvariable 
  var verg="";
  // Schleife über alle Objekte im Array
  for(var i=0; i<vjson.length; i++){
      // nimm jeweils aktuelles Objekt aus dem Array
      var vobj = vjson[i];
      // verkette Informationen aus vobj
      // in die Stringvariable verg
      verg = verg + vobj.VN + " " + vobj.NN ;
      // Falls noch weitere Objekte kommen,
      // verkette verg mit "|"
      if(i<vjson.length-1) verg = verg + "|";
  }
  return verg;
}
]]>
</msxsl:script>
  <xsl:template match="/">  
  <root>  
  <xsl:value-of select="jscript:getJSONasString()"/>
  </root>
  </xsl:template>
</xsl:stylesheet>

Das generierte XML-Dokument sieht dann folgendermaßen aus:


<root>Lotte Rielos|Hugo Holzflos|Resi Denzschlos</root>

Die beschriebene Funktion getJSONasString zeigt, dass der Entwickler auch hier um ein klares Verständnis des jeweiligen Objekts nicht herumkommt. Um JSON auswerten zu können, muss er wissen, dass ein Objekt ein bestimmtes Property hat.

JSON-Auswertung mit C#.NET

Neben JavaScript können auch andere leistungsfähige Programmiersprachen den JSON-Code auszuwerten. Oberflächlich betrachtet, scheint es in C#.NET eine syntaktische Ähnlichkeit zu JSON zu geben, die das folgende Beispiel in C#.NET problemlos laufen lässt:


var myobj = new { Nachname = "Hilflos", Vorname = "Hugo" };
Console.WriteLine("{0} {1}", myobj.Vorname, myobj.Nachname);

Auch die Abarbeitung sämtlicher Properties in myobj funktioniert:


foreach (var xx in myobj.GetType().GetProperties()) 
{ 
  Console.WriteLine("{0} -> {1}", xx.Name, 
                     xx.GetGetMethod().Invoke(myobj, null));
}

Das Ergebnis sieht aus Konsole so aus:


Hugo Hilflos
Nachname -> Hilflos
Vorname -> Hugo

Aber ganz so einfach ist es nicht. Schon der Versuch, ein Feld in myobj zu überschreiben, geht schief. Die Fehlermeldung ist aufschlussreich: Es wurde ein anonymes Objekt generiert, für dessen Felder eine Zuweisung nicht möglich ist.


//myobj.Nachname = "Ruhelos";      
//Fehler CS0200 Für die Eigenschaft oder den Indexer 
//"<anonymous type: string Nachname, string Vorname>.Nachname" 
//ist eine Zuweisung nicht möglich.
//Sie sind schreibgeschützt.

Hinter diesen abweichenden Verhaltensweisen stehen unterschiedliche Objektmodelle in JavaScript einerseits und "klassischen" objektorientierten Sprachen wie Java oder C#.NET andererseits.

In der "klassischen" objektorientierten Programmierung hat sich bewährt, die Datenstruktur bzw. Funktionalität von Objekten durch Templates, etwa in Form von Klassen, eindeutig zu beschreiben. Jedes Objekt als Instanz einer Klasse weist somit alle Felder, Methoden, Events etc. auf, die in jener Klasse definiert ist. Dieses Vorgehen verbindet sich in der Regel mit Datenkapselung, Vererbung, Polymorphie, diversen Patterns, Interfaces, early bzw. late binding sowie anderen Konzepten der Objektorientierung.

Alternativ zur Template-basierten Typdeklaration besteht in einigen Sprachen (JavaScript, auch PHP) die Möglichkeit, Objekten zur Laufzeit Properties, Funktionen oder Arrays dynamisch zuzuweisen. So können einer Objektvariable mensch jederzeit Eigenschaften wie Vorname und Nachname im Sinn von public Stringvariablen mit Werten zugewiesen, geändert und wieder ausgewertet werden, ohne dass jene Felder in irgendeinem Template vorher definiert sein müssten.

Die Initialisierung von


var myobj = new { Nachname = "Hilflos", Vorname = "Hugo" };

in C#.NET ist also nicht ganz trivial. Vor diesem Hintergrund ist auch die obige Fehlermeldung zu verstehen. Es wird ein anonymes Objekt generiert, also die Instanz einer anonymen Klasse, die nicht von Programmierern als eigenständige Komponente definiert, sondern zur Laufzeit von der Runtime automatisch generiert wird.

So erzeugt der Compiler in C#.NET bei Aufruf von new zusammen mit einem Objektinitialisierer automatisch einen Namen für den anonymen Typen, der sich bei Neukompilierung ändern kann. Mit "Console.WriteLine(myobj.GetType().Name);" kann man sich den Namen dieser Klasse auch anzeigen lassen, bei mir hiess es


<>f__AnonymousType0`2

In den generierten Klassen sind die dynamisch hinzugefügten Eigenschaften schreibgeschützt. Sie müssen also mit einem parametrisierten Konstruktor zugewiesen werden und können daher nur gelesen werden. Jede nachträgliche Änderung oder Erweiterung um zusätzliche Eigenschaften wird vom Compiler ungnädig angemeckert. Das erklärt die Fehlermeldung bei dem Wunsch, einen Feldinhalt ändern zu wollen.

Dieses Verhalten wird bei der Reflection-Analyse des Objekts myobj bestätigt. Es gibt nur GET-Properties bzw. get_-Methoden. Beim Versuch, das SET-Property aufzurufen, kommt eine Exception: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt..


foreach(System.Reflection.MethodInfo mi 
        in myobj.GetType().GetMethods())
{
    Console.WriteLine("Methode: {0}", mi.Name);
}
foreach (System.Reflection.PropertyInfo pi 
         in myobj.GetType().GetProperties())
{
    Console.WriteLine("Property: {0}", pi.Name);
    Console.WriteLine("Property GET: {0}", pi.GetMethod.Name);
    //Console.WriteLine("Property SET: {0}", pi.SetMethod.Name);    
}
foreach (System.Reflection.ConstructorInfo ci 
         in myobj.GetType().GetConstructors())
{
    Console.WriteLine("Constructor: {0}", ci.Name);
}

Das Ergebnis des vorstehenden Aufrufs finden Sie hier:


Methode: get_Nachname
Methode: get_Vorname
Methode: Equals
Methode: GetHashCode
Methode: ToString
Methode: GetType
Property: Nachname
Property GET: get_Nachname
Property: Vorname
Property GET: get_Vorname
Constructor: .ctor

Der JavaScriptSerializer

Die vorbeschriebene Problematik sollte Ihnen bewusst sein, wenn Sie versuchen, JSON-Strings in C#.NET-Objekte zu deserialisieren. Als hilfreiche Klasse steht JavaScriptSerializer zur Verfügung, die sich im Namespace System.Web.Script.Serialization befindet. Falls Sie diesen Namespace nicht unter den Verweisen finden sollten, binden Sie System.Web.Extensions ein.

Die Dokumentation hierzu finden Sie unter

Um mit dem JavaScriptSerializer arbeiten zu können, ist eine Klasse erforderlich, deren public-Felder identisch (Case-sensitiv!) sein müssen mit jenen Feldern, die im JSON-String benannt werden. Ist das nicht der Fall, gibt's eine Fehlermeldung, z.B. 'Person' enthält keine Definition für 'name', und es konnte keine name-Erweiterungsmethode gefunden werden, die ein erstes Argument vom Typ 'Person' akzeptiert.


class Person
{
    public int id;
    public string vorname;
    public string name;
}

Mit diesen Vorbereitungen sind Sie in der Lage, aus einem JSON-String elegant C#.NET-Objekte zu generieren.


using System.Web.Script.Serialization;
static void JSONTestArray()
{
  string json = "[
    {'id':'1','name':'Holzflos','vorname':'Hugo'},
    { 'id':'2','name':'Sagblos','vorname':'Stefan'},
    { 'id':'8','name':'Rhodos','vorname':'Rudi'}]";
  JavaScriptSerializer js = new JavaScriptSerializer();
  Person[] persons = js.Deserialize<Person[]>(json);
  foreach(Person p in persons)
  {
    Console.WriteLine("{0} {1}", p.vorname, p.name);
  }
}

Der Aufruf lautet:


Hugo Holzflos
Stefan Sagblos
Rudi Rhodos

In Java gibt es eine ganze Anzahl spezieller Klassen, die JSON-Strings in Java-Objekte deserialisieren können. Begriffe wie GSON oder JACKSON machen hier die Runde.

wg / 7. Juli 2018



Fragen? Anmerkungen? Tipps?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/JSON.html