use `base::serializer()` in the plumber API

Xianying Tan


Recently, I need to share an R model on the server to my colleagues who use R. Plumber comes to my mind immediately. Build a web API using plumber is really easy. I love the roxygen-way to define the API. It’s elegant and easy to maintain.

Usually, web APIs use JSON to represent data. Unfortunately, JSON encodes objects in a string, which may result in information losses. For example, the attributes (other than names) cannot be preserved. And it causes troubles:

Luckily, all my “clients” (my colleagues) are R users, so I don’t really need a general web API. JSON is only one of the many methods to serialize objects and I’m not bound to it. Due to the existence of base::saveRDS(), I know there must be a serializing method provided by R itself - whether the method is exported or not is the only thing in doubt. Fortunately, with little effort, base::serialize() and base::unserialize() are the cures I’m looking for.

My solution is provided in the code below. Since the rds file is almost the seamless representation of the R objects (external pointers are the exception), using base::serialize() as the customized serializer of the plumber API minimizes the efforts required to establish a stable plumber API for the R users.


UPDATE @2020/03/21

As the time of writing, the dev version of plumber now gains the new native serializer rds.

The sample code (BOTH POST and RETURN r objects)


#* @post /api
#* @serializer rds
function(req) {


(In practice, you probably want to have a condition inside. A good example is this:

x <- plumb("plumber.R")
x$filter("robj", function(req) {
  req$robj <- unserialize(req$rook.input$read())
x$run(debug = TRUE, port = 9999)


out <- httr::POST(
  encode = "raw",
  body = serialize(iris, NULL),
# you may need to check httr::status_code() == 200L
# or if is.raw(httr::content(out)) is TRUE, first

  1. Let’s make a large double vector by v <- rnorm(1e8), system.time(invisible(jsonlite::toJSON(v))) costs 27 seconds while system.time(invisible(serialize(v, NULL))) costs less than 4 seconds on my computer ↩︎