Skip to main content
Version: 2.5.x (dev)

JSON

Liquidsoap can parse and generate JSON data directly in your scripts. You can use it to load configuration files, talk to web APIs or exchange metadata with other programs.

This page explains how JSON parsing works, how types drive the parser, and how to use nullable types, custom keys and objects parsed as lists.

Parsing a JSON object​

You can parse a JSON string using the special let json.parse syntax:

let json.parse v = '{"foo": "abc"}'
print("We parsed a JSON object and got value " ^ v.foo ^ " for attribute foo!")

This prints:

We parsed a JSON object and got value abc for attribute foo!

Liquidsoap infers from the script that v.foo is used as a string. At runtime, the parser checks that the JSON data contains a "foo" field and that its value is a string. If the data does not match, the parser raises an error.

For instance, with a number in place of the string:

let json.parse v = '{"foo": 123}'

the script fails with:

Error 14: Uncaught runtime error:
type: json,
message: "Parsing error: json value cannot be parsed as type {foo: string, _}"

Loading JSON from files​

You can load the JSON data from a file:

let json.parse v = file.contents("/path/to/file.json")

For instance, to parse the package.json file of an npm package:

let json.parse package = file.contents("/path/to/package.json")

name = package.name
version = package.version
test = package.scripts.test

print("This is package " ^ name ^ ", version " ^ version ^ " with test script: " ^ test)

Type annotations​

Liquidsoap sometimes cannot infer the type of a parsed value. This happens for instance when a variable is only used inside a string interpolation (#{...}). In this case, the value is parsed as null and Liquidsoap logs a warning.

To avoid this, add a type annotation to the parse statement. The parser uses the annotation to decide which JSON data to extract:

let json.parse ({
name,
version,
scripts = {
test
}
} : {
name: string,
version: string,
scripts: {
test: string
}
}) = file.contents("/path/to/package.json")

With this annotation, name, version and test are strings, even when you only use them inside interpolations.

The following sections describe the types you can use in annotations.

Ground types​

TypeDescriptionExample value
stringA sequence of characters"hello"
intAn integer42
floatA number, including decimals3.14 or 123.0
boolA booleantrue

Integers are accepted where a float is expected: 123 can be parsed as a float.

Nullable types​

Add ? to a type to make it nullable:

test: string? # test is either a string or null

A nullable field is set to null when it is missing from the JSON data or when its value has a different type. Use nullable types for data that may or may not include a field:

let json.parse ({
scripts
} : {
scripts: {
test: string?
}?
}) = file.contents("package.json")

You can then check whether the values are present:

# Option 1: Explicit check
test =
if null.defined(scripts) then
null.get(scripts).test
else
null
end

# Option 2: Fallback value
test = (scripts ?? { test = null }).test

Tuples​

A tuple type parses a JSON array with a specific type for each position:

(int * float * string)

This parses a JSON array like [1, 2.5, "hello"].

Use _ for the positions you want to skip:

(_ * _ * float) # Only the third element must be a float

Lists​

A list type parses a JSON array whose elements all have the same type:

[int] # list of integers
[float?] # list of nullable floats

For instance, the JSON array

[44.0, 55, 66.12]

can be parsed with type [float].

Objects​

A record type parses a JSON object into named fields:

{foo: int, bar: string}

The parser extracts the fields listed in the type and ignores the other fields of the JSON object.

Custom JSON keys​

JSON keys can contain spaces and other characters that are not valid in Liquidsoap variable names. Use as to map such a key to a field name:

{"foo bar" as foo_bar: int}

With this type, the JSON object

{ "foo bar": 123 }

is parsed as a record with field foo_bar = 123.

Associative objects as lists​

When you do not know the keys of an object in advance, parse the object as a list of key/value pairs with the type [(string * <value type>)] as json.object.

For instance, the JSON object

{ "a": 1, "b": 2, "c": 3 }

parsed with type

[(string * int)] as json.object

gives the list

[("a", 1), ("b", 2), ("c", 3)]

Use int? as the value type if some values may be missing or of another type: these values are parsed as null.

Handling errors​

A parsing error raises an error.json error, which you can catch:

try
let json.parse ({status, data = {track}} : {...}) = response
# Do something with data
catch err: [error.json] do
# Handle the parse failure
end

Full example​

The following example uses all the types described above:

json-ex.liq
data =
'{
"foo": 34.24,
"gni gno": true,
"nested": {
"tuple": [123, 3.14, false],
"list": [44.0, 55, 66.12],
"nullable_list": [12.33, 23, "aabb"],
"object_as_list": {
"foo": 123,
"gni": 456.0,
"gno": 3.14
},
"arbitrary object key ✨": true
},
"extra": "ignored"
}'

let json.parse (x :
{
foo: float,
"gni gno" as gni_gno: bool,
nested: {
tuple: (_ * float),
list: [float],
nullable_list: [int?],
object_as_list: [(string*float)] as json.object,
"arbitrary object key ✨" as arbitrary_object_key: bool,
not_present: bool?
}
}
) = data

Other features​

JSON5​

Liquidsoap can parse JSON5 data, which allows trailing commas, comments and other extensions:

let json.parse[json5=true] x = ...

Exporting to JSON​

json.stringify converts a value to a JSON string:

print(json.stringify({artist="Bla", title="Blo"}))

Building JSON manually​

When you generate JSON output dynamically, use json.object to build an object key by key:

j = json.object()
j.add("foo", 1)
j.add("bar", "baz")
j.remove("foo")
print(json.stringify(j))

json.value converts any value to the json type. You can use it when a function returns JSON values of different types, for instance in an HTTP response handler:

try
# Send a number here:
id = 1234
res.json(json.value(id))
catch err do
# Or a string in case of an error:
res.json(json.value("Error while processing request: #{err}"))
end

The test file tests/language/json.liq in the Liquidsoap source repository contains many more examples.