Jeg arbejder en del med både JSON og Javascript, og oplever hyppigt at folk blander de to sammen. Oftest fordi de ikke rigtigt har forstået hvad JSON er. De ved at navnet kommer fra “JavaScript Object Notation” og konkluderer hurtigt (og lidt forhastet) at “JSON jo bare er Javascript objekter”. Og selvom det er helt forkert er det svært at bebrejde dem deres opfattelse. Populariseringen af JSON trak i høj grad på at “JSON er Javascript” og at enhver Javascript fortolker derfor umiddelbart kunne behandle JSON. Efter at JSON er blevet populært har det også smittet af den modsatte vej, hvor Javascript objekter nu ses omtalt som “JSON objekter”.
Men JSON er ikke Javascript.
JSON er data. Det er et format hvormed man kan beskrive primitive datatyper og hierarkiske data-strukturer som en simpel tegnsekvens. I alle de velkendte programmeringssprog — inklusive Javascript — vil JSON optræde i form af en streng, dvs. imellem citationstegn. Strengen er opbygget efter samme syntaks som den notation hvormed man beskriver datastrukturer i Javascript, deraf navnet.
JSON er dog mere end objekter. Det kommer ofte som en overraskelse for folk at en JSON streng både kan starte med “{” og “[". Sidstnævnte bruges til at beskrive en sorteret sekvens af elementer, typisk kaldt for et array. En JSON datastruktur kan (naturligvis) indeholde primitive data-typer (tal, strenge, boolske værdier og den særlige værdi null), men den kan ikke kun være en primitiv værdi. Den skal starte med enten "{" eller "[".
Under populariseringen af JSON blev det ofte fremhævet at JSON data i en Javascript fortolker let kunne "deserialiseres" (konverteres) til en hierarkisk Javascript datastruktur ved brug af eval(...). Det er ikke helt korrekt, og jeg har flere gange oplevet overraskelse over at eval(...) fejler på JSON data, hvis ikke man tilføjer et sæt parenteser til strengen. Selv nu, hvor eval(...) af sikkerhedsmæssige årsager frarådes, også til JSON data, lader der til at være ringe forståelse for disse fejl.
Forklaringen er dog ret simpel. eval(...) er lavet til at afvikle et Javascript program. Det er det eneste den kan. Den afvikler et Javascript program som man giver til funktionen i en streng, og så returnerer den værdien af programmets sidste erklæring. Og JSON er jo ikke Javascript. Grunden til at det alligevel virker i nogle tilfælde, og at det (for gyldig JSON) altid kan bringes til at virke ved at tilføje et sæt parenteser, er at en JSON struktur mere eller mindre tilfældigt er meget tæt på at kunne fortolkes som et Javascript program.
I ECMAScript definitionen (ECMA-262), som definerer Javascript sproget, kan man bl.a. læse følgende:
- "... if the parameter to the built-in eval function is a string, it is treated as an ECMAScript Program."
- Et ECMAScript program består af en sekvens af erklæringer ("Statement" eller "FunctionDeclaration").
- En erklæring ("Statement") kan være en række forskellige ting (se afsnit 12 i ECMA-262), hvoraf de eneste interessante i denne sammenhæng er en "Block" eller en "ExpressionStatement".
- En "Block" er en sekvens af erklæringer imellem et sæt krøllede parenteser: "{ ... }".
- En "ExpressionStatement" består af et enkelt udtryk ("Expression") efterfulgt af et valgfrit semikolon.
- Et udtryk ("Expression") kan være en række forskellige ting (se afsnit 11 i ECMA-262), hvoraf de eneste interessante i denne sammenhæng er en "ArrayLiteral" ("[ ... ]“), en “ObjectLiteral” (“{ … }”) eller et udtryk i parenteser.
Hvis en JSON streng der starter med en “{” evalueres med eval(...), så vil den blive opfattet som en “Block” fra punkt 4 ovenfor, fordi denne har højere prioritet i ECMAScript grammatikken end en “ExpressionStatement”. Hvis JSON strengen starter med “[” vil den blive opfattet som en “ArrayLiteral” og vil dermed blive konverteret korrekt, forudsat naturligvis at der er tale om gyldig JSON. Under alle omstændigheder kan man pakke JSON strengen ind i et sæt parenteser. Strengen vil så ikke længere være gyldig JSON, men parenteserne får strengen til at blive opfattet som et udtryk, og får dermed det oprindelige JSON indhold til at blive opfattet korrekt som en “ArrayLiteral” eller en “ObjectLiteral”, og parenteserne ændrer ikke værdien af udtrykket.
Bemærk at den gyldige syntaks for Javascript arrays (specifikt “ArrayLiteral”) og objekter (specifikt “ObjectLiteral”) tillader en del mere end den gyldige syntaks for JSON arrays og objekter. F.eks. kan både Javascript arrays og objekter indeholde funktions-erklæringer, variabel-referencer, brug af indbyggede objekter som f.eks. Date og Math, samt den særlige værdi “undefined”, ingen af hvilke er gyldige i JSON. Navne på egenskaberne i et JSON objekt skal desuden være i dobbelte citationstegn, hvilke ikke er et krav for Javascript objekter. Gyldig JSON kan valideres med JSONLint.
14. april 2009 kl. 11:56
Og det er ikke kun hvem som helst der tager fejl af JavaScript og JSON!
Se f.eks. denne MSDN artikel om “JSON” serialisering.
Her fremgår det at:
Date object [is] represented in JSON as “\/Date(number of ticks)\/”. The number of ticks is a positive or negative long value that indicates the number of ticks (milliseconds) that have elapsed since midnight 01 January, 1970 UTC.
Men det er jo ganske ukorrekt, eftersom det vil fejle hvis outputtet forsøges parset som JSON med en JSON parser. Det vil dog virke efter hensigten hvis det parses som JavaScript (f.eks. med “eval” kommandoen i JavaScript). Det der gives med i outputtet er jo instruktionen “kald funktionen Date med et angivet antal millisekunder”, og det giver i sagens natur kun mening i et JavaScript miljø.
20. april 2010 kl. 09:05
Jeg kan ikke se det store problem i dette. Du skriver jo selv at JSON er en notations-form og altså dermed ikke Javascript kode. Stort set alle jeg arbejder med laver ikke den destinktion du nævner.
21. april 2010 kl. 18:28
@Frank: Det er ikke noget stort problem. Men ikke desto mindre fører sammenblandingen af de to rigtigt ofte til misforståelser, og nogle gange til direkte fejl. Fejl der let kunne undgås hvis man husker at der er en forskel.
17. maj 2010 kl. 15:28
Hej Jakob.
Tak for et udemærket indlæg.
Jeg indrømmer blankt, at jeg først opdagede den komplette forskel, efter at have opdateret til jQuery 1.4.
Det var nemlig her de droppede supporten for malformed JSON;)
Og jeg tror stadig der er mange, for hvem disse forskelle ikke er sunket ind;)