Ballerina’s JSON type and lax static typing

Maryam Ziyad
Ballerina Swan Lake Tech Blog
4 min readSep 13, 2019

Ballerina, a programming language primarily targeting network-distributed applications, just hit 1.0!

Among the numerous features that make Ballerina attractive, ranked high up there are arguably its native support for JSON and its type-safety. Any standard JSON value has a natural representation in Ballerina, which makes it quite straightforward to work with data in the JSON format.

Ballerina has a built-in json type which represents any all valid JSON values.

Ballerina’s representation of the basic JSON data types are as follows:

  • Any number can be represented in Ballerina as an int, float or a decimal.
  • Similarly any boolean value would be represented by the boolean type.
  • Any string would be represented by the string type.
  • Ballerina’s () (nil) type represents the absence of any other value. In a JSON context, Ballerina’s () type also represents null.
  • A JSON array is naturally a Ballerina array, where the element type is a JSON compatible type. Basically the json[] type represents all possible JSON arrays.
  • A JSON object, which is basically a collection of key-value pairs, is represented by a map in Ballerina, where the constraint of the map is a JSON compatible type. The map<json> type represents all possible JSON objects.

Thus the json type is a union type in Ballerina, whose value space is defined as the union of the value spaces of () | boolean | int | float | decimal | string | json[] | map<json>.

Lax Static Typing

Ballerina is a statically typed language.

Ballerina also has the concept of lax static typing where static typing rules are less strict, effectively moving some of the type-checking from compile time to runtime. This is allowed only with types that are defined to be lax. With Ballerina 1.0 this is basically json and map<T> where T is lax.

For example, field access (accessing a member via the . operator — e.g., foo.name) is generally allowed only on object/record values where it is guaranteed that the value has a field by that name. Similarly optional field access (via the ?. operator) is generally allowed only on record values where it is possible that the value could have a field by the name (i.e., a required field or an optional field in the type-descriptor).

The only exception to this is when it comes to lax types. Field access and optional field access are also allowed on lax types, but the type of such an expression would also allow for error scenarios.

Field Access on Lax Types

Field access is allowed on lax types. For example it is allowed on a variable (say j) of type json. At runtime one of the following could happen when attempting to access a field named name in j, via j.name.

  • j is a JSON object (i.e., map<json>) and j has a key-value pair (k, v)where k is name. Here the resultant value of j.name would be v which would be json-compatible.
  • j is a JSON object, but j does not have a key-value pair where name is the key. In such a scenario, accessing a field named name would fail, and the resultant value of j.name would be an error indicating that j does not have an entry with the particular key.
  • j may not be a JSON object (given that the json type is a union type representing all possible JSON values including numbers, strings, booleans, arrays and objects). In such a scenario, accessing a field is not possible, and the resultant value of j.name would again be an error indicating that j is not a JSON object.

Thus the effective type of the expression j.name would be the union of the type of the value if present (json) and error. This approach leaves pretty much no room for errors, and ensures all possible error scenarios are handled.

Optional Field Access on Lax Types

Optional field access on lax types also has similar behaviour, with the only exception being the result on a missing key.

While field access returns an error if a JSON object does not have a field by the specified key, optional field access return () (nil) in such a scenario.

Thus the effective type of the expression j?.name where j is lax, would be the union of the type of the value if present, () (nil), and error.

Accessing fields of JSON values is something that underwent significant changes from the previous release of Ballerina, and naturally a lot of people have had a lot of questions regarding this change.

If you haven’t used a pre-1.0 version of Ballerina, do feel free to skip to the end! :)

With pre-1.0 versions, accessing a field on a json-typed variable did not return an error. Instead, in any of the error scenarios (i.e., not a JSON object, missing key, etc.) nil (()) was returned as the value.

This had obvious drawbacks and was error-prone since it was not quite straightforward to figure out why accessing a field returned () — was it because the value had a field by the specified key and the value was nil, or was it because the value did not have a field by the particular key, or was it because it was not even a JSON object?!

While the changes and formalization to JSON access may seem cumbersome due to additional type tests/casts, this also leaves little to no room for error, and forces a user to handle any and all possible scenarios which IMO makes it worthwhile. :)

This is simply a basic introduction to Ballerina’s JSON type and lax static typing. Ballerina additionally provides a number of operations facilitating working with JSON data.

Cheers!

--

--