Skip to content
AgileLogics
5 min read

Working with JSON and APIs

Prerequisites: Lists, Tibbles

JSON is the format most web APIs use to send data. The jsonlite package converts between JSON and R objects.

JSON to R

library(jsonlite)

json <- '[
  {"name": "Ada", "age": 36, "langs": ["R", "Python"]},
  {"name": "Linus", "age": 28, "langs": ["C"]}
]'

people <- fromJSON(json)
class(people)
people[, c("name", "age")]
people$langs[[1]]
[1] "data.frame"
   name age
1   Ada  36
2 Linus  28
[1] "R"      "Python"

fromJSON simplifies an array of similar objects into a data frame automatically. Nested arrays (like langs) become a list column, with one vector per row.

Paste an API response into the JSON Formatter to see its structure before deciding how to flatten it.

R to JSON

library(jsonlite)

df <- data.frame(name = c("Ada", "Linus"), age = c(36, 28))
toJSON(df)
toJSON(list(status = "ok", count = 2), auto_unbox = TRUE)
[{"name":"Ada","age":36},{"name":"Linus","age":28}]
{"status":"ok","count":2}

auto_unbox = TRUE writes single values as 2 rather than [2] — R values are always vectors, so without it every value becomes an array. Add pretty = TRUE for indented output.

Calling an API

fromJSON can read a URL directly for simple public APIs. For anything more — headers, authentication, error handling, retries — use the httr2 package:

library(httr2)

resp <- request("https://api.github.com/repos/tidyverse/dplyr") |>
  req_timeout(10) |>
  req_perform()

repo <- resp_body_json(resp)
repo$stargazers_count

req_perform() raises an R error for HTTP error statuses (4xx and 5xx), so failures aren't silently ignored.

Common mistakes

  • Hard-coding API keys in scripts. Store them in environment variables (in a .Renviron file that isn't committed to version control) and read them with Sys.getenv("API_KEY").