Structuring user input for an API client

I am writing an R package as a client for an API (which doesn't require authentication and has no rate limits, so you are free to explore). The API works with POST requests, see. e.g. here: https://api.statbank.dk/console#data

The tricky input is the variables parameter in the API call body. Here is an example:

{
	"lang": "en",
	"table": "folk1c",
	"format": "CSV",
	"valuePresentation": "Default",
	"timeOrder": "Ascending",
	"variables": [
		{
			"code": "IELAND",
			"values": [
				5100
				]
		},
		{
			"code": "KØN",
			"values": [
				"1",
				"2"
				]
		},
		{
			"code": "tid",
			"values": [
				"*"
				]
		}
		]
}

The variables input can look in many different ways and can be very complex. A successful R implementation of the above call body is like this:


json_body <- list(table = "folk1c",
			format = "CSV",
			lang = "en",
			delimiter = "Tab",
			variables = list(list(code = "ieland", values = I(5100)),
						list(code = "køn", values = c(1,2)),
						list(code = "tid", values = I("*")))
						)

response <- POST("https://api.statbank.dk/v1/data", body = json_body, encode = "json")
output <- content(response, type = "text/tab-separated-values")

My my main question is, what is from the package user perspective the most painless and elegant way (I am looking for some inspiration here) to request the variables parameter as a function input as I cannot expect a user to have to provide such a complicated nested list object with I(), etc.

Thanks

1 Like

You could offer a statbank_var class for users to bundle the needed details. This provides a couple benefits:

  1. Code becomes easier to read, because the object-creating code acts like an easily visible "block."
  2. You can do validation inside the class function, with helpful error messages.
  3. You can make the interface easy for users and handle the nitty-gritty (like I("*")) in the class function.
  4. You can write an as.statbank_var(x) function and use it in functions to make sure the inputs match your expectations.
  5. You can write wrappers to transform data in one form (say, a tibble) to a list of statbank_var objects. This depends on how you expect users to organize their data in the first place.

Here's a rough example:

#' @param code Variable code (single string)
#' @param values Values from the variable to do XYZ (character vector)
#' @export
statbank_var <- function(code, values) {
  code <- as.character(code)
  values <- as.character(values)
  if (length(code) != 1) stop("'code' must be exactly 1 value")
  # Other validation and prep work...
  out <- list(code = code, values = I(values))
  class(out) <- "statbank_var"
  out
}

With this, your example would be:

query_statbank <- function(table, format, lang, delimiter, variables) {
  json_body <- list(
    table = table, format = format, lang = lang, delimiter = delimiter,
    variables = variables
  )
  POST("https://api.statbank.dk/v1/data", body = json_body, encode = "json")
}

query_vars <- list(
  statbank_var(code = "ieland", values = 5100),
  statbank_var(code = "køn", values = c(1,2)),
  statbank_var(code = "tid", values = "*"))
)

query_statbank("folk")
res <- query_statbank(
  table = "folk1c",
	format = "CSV",
	lang = "en",
	delimiter = "Tab",
	variables = query_vars
)

Note: I can't read Danish, so I'm not sure what the API expects for variables.

1 Like

Thanks a lot @nwerth - I will definitely consider this. I have recently submitted the package to CRAN and awaiting the validation process. In the meantime you are also more than welcome to contribute on Github :slight_smile: