Skip to content

JsonError

Standard library class · Extends RuntimeError

A JsonError is what Emerald raises when reading or writing JSON goes wrong. It happens in two different ways, and the message tells you which:

  • The text isn’t JSON. A bracket is missing, or there is a comma where JSON doesn’t allow one. The message gives the line and column where reading stopped.
  • The JSON is fine, but it isn’t what the program asked for. The program asked for a whole number and found text, or for a key the document doesn’t have. The message gives the place in the document.
try {
Json.parse("[1, 2,]")
}
catch error: JsonError {
print(error.message) # prints line 1, column 7: JSON does not allow a comma before "]"
}

message: String

What went wrong, and where, in words.

A problem in the text begins with line and column, counting from 1. The place is where reading had to stop, which is sometimes just after the mistake. For a comma left before a closing bracket, it is the bracket:

const text = """
{
"name": "Ada",
"level": 7,
}
"""
try {
Json.parse(text)
}
catch error: JsonError {
print(error.message) # prints line 4, column 1: JSON does not allow a comma before "}"
}

Line 3 is the one with the extra comma, and the message points at the } on line 4 that made it wrong. These are the mistakes that come up most, and what the message says for each. Every one of them is preceded by the line and column.

The text has The message says
A comma after the last item JSON does not allow a comma before "]", or "}"
Single quotes: {'a': 1} JSON strings use double quotes, not single quotes
A key without quotes: {a: 1} JSON object keys need double quotes: write "a"
Two items with no comma between them, or a closing bracket missing expected "," or "]" after this value, or "}"
A key with no colon: {"a" 1} expected ":" after this key
A comment JSON has no comments
NaN or Infinity NaN is not a JSON number, or the same for Infinity
A number such as 01 a number cannot have a leading zero
True instead of true "True" is not a JSON value
The same key twice in one object the key "a" is already used in this object
Nothing at all the document is empty
A second value after the first the document has more after this value; JSON allows only one value

A problem with the content begins with at and a path to the value that was wrong. The path is written the way you would reach the value in a program:

The path Where it is
players The "players" entry of the top-level object
players[2] Its item at position 2, counting from 0: the third
players[2].score The "score" entry of that item
["first name"] A key that isn’t a plain word goes in brackets and quotes
[1].score The same, when the top-level value is itself a list

When the wrong value is the whole document, the message says the document instead.

const document = Json.parse('{"players": [{"name": "Ada", "score": 5}, {"name": "Linus", "score": "high"}]}')
try {
print(document.get("players").at(1).get("score").int())
}
catch error: JsonError {
print(error.message) # prints at players[1].score: expected a whole number, found the text "high"
}

The path is what makes an error in a large document findable. It stays attached to a value as you step through it with get and at, and Json.decode builds the same path while it reads into your types. A field the document leaves out, with no default to fall back on, says at score: this value is missing.

When nothing catches the error, the program stops and shows the message with the line of your program that led to it:

main.em:2:7: JsonError: at players[1].score: expected a whole number, found the text "high"
Reading
Json.parse The text is not JSON
Json.decode The text is not JSON, or does not fit the type after as:
Looking inside a Json value
get, at The key or position is missing, or the value is the wrong kind to look inside
count, keys() The value is not a list or an object
string(), int(), float(), bool(), list(), object() The value is a different kind, or int() finds a fraction or a number too large for an Int
Writing
Json.encode, Json.from_float A Float is infinity or not a number, which JSON can’t write

Catch JsonError where the program can do something sensible about bad JSON, such as falling back to defaults:

struct Settings {
const name: String = "Player"
const volume: Int = 5
}
func read_settings(text: String): Settings {
try {
return Json.decode(text, as: Settings)
}
catch error: JsonError {
print("Using the defaults: #{error.message}")
return Settings()
}
}
print(read_settings('{"volume": 9}'))
print(read_settings('{"volume": "loud"}'))

That prints:

Settings(name: "Player", volume: 9)
Using the defaults: at volume: expected a whole number, found the text "loud"
Settings(name: "Player", volume: 5)

A JsonError is a RuntimeError, so catch error: RuntimeError catches it too, along with every other kind of runtime problem. Catching JsonError by name lets those others keep going. And it works the other way: a catch for another kind, such as FileError, doesn’t catch a JsonError. When the text comes from a file, a program that wants to survive both a missing file and a broken one needs a catch for each.

If bad input is an ordinary thing to expect and not an emergency, you can skip the error entirely. Json.parse_maybe, and the _maybe form of each lookup and conversion, such as get_maybe and int_maybe, give nothing instead of raising. Json.decode has no such form, so catch its error.