JSON
Database
XSLT
Børre Stenseth
Moduler>AJAX>Generelt>JSON

JSON

Hva
En måte å pakke data på

JSON er, slik vi skal betrakte det, en måte å pakke data på slik at de blir enkle å bearbeide på klientsiden. Hvis vi har AJAX som referanseramme betyr det at vi skal bruke JSON som format når vi returnerer svar på en forespørsel, en XMLHttpRequest. Vineksemplene nedenfor illustrerer dette, med ulike datakilder på tjeneren

JSON

Grunnlaget for JSON er at vi kan tolke tekstformat som Javascriptobjekter i et javaskript. Vi kan f.eks. gjøre slik:

var T='{"navn":"Ole","adresse":"Halden"}'
var person=eval("("+T+")");
alert(person.navn);	

Nøkkelen er eval-funksjonen som tolker teksten som evalueres som Javascriptkode. Siden Javascrupt er et interpretert språk er dette en vanlig og lett implementerbar mekanisme.

Vi kan også gjøre slik:

var T='{"personer":[{"navn":"Ole","adresse":"Halden"},{"navn":"Jens","adresse":"Moss"}]}'
var p=eval("("+T+")");
// var p=JSON.parse(T);
var person= p.personer;
alert(person[1].navn);	

Full forklaring av syntaksen finnes på [1] . Vi kan nøye oss med å konstatere at vi kan pakke attributter i objekter ved å angi "attributtnavn":"attributtverdi". Vi kan lage en kommaseparert liste av slike par. Videre ser vi at vi kan lage arrays med [].

Vi skal kunne forvente at browsere supporter et JSON-objekt. Dette skal ha to metoder:

  • parse() - som gjør det samme som den gamle metode eval
  • stringify() - som lage json av et objekt

Anta følgende Javascript:

var DATA='{"personer":[{"navn":"Ole","adresse":"Halden"},{"navn":"Jens","adresse":"Moss"}]}';
var obj=null;
var personer=null;

function extractNames(){
    if(obj==null){
        obj=JSON.parse(DATA);
        personer=obj.personer;
    }
    var s='';
    for(ix=0;ix<personer.length;ix++){
        s+=personer[ix].navn+'\n';
    }
    alert(s);
}
function changeandmakeJson(){
    if(obj==null){
        obj=JSON.parse(DATA);
        personer=obj.personer;
    }
    personer[0].navn="kristoffer";
    newObj={}
    newObj.personer=personer;
    S=JSON.stringify(newObj);
    alert(S);
}

test funksjonene her:

  • extractNames
  • changeandmakeJson

AJAX

Det interessante med denne angrepsvinkelen er altså at vi får svært enkel koding på klientsiden. Vi får tilgang til objekter og kan plukke attributter etter ønske.

Hvis vi ser dette i en AJAX situasjon, har vi følgende:

json-1
Produksjon av JSON

Hvorvidt bruk av JSON er en effektiv og/eller enkel metode avhenger av hvordan dataene våre er representerte på tjeneren, og det avhenger av hva vi må gjøre med dem på klienten. Det er klart at dersom vi skal overføre en hel fil som er ferdiglagd som et HTML-fragment og vi skal plassere dette fragmentet samlet på klienten er det mest effektive å bruke ren tekst slik:

	document.getElementById("target").innerHTML= myRequest.responseText

Dersom teksten som kommer tilbake består av mange "deler" som skal plasseres på forskjellige steder på siden stiller saken seg litt annerledes. Både kommaseparert tekst, XML og JSON kan være alternativer.

På tjenersiden vil mye avhenge av dataformatet og programmeringsverktøyet vi har til rådighet. Som regel kan vi vel regne med at programmeringsverktøyet på tjeneren er bedre og raskere enn Javascript. Dette skulle i så fall tale for at så mye som mulig gjøres klart på tjeneren.

På den annen side er det ofte slik at bruken av de data vi sender tilbake vil endre seg i forskjellige situasjoner avhengig av bruksønsker og designbeslutninger. Det er kanskje en fordel å lage en generell tjenerfunksjon og så lage spesielle klientløsninger etter ønske. Dette taler for et fleksibelt format som peker i retning av JSON, eller XML. De to, JSON og XML, kan langt på vei sidestilles prinsippielt, men JSON gjør trolig lettere å programmere på klienten.

Nedenfor skal vi gå gjennom noen eksempler på pakking av data på tjenersiden og bruk på klientsiden. Vi skal bruke vindataene som gjennomgangseksempel. Disse er beskrevet i modulen Noen datasett. Vi skal bruke både XML-versjonen og databaseversjonen. Vi skal i alle tilfellene lage en klientside som gir oss mulighet for å velge vin av en bestemt type (rød, hvit, rose, musserende) og fra et bestemt land. Vi skal i alle løsningen bruke JSON, selv om et par av eksemplene åpenbart kunne løses med lettere med tekst og alle kunne løses med XML.

Eksempler

I alle eksemplene bruker vi et JSON format som ser slik ut, uten linjeskift:

	{"diagnose":"OK",
	 "overskrift":"Rødvin fra Island",
	"viner":[
	{"navn":"Masi","terning":"4","beskrivelse":"OK"},
	{"navn":"Perequita","terning":"5","beskrivelse":"God"},
	...
	]}

Altså en diagnose som sier om dataoppslaget har gått bra eller ikke, en passelig overskrift pluss en array med vinobjekter.

I alle eksemplene bruker vi samme javascript på klienten:

_script1.js

Den interessante koden biten er funksjonen processJSON:

function processJSON(S,targetId)
{
    var p = null;
    try{
        p = eval("(" + S + ")");
    }   
    catch(ex)
    {
        document.getElementById(targetId).innerHTML = 
           '<p>Feil i dataformatet: ' + ex.message + '</p>';
        return;
    }
    var dia=p.diagnose;
    if(dia.indexOf('OK')==0)
    {
        var vinlist=p.viner;
        // manual count due to diff between browsers on empty fields
        var count=0;
        var S='';
        for(var ix=0;ix<vinlist.length;ix++)
        {
            try
            {
              S+='<p>';
              S += '<span style="font-size:20px;color:blue;margin-right:20px">' +
                   vinlist[ix].terning+'</span>';
              S+=vinlist[ix].navn+'<br/>';
              S+=vinlist[ix].beskrivelse+'<br/>';
              S+='</p>';
              count++;
            }
            catch(e)
            {
            }
        }
        var T='<h1>'+count+' '+p.overskrift+'</h1>';
        document.getElementById(targetId).innerHTML = T + S;
    }
    else
    {
        document.getElementById(targetId).innerHTML = '<pre>' + dia + '</pre>';
    }
}

En typisk tekst som overføres og parses ser slik ut: typisk string

Eksempel 1

Vi bruker XML-data på tjeneren, skriver DOM-code som identifiserer de vinene vi er interesserte i og produserer JSON. På klientsiden lager vi HTML av JSON-formatet.

Du kan teste herhttp://donau.hiof.no/borres/dn/jsondemo/page1.html

Koden på tjeneren ser slik ut:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Xml.XPath;
using System.Text;
using System.Collections.Specialized;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            // pick parameters
            NameValueCollection ps = this.Request.Params;            
            String country = ps.Get("land").Trim();
            String winetype = ps.Get("vintype").Trim();
            
            
            string header="Noen viner";
            switch (winetype)
            {
                case "red": header = 
                    String.Format("R�de viner fra {0}", country); break;
                case "white": header = 
                    String.Format("Hvite viner fra {0}", country); break;
                case "rose": header = 
                    String.Format("Ros� viner fra {0}", country); break;
                default: header = 
                    String.Format("Musserende viner fra {0}", country); break;
            }
            // access XML-data
            // will be faster with local vinfile
            XmlDocument doc = new XmlDocument();
            XmlReader reader = 
                XmlReader.Create("http://www.ia.hiof.no/~borres/commondata/vin/viner.xml");
            doc.Load(reader);
            // select what we want
            String xp = 
                String.Format("//wine[country='{0}' and type='{1}']", country, winetype);
            XmlNodeList list = doc.SelectNodes(xp);
            // wrap it as JSON
            StringBuilder sb = new StringBuilder(100000);
            String oneWine = 
@"{3}""navn"":""{0}"",""terning"":""{1}"",""beskrivelse"":""{2}""{4},";
            foreach (XmlNode node in list)
            {
                // escaping possible " in name:
                String name = 
                    ((XmlElement)node).GetElementsByTagName("name")[0].InnerText.Replace("\"","\\\"");
                String dice = 
                    ((XmlElement)node).GetElementsByTagName("dice")[0].InnerText;
                String desc = 
                    ((XmlElement)node).GetElementsByTagName("description")[0].InnerText;
                sb.Append(String.Format(oneWine, name, dice, desc,"{","}"));
            }
            // return it
            String ret = sb.ToString();
            ret = @"{""diagnose"":""OK"",""overskrift"":"""+header+@""",""viner"":[" + ret + "]}";
            this.Response.Write(ret);
        }
        catch (Exception ex1)
        {
            this.Response.Write(@"{""diagnose"":""Beklager feil. Mail deres webforhandler snarest""}");
        }
        finally
        {
            this.Response.Flush();
        }
    }
}

Eksempel 2

Vi bruker XML-data på tjeneren, skriver en XSLT-transformasjon som lager JSON direkte. På klientsiden lager vi HTML av JSON-formatet.

Du kan teste herhttp://donau.hiof.no/borres/dn/jsondemo/page2.html

Koden på tjeneren ser slik ut:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Text;
using System.Collections.Specialized;
using System.Net;
public partial class process2 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            // pick parameters
            NameValueCollection ps = this.Request.Params;
            String country = ps.Get("land").Trim();
            String winetype = ps.Get("vintype").Trim();
            // make header
            string header = "Noen viner";
            switch (winetype)
            {
                case "red": 
                    header = String.Format("R�de viner fra {0}", country); break;
                case "white": 
                    header = String.Format("Hvite viner fra {0}", country); break;
                case "rose": 
                    header = String.Format("Ros� viner fra {0}", country); break;
                default: header = 
                    String.Format("Musserende viner fra {0}", country); break;
            }
            // where is xml-file and xslt-file
            String XmlSource = 
                "http://www.ia.hiof.no/~borres/commondata/vin/viner.xml";
            String XsltSource = 
                "http://donau.hiof.no/borres/dn/jsondemo/trans2.xsl";
            
            // prepare the transformation with arguments
            XslCompiledTransform xslt = new XslCompiledTransform();
            xslt.Load(XsltSource);
            XsltArgumentList argList = new XsltArgumentList();
            argList.AddParam("theCountry", "", country);
            argList.AddParam("theType", "", winetype);
            // Create a StringWriter to produce to
            StringBuilder sb=new StringBuilder(100000);
            System.IO.StringWriter swriter= new System.IO.StringWriter(sb);
            
            // The easy way:
            // xslt.Transform(new System.Xml.XPath.XPathDocument(XmlSource), argList, swriter);
            // but we are doing a hack to escape element "'s
            String xmlstring = new WebClient().DownloadString(new Uri(XmlSource));
            xmlstring = 
                xmlstring.Substring(0,50)+xmlstring.Substring(50).Replace("\"", "\\\"");
            XmlReader xr=XmlReader.Create(new System.IO.StringReader(xmlstring));            
            // Transform the file
            xslt.Transform(xr, argList, swriter);
            
            swriter.Flush();
            swriter.Close();
            
            // return it
            String ret = sb.ToString();
            ret = @"{""diagnose"":""OK"",""overskrift"":""" + header + @""","+ ret+"}";
            this.Response.Write(ret);
        }
        catch (Exception ex1)
        {
            this.Response.Write(@"{""diagnose"":""Beklager feil. ""}");
        }
        finally
        {
            this.Response.Flush();
        }
    }
}

Transformasjonen er slik:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
  <xsl:param name="theCountry" select="'Frankrike'"/>
  <xsl:param name="theType" select="'red'"/>
  <xsl:template match="/">
    "viner":[
    <xsl:apply-templates select="//wine[country=$theCountry and type=$theType]">
      <xsl:sort order="descending" select="dice"/>
    </xsl:apply-templates>
    ]
  </xsl:template>
  <xsl:template match="//wine">
    {"terning":"<xsl:value-of select="dice"/>",
     "navn":"<xsl:value-of select="name"/>",
    "beskrivelse":"<xsl:value-of select="description"/>"},
  </xsl:template>
</xsl:stylesheet> 

Eksempel 3

Vi bruker en database tjeneren og lager JSON basert på de recordene vi plukker opp med en SQL-setning. På klientsiden lager vi HTML av JSON-formatet.

Du kan teste herhttp://donau.hiof.no/borres/dn/jsondemo/page3.html

Koden på tjeneren ser slik ut:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
using System.Data.OleDb;
using System.Collections.Specialized;
public partial class process3 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
       
        // connection string 
        String connectString =
                    @"SERVER=donau.hiof.no;
                    DATABASE=bs;
                    UID=bs2;
                    PASSWORD=bs2;
                    PROVIDER=SQLOLEDB";
        //use OLE
        OleDbConnection con = null;
        OleDbDataReader myReader = null;
        try
        {
            // pick parameters
            NameValueCollection ps = this.Request.Params;
            String country = ps.Get("land").Trim();
            String winetype = ps.Get("vintype").Trim();
            // set header
            string header = "Noen viner";
            switch (winetype)
            {
                case "red": 
                    header = String.Format("R�de viner fra {0}", country); break;
                case "white": 
                    header = String.Format("Hvite viner fra {0}", country); break;
                case "rose": 
                    header = String.Format("Ros� viner fra {0}", country); break;
                default: 
                    header = String.Format("Musserende viner fra {0}", country); break;
            }
            // make sql
            string myQuery = 
                String.Format(@"select name,dice,description from wines 
                                            where country='{0}' and type='{1}'
                                            order by dice desc;", 
                                            country, winetype);
            
            con = new OleDbConnection(connectString);
            con.Open();
            
            // execute the query
            OleDbCommand myCommand = new OleDbCommand(myQuery, con);
            myReader = myCommand.ExecuteReader();
            String oneWine = 
@"{3}""navn"":""{0}"",""terning"":""{1}"",""beskrivelse"":""{2}""{4},";
            StringBuilder sb = new StringBuilder(10000);
            while (myReader.Read())
            {
                String name = myReader.GetString(0).Replace("\"", "\\\""); 
                String dice = Convert.ToString(myReader.GetByte(1));
                String desc = myReader.GetString(2);
                sb.Append(String.Format(oneWine, name, dice, desc, "{", "}"));
            }
            // skim of trailing , - for IE, and return it
            String ret = sb.ToString();
            ret = ret.Substring(0, ret.Length - 1);
            ret = 
@"{""diagnose"":""OK"",""overskrift"":""" + header + @""",""viner"":[" + sb.ToString() + "]}";
            this.Response.Write(ret);
        }
        catch (Exception ex)
        {
            this.Response.Write(@"{""diagnose"":""Beklager.""}");
        }
        finally
        {
            if (con != null)
                con.Close();
            if (myReader != null)
                myReader.Close();
        }
    }
}
Referanser
  1. JSON(JavaScript Object Notation)json.orgwww.json.org/14-03-2010
  1. JSONLintarc90jsonlint.com/02-11-2012
  1. Json Parser Onlinejson.parser.online.fr/02-11-2012
Vedlikehold
B.Stenseth, revidert oktober 2009
(Velkommen) Moduler>AJAX>Generelt>JSON (Andre domener)