Skip to contents

The rule

Real data has holes in it, and every write you send — api_post(), api_patch(), and the _multi and _df variants — has to say something about them. All of them say the same thing, whatever the column type:

In R On the wire Meaning
NA, any type null clear the field
absent column or list element not sent leave the field unchanged
NaN, Inf, -Inf error refused before the request is sent
NULL list element null clear the field

You do not have to configure any of this, and it does not vary by whether you pass a tibble, a data frame, or a list.

Clearing a field, or leaving it alone

An update can mean two different things about a field, and the difference matters: clearing a recorded value is not the same as declining to touch it. The two are said differently.

To clear a field, include the column and set it to NA:

# Drops the recorded comment
api_patch("counts", "projects/age-classes",
  id = 42,
  body = tibble::tibble(comment = NA_character_)
)

To leave a field alone, leave the column out of the body:

# Changes age_class; the comment is untouched
api_patch("counts", "projects/age-classes",
  id = 42,
  body = tibble::tibble(age_class = "Sub-Adult")
)

This is worth knowing when the body is something you computed rather than typed out. A tibble that has been joined, pivoted, or filtered often carries NA cells in columns you never meant to write, and each of those now clears a field. Select the columns you intend to change before sending:

body <- dplyr::select(updates, age_min_months, age_max_months)

api_patch_multi("counts", "projects/age-classes/multiple",
  id = updates$id,
  body = body
)

If a field you clear is one the API requires, the request fails with a 422 naming that field. That is the intended outcome: an NA you did not mean to send is an error rather than a change you never notice.

NaN and Inf

Division by zero, a log of zero, and a model that failed to converge all produce values JSON has no way to write down. Rather than guess at a substitute, these are refused before the request leaves your machine, and the error names the fields responsible:

api_post("counts", "estimates",
  body = list(ratio = 0 / 0, density = 1 / 0)
)
#> Error: Request body contains 2 values JSON cannot represent.
#> x ratio and density are `NaN` or `Inf`.
#> i Use `NA` to clear the field, or drop it from the body to leave it
#>   unchanged.

Decide what the value should mean and say so: NA if the quantity is genuinely unknown, a real number if it is not.

estimates <- estimates |>
  dplyr::mutate(ratio = dplyr::if_else(is.finite(ratio), ratio, NA_real_))

What changed in 1.3.0

Before 1.3.0 the wire value depended on the column’s type and on which function you called. A numeric NA was sent as the string "NA", a character NA as null, and in a multi-row update an NA was dropped from the request altogether — so the server left that field alone and returned success, and the change you asked for silently did not happen.

If you have code that relied on NA meaning “leave this alone” in api_patch_multi(), it now means “clear this”. Select the columns you intend to write, as above, and the behavior is the same as before.

Records written with api_post_df() may also hold the literal text "NA" in columns where a value was missing. Those are worth finding and clearing:

api_get("counts", "estimates", filters = list(comment = "NA")) |>
  parse_json2tibble()