Reading and Writing JSON
Programs talk to each other in JSON: a web form sends it, a payment service answers with it, a file stores it. It looks like this:
{"venue": "Town Hall", "guests": 120, "fee": "500.00"}
JSON from outside can’t be trusted. A field may be missing, a number may be text, a guest count may be 9000. Quill reads JSON as a kind, and checks all of it before your program uses any of it.
Reading JSON as a type
Say a booking arrives in a file, inbox/booking.json:
{"venue": "Town Hall", "guests": 120, "fee": "500.00"}
as Booking reads the text as a Booking:
permit read "inbox"
type Booking
venue: text
guests: number where 1..500
fee: USD
body = read_file("inbox/booking.json")
on FileMissing -> "none"
booking = body as Booking
on Invalid -> Booking venue = "unknown", guests = 1, fee = 0 USD
say booking.venue
say booking.fee
Town Hall
500 USD
as Booking checks that every field is there, that each one is the right kind, and that guests is from 1 through 500. Money is read exactly, from a number or from text like "500.00", so no cent is lost. Fields the type doesn’t name are left out.
When the text doesn’t fit, as fails with Invalid, and the program says what happens, like any failure. Here a booking for 9000 guests is refused:
{"venue": "Stadium", "guests": 9000, "fee": 1}
permit read "inbox"
type Booking
venue: text
guests: number where 1..500
fee: USD
body = read_file("inbox/huge.json")
on FileMissing -> "none"
booking = body as Booking
on Invalid -> Booking venue = "refused", guests = 1, fee = 0 USD
say booking.venue
refused
A program never holds a Booking that breaks its type’s rules, wherever the data came from.
Other kinds
as reads any kind a function can name: as number, as text, as list of Booking, as maybe number. A field that is maybe can be null or left out of the JSON, and is then missing:
[1.10, 2.25, 0.10]
permit read "inbox"
body = read_file("inbox/prices.json")
on FileMissing -> "[]"
prices = body as list of number
on Invalid -> list of number
say prices
1.10, 2.25, 0.10
Writing JSON
json turns any value into JSON text: a record becomes an object, a list an array, money an exact number, and missing becomes null:
type Booking
venue: text
guests: number
fee: USD
hall = Booking venue = "Town Hall", guests = 120, fee = 500.50 USD
say json(hall)
say json(list 1, 2, 3)
{"venue":"Town Hall","guests":120,"fee":500.50}
[1,2,3]
JSON written inside a program
JSON usually comes from outside: a file, a request, an answer. When you write it inside your own text, remember that { starts a value in Quill text. Write \{ and \} for the braces themselves:
type Point
x: number
y: number
point = "\{\"x\": 3, \"y\": 4\}" as Point
on Invalid -> Point x = 0, y = 0
say point.x + point.y
7
Common mistakes
Reading JSON can fail, so the failure has to be handled:
type Point
x: number
point = "[]" as Point
say point.x
Problem on line 4 of example.quill:
point = "[]" as Point
^^^^^^^^
Invalid isn't handled.
Reading text as Point can fail with Invalid, so the call needs on Invalid.
Did you mean: on Invalid -> say "Invalid"
as reads text. A value that is already a number or a record doesn’t need reading:
type Point
x: number
point = 5 as Point
on Invalid -> Point x = 0
Problem on line 4 of example.quill:
point = 5 as Point
^
as Point needs text for the JSON, but 5 is a Number.
as reads JSON text, like body as Point.
Try it
- Write
type Studentwithname: textandscore: number where 0..100, and a file with a student in JSON. Read it withas Student. - Change the score in the file to
120, and seeon Invalidrun. - Read a JSON list of students with
as list of Student, and say the names of those who passed withget name of students where score >= 50. - Say
jsonof one of your students.