JSON
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:
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.
Vindata
I alle eksemplene bruker vi et JSON format som ser slik ut, vist med 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. Merk at vi pakker dataene uten linjeskift slik at de kan leses som en sammenhengende string i Javascript.
I alle eksemplene bruker vi samme javascript på klienten:
Den interessante koden biten er funksjonen processJSON:
function processJSON(S,targetId) { var p = null; //alert(S); try{ p = eval("(" + S + ")"); } catch(ex) { document.getElementById('result').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
Vin fra XML
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.
Koden på tjeneren ser slik ut:
#! /usr/bin/python # -*- coding: utf-8 -*- import xml.dom.minidom,urllib,cgi #------------------------------------------------------------- # Receive: land and vintype # Return JSON format for selected wines #{"diagnose":"OK", # "overskrift":"Hvite viner fra Italia", # "viner":[ # {"navn":"A","terning":"4","beskrivelse":"grei"}, # {"navn":"B","terning":"4","beskrivelse":"ok"} # ] #} # B. Stenseth 2009 #------------------------------------------------------------- #------------------------------------------------------ # datasource XMLSOURCE='http://www.it.hiof.no/~borres/commondata/vin/viner.xml' #------------------------------------------------------ # string for one wine wine="""{"navn":"%s","terning":"%s","beskrivelse":"%s"}""" #----------------------- # downloading an xml-file as string def loadUrl(urladdress): try: f=urllib.urlopen(urladdress) s=f.read() f.close() return s except: return None #----------------------- # collect all text in a node def getText(nodelist): rc = '' for node in nodelist: if node.nodeType == node.TEXT_NODE: #t=node.data.encode('ISO-8859-1') t=node.data.encode('utf-8') rc += t return rc #-------------------------- # prepare one wine def doOneWine(vin,theCountry,theType): land=getText(vin.getElementsByTagName('country')[0].childNodes) if land != theCountry: return '' vintype=getText(vin.getElementsByTagName('type')[0].childNodes) if vintype != theType: return '' navn=getText(vin.getElementsByTagName('name')[0].childNodes) navn=navn.replace('"','\\"') terning=getText(vin.getElementsByTagName('dice')[0].childNodes) beskrivelse=getText(vin.getElementsByTagName('description')[0].childNodes) return wine%(navn,str(terning),beskrivelse) #-------------------------- # buld tree and viner string def doAll(theCountry,theType): try: vintxt=loadUrl(XMLSOURCE) if vintxt==None: return None vindom = xml.dom.minidom.parseString(vintxt) vinlist=vindom.getElementsByTagName('wine') result='' for vin in vinlist: t=doOneWine(vin,theCountry,theType) if len(t)>0: result=result+t+',' return result except: return None #---------------------- form=cgi.FieldStorage() print 'Content-type: text/html; charset=utf-8\n' land='Ukjent' vintype='Ukjent' dia='"diagnose":"BAD"' if form.has_key('land'): land=form['land'].value if form.has_key('vintype'): vintype=form['vintype'].value if vintype=='red': header='"overskrift":"Røde viner fra %s"'%land elif vintype=='white': header='"overskrift":"Hvite viner fra %s"'%land elif vintype=='rose': header='"overskrift":"Roseviner fra %s"'%land elif vintype=='sparkling': header='"overskrift":"Musserende viner fra %s"'%land else: header='"overskrift":"Ukjent vintype"' viner=doAll(land,vintype) if viner !=None: dia='"diagnose":"OK"' else: viner='xx' retstr='{'+dia+','+header+',"viner":['+viner[:-1]+']}' print retstr
VIN fra XML via XSLT
Vi bruker XML-data på tjeneren, skriver en XSLT-transformasjon som lager JSON direkte. På klientsiden lager vi HTML av JSON-formatet.
Koden på tjeneren ser slik ut:
#! /usr/bin/python # -*- coding: utf-8 -*- import xml.dom.minidom,urllib,cgi from lxml import etree import cgitb; cgitb.enable() #------------------------------------------------------------- # Receive: land and vintype # Return JSON format for selected wines #{"diagnose":"OK", # "overskrift":"Hvite viner fra Italia", # "viner":[ # {"navn":"A","terning":"4","beskrivelse":"grei"}, # {"navn":"B","terning":"4","beskrivelse":"ok"} # ] #} # B. Stenseth 2009 #------------------------------------------------------------- #------------------------------------------------------ # data and transform source XMLSOURCE='http://www.it.hiof.no/~borres/commondata/vin/viner.xml' XSLSOURCE='http://www.it.hiof.no/~borres/cgi-bin/json/vin/trans2.xsl' #------------------------------------------------------ # string for one wine wine="""{"navn":"%s","terning":"%s","beskrivelse":"%s"}""" #----------------------- # helper to load a text file def getTextFile(filename): try: file=open(filename,'r') intext=file.read() file.close() return intext except: return None #----------------------- # downloading an xml-file as string def loadUrl(urladdress): try: f=urllib.urlopen(urladdress) s=f.read() f.close() return s except: return None #----------------------- def doAll(land,vintype): xsltstr=loadUrl(XSLSOURCE) xmlstr=loadUrl(XMLSOURCE) xmlstr=xmlstr.strip() # hack to keep " parts=xmlstr.split('\n',1) xmlstr=parts[0]+'\n'+parts[1].replace('"','\\"') xmlTree=etree.XML(xmlstr) xsltTree=etree.XML(xsltstr) transform=etree.XSLT(xsltTree) c=etree.XSLT.strparam(land) t=etree.XSLT.strparam(vintype) resultTree=transform(xmlTree,theCountry=c,theType=t) return str(resultTree) #---------------------- form=cgi.FieldStorage() print 'Content-type: text/html; charset=utf-8\n' land='Portugal' vintype='red' dia='"diagnose":"BAD"' if form.has_key('land'): land=form['land'].value if form.has_key('vintype'): vintype=form['vintype'].value if vintype=='red': header='"overskrift":"Røde viner fra %s"'%land elif vintype=='white': header='"overskrift":"Hvite viner fra %s"'%land elif vintype=='rose': header='"overskrift":"Roseviner fra %s"'%land elif vintype=='sparkling': header='"overskrift":"Musserende viner fra %s"'%land else: header='"overskrift":"Ukjent vintype"' viner=doAll(land,vintype) if len(viner) > 0: dia='"diagnose":"OK"' else: viner='xx' retstr='{'+dia+','+header+','+viner+'}' print retstr
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>
Vin fra database
Vi bruker en database på tjeneren og lager JSON basert på de recordene vi plukker opp med en SQL-setning. På klientsiden lager vi HTML av JSON-formatet.
Koden på tjeneren ser slik ut:
#! /usr/bin/python2.5 # -*- coding: utf-8 -*- import MySQLdb,cgi,urllib #------------------------------------------------------------- # Receive: land and vintype # Return JSON format for selected wines #{"diagnose":"OK", # "overskrift":"Hvite viner fra Italia", # "viner":[ # {"navn":"A","terning":"4","beskrivelse":"grei"}, # {"navn":"B","terning":"4","beskrivelse":"ok"} # ] #} # Datasource: mySQL base as described in # http://www.ia.hiof.no/~borres/ml/datasett/p-datasett.html # B. Stenseth 2007 #------------------------------------------------------------- #------------------------------------------------- # connect and execute a sql-request #------------------------------------------------- def connectAndExecute(sql): try: myBase=MySQLdb.connect(host='frigg.hiof.no', user='student', passwd='student', db='vin') myTab=myBase.cursor() myTab.execute(sql) myBase.close() return myTab.fetchall() except MySQLdb.Error, e: print "Error %d: %s" % (e.args[0], e.args[1]) return None #------------------------------------------------- # do the job #------------------------------------------------- def doAll(land,vintype): resultTxt='' sql="""select name,dice,description from wines where country='%s' and type='%s' order by dice desc;""" result=connectAndExecute(sql%(land,vintype)) if result==None: return None wine="""{"navn":"%s","terning":"%s","beskrivelse":"%s"}""" for row in result: navn=str(row[0]) navn=navn.replace('"','\\"') terning=str(row[1]) beskrivelse=str(row[2]) resultTxt=resultTxt+wine%(navn,terning,beskrivelse)+',' return resultTxt #---------------------- form=cgi.FieldStorage() print 'Content-type: text/html; charset=utf-8\n' land='Ukjent' vintype='Ukjent' dia='"diagnose":"BAD"' if form.has_key('land'): land=form['land'].value if form.has_key('vintype'): vintype=form['vintype'].value if vintype=='red': header='"overskrift":"Røde viner fra %s"'%land elif vintype=='white': header='"overskrift":"Hvite viner fra %s"'%land elif vintype=='rose': header='"overskrift":"Roseviner fra %s"'%land elif vintype=='sparkling': header='"overskrift":"Musserende viner fra %s"'%land else: header='"overskrift":"Ukjent vintype"' viner=doAll(land,vintype) if viner != None: dia='"diagnose":"OK"' viner=viner.decode('iso-8859-1') viner=viner.encode('utf-8') else: viner='xx' retstr='{'+dia+','+header+',"viner":['+viner[:-1]+']}' print retstr