LessonsExpert: BeyondLesson 54

JSON

Read and write JSON, the text format of almost every web service: JSONImport into arrays, JSONExport back out, numbers and lists, errors, and settings files.

  • Expert
  • Lesson 54 of 83
  • About 10 minutes
  • 6 examples

JSON in one look

JSON (JavaScript Object Notation) is plain text for structured data, and almost every web service sends and takes it. Its pieces map onto xTalk's naturally:

JSONLooks likeIn xTalk
object{"name": "Ada", "born": 1815}an array with those keys
array (a list)["tea", "cake"]an array with keys 1, 2, 3 …
string"Zoë said \"hi\""text
number3.14a number
true, falsetrue, false
nullempty

The two functions that do the work, JSONImport and JSONExport, come in the JSON library that OXT-Beyond loads as it starts.

Reading JSON: JSONImport

JSONImport turns JSON text into an xTalk value, usually an array, whose elements you reach with keys, as deep as the JSON goes:

ExampleRun it in the playground
put "{'name': 'Zoë', 'tags': ['admin', 'editor'], 'address': {'city': 'Lyon'}, 'score': 3.5, 'active': false, 'manager': null}" into tJSON
replace "'" with quote in tJSON

put JSONImport(tJSON) into tUser
put tUser["name"] && "lives in" && tUser["address"]["city"]
put "tags:" && the number of elements of tUser["tags"] & ", the second is" && tUser["tags"][2]
put "score doubled:" && tUser["score"] * 2
put "active:" && tUser["active"]
put "manager is empty:" && (tUser["manager"] is empty)
Output
Zoë lives in Lyon
tags: 2, the second is editor
score doubled: 7
active: false
manager is empty: true

JSON's strings are in double quotes, which end an xTalk string. To write JSON in a script, this example writes ' for each " and then replaces them: a trick for examples and tests. JSON you work with usually comes from a file or a web service, already as text.

Writing JSON: JSONExport

JSONExport turns an xTalk value into JSON text, escaping quotes and line breaks in strings for you:

ExampleRun it in the playground
put "Ada said" && quote & "hello" & quote & return & "and left" into tNote["text"]
put JSONExport(tNote)

put 1815 + 0 into tPerson["born"]
put JSONExport(tPerson)
put "1815" into tPerson["born"]
put JSONExport(tPerson)
Output
{"text": "Ada said \"hello\"\nand left"}
{"born": 1815}
{"born": "1815"}

The last two lines show a difference that matters to many services. A value that xTalk holds as a number is written as a JSON number; one it holds as text is written as a string, even when the text is digits. A value typed in a script, or read from a field or a file, is text; arithmetic (here + 0) makes it a number.

Writing a JSON list

An xTalk array is always written as a JSON object, even when its keys are 1, 2, 3, so ["tea", "cake"] comes out as {"1": "tea", "2": "cake"}. When a service needs a JSON list, build it from the elements in order, each one exported on its own:

ExampleRun it in the playground
function jsonList pArray
   local tItems
   repeat with i = 1 to the number of elements of pArray
      put JSONExport(pArray[i]) & "," after tItems
   end repeat
   delete the last char of tItems
   return "[" & tItems & "]"
end jsonList

put "tea" into tOrder[1]
put "cake" into tOrder[2]
put "juice" into tOrder[3]
put jsonList(tOrder) into tJSON
put tJSON
put JSONImport(tJSON) into tBack
put tBack[2]
Output
["tea","cake","juice"]
cake

When the JSON is wrong

Text that is not valid JSON makes JSONImport throw an error, so wrap it in a try whenever the text comes from outside your program:

ExampleRun it in the playground
command readJSON pText, @rValue
   try
      put JSONImport(pText) into rValue
   catch tError
      return "not valid JSON" for error
   end try
   return empty for value
end readJSON

repeat for each line tText in "[1, 2, 3]" & return & "{broken"
   readJSON tText, tValue
   if the result is not empty then
      put tText & ":" && the result
   else
      put tText & ": read," && the number of elements of tValue && "elements"
   end if
end repeat
Output
[1, 2, 3]: read, 3 elements
{broken: not valid JSON

A settings file

JSON makes a good settings file: readable, and easy to edit by hand if need be. Write it as UTF-8 through binfile: so accented letters survive on every system (Reading and writing files):

ExampleRun it in the playground
put specialFolderPath("temporary") & "/oxt-lesson-settings.json" into tPath

put "dark" into tSettings["theme"]
put 14 + 0 into tSettings["textSize"]
put "Zoë" into tSettings["user"]
put textEncode(JSONExport(tSettings), "UTF-8") into URL ("binfile:" & tPath)

put JSONImport(textDecode(URL ("binfile:" & tPath), "UTF-8")) into tLoaded
put tLoaded["user"] && "likes" && tLoaded["theme"] && "at" && tLoaded["textSize"] && "points"
delete file tPath
Output
Zoë likes dark at 14 points

Try it yourself

A web service sent this list of books: [{"title": "Dune", "pages": 412}, {"title": "Emma", "pages": 474}, {"title": "Momo", "pages": 304}]. Read it, put the titles sorted by pages, longest first, and the total number of pages.

Show a solution
ExampleRun it in the playground
put "[{'title': 'Dune', 'pages': 412}, {'title': 'Emma', 'pages': 474}, {'title': 'Momo', 'pages': 304}]" into tJSON
replace "'" with quote in tJSON
put JSONImport(tJSON) into tBooks

put 0 into tTotal
repeat with i = 1 to the number of elements of tBooks
   put tBooks[i]["pages"] & tab & tBooks[i]["title"] & return after tList
   add tBooks[i]["pages"] to tTotal
end repeat
set the itemDelimiter to tab
sort lines of tList numeric descending by item 1 of each
repeat for each line tLine in tList
   put item 2 of tLine && "(" & item 1 of tLine && "pages)"
end repeat
put "total:" && tTotal
Output
Emma (474 pages)
Dune (412 pages)
Momo (304 pages)
total: 1190

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.