19 Working with JSON
list()s in R – both are recursive and heterogeneous data structures that have similar semantics for accessing values. In JSON, there are three basic building blocks: objects, arrays, and primitive data types (e.g., number, string, boolean,
Loosely speaking, a JSON array is similar to a un-named
list() in R and a JSON object is similar to an un-named
list(). In fact, if you’re already comfortable creating and subsetting named and un-named
list()s in R, you can transfer some of that knowledge to JSON arrays and objects.
19.1 Assignment, subsetting, and iteration
In R, the
<- operator assigns a value to a name, and the
[[ operator extracts a list element by index:
In JS, the
= assigns a value to a name. When assigning a new name, you should include the
var keyword (or similar) to avoid creation of a global variable. The
[ operator extracts list elements by index, but be careful, indexing in JS starts at 0 (not 1)!
In R, the
[[ operator can be used to extract list elements by name. The difference is that
$ does partial matching of names, while
[[ requires the exact name.
In JS, the
[ operator can be used to extract list elements by name. In either case, the naming must be exact.
list()s, arrays and objects in JS come with properties and methods that can be accessed via the
. operator. Arrays, in particular, have a
length property and a
map() method for applying a function to each array element:
In R, both the
purrr::map() family of functions provide a similar functional interface. Also, note that operators like
+ in JS do even more type coercion than R, so although
item + 1 works for strings in JS, it would throw an error in R (an that’s ok, most times you probably don’t want to add a string to a number)! If instead, you wanted to only add 1 to numeric values, you could use
is.numeric() in R within an if else statement.
In JS, you can use the
typeof keyword to get the data type as well as the conditional ternary operator (
condition ? exprT : exprF) to achieve the same task.
There are a handful of other useful array and object methods, but to keep things focused, we’ll only cover what’s required to comprehend Section 20. A couple examples in that section use the
filter() method, which like
map() applies a function to each array element, but expects a logical expression and returns only the elements that meet the condition.
19.2 Mapping R to JSON
In R, unlike JSON, there is no distinction between scalars and vectors of length 1. That means there is ambiguity as to what a vector of length 1 in R should map to in JSON. The jsonlite package defaults to an array of length 1, but this can be avoided by setting
auto_unbox = TRUE.
It’s worth noting that plotly.js, which consumes JSON objects, has specific expectations and rules about scalars versus arrays of length 1. If you’re calling the plotly.js library directly in JS, as we’ll see later in Section 20, you’ll need to be mindful of the difference between scalars and arrays of length 1. Some attributes, like
marker.size, accept both scalars and arrays and apply different rules based on the difference. Some other attributes, like
z only accept arrays and will error out if given a scalar. To learn about these rules and expectations, you can use the
schema() function from R to inspect plotly.js’ specification as shown in Figure 19.1. Note that attributes with a
'data_array' require an array while attributes with an
arrayOk: true field accept either scalars or arrays.
In JSON, unlike R, there is no distinction between a heterogeneous and homogeneous collection of data types. In other words, in R, there is an important difference between
list(1, 2, 3) and
c(1, 2, 3) (the latter is an atomic vector and has a different set of rules). In JSON, there is no strict notion of a homogeneous collection, so working with JSON arrays is essentially like being forced to use
list() in R. This subtle fact can lead to some surprising results when trying to serialize R vectors as JSON arrays. For instance, if you wanted to create a JSON array, say
[1,"a",true] using R objects, you may be tempted to do the following:
But this actually creates an array of strings instead of the array with a number, string, and boolean that we desire. The problems actually lies in the fact that
c() coerces the collection of values into an atomic vector. Instead, you should use