Ballerina’s JSON type and lax static typing
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 adecimal
. - 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 representsnull
. - 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>
) andj
has a key-value pair(k, v)
wherek
isname
. Here the resultant value ofj.name
would bev
which would bejson
-compatible.j
is a JSON object, butj
does not have a key-value pair wherename
is the key. In such a scenario, accessing a field namedname
would fail, and the resultant value ofj.name
would be an error indicating thatj
does not have an entry with the particular key.j
may not be a JSON object (given that thejson
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 ofj.name
would again be an error indicating thatj
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.
- Check out the JSON Ballerina by Examples under the JSON/XML section for examples of the language level support to work with JSON.
- Check out the standard library API docs for conversion from XML/table to JSON and from JSON to XML.
Cheers!