3
#* @get /json
#* @serializer unboxedJSON
function() {
  return(iris)
}

#* @get /csv
#* @serializer csv list(type="text/plain; charset=UTF-8")
function() {
  return(iris)
}

#* @param type csv or json
#* @get /data
function(type = 'csv') {
  if (type == 'cvs') {
    #* @serializer csv list(type="text/plain; charset=UTF-8")
    return(iris)
  } else {
    #* @serializer unboxedJSON
    return(iris)
  }
}

The first 2 endpoints above each work fine, but the 3rd endpoint does not work as the #* @serializer unboxedJSON cannot be inside the function it seems. However, it would be great if I could somehow have a single endpoint which handles serializing before returning. plumber has plumber::serializer_csv and plumber::serializer_unboxed_json() and all of their serializers as functions, however I'm not sure how to use them inside the endpoint functions (or if this is even possible)

Thanks!

Canovice
  • 9,012
  • 22
  • 93
  • 211
  • Why one endpoint? Serializers are on an endpoint basis. One serializers, one endpoint. Instead of passing a type parameters. Just create two endpoint /data/json and /data/csv. Both endpoint expression can use the same function. I mean, it is feasible but I do not see any reason to do it. – Bruno Tremblay Oct 15 '20 at 21:12
  • If you really want to do down this route, I would use plumber fitlers, check for incoming headers (say Accept) for the type of data user wants returned and modify res$serializer according to the what the user asked. – Bruno Tremblay Oct 15 '20 at 21:20

1 Answers1

4

You can create a custom serializer that changes the serialization format depending on the the response object. Here is an example. In this case, I attach an attribute to my response that provides the format that I want:

library(plumber)

#* @apiTitle Example Plumber API with Serializer to switch formats using object
#* attributes

serializer_switch <- function() {
  function(val, req, res, errorHandler) {
    tryCatch({
      format <- attr(val, "serialize_format")
      if (is.null(format) || format  == "json") {
        type <- "application/json"
        sfn <- jsonlite::toJSON
      } else if (format == "csv") {
        type <- "text/csv; charset=UTF-8"
        sfn <- readr::format_csv
      } else if (format == "rds") {
        type <- "application/rds"
        sfn <- function(x) base::serialize(x, NULL)
      }
      val <- sfn(val)
      res$setHeader("Content-Type", type)
      res$body <- val
      res$toResponse()
    }, error = function(err) {
      errorHandler(req, res, err)
    })
  }
}

register_serializer("switch", serializer_switch)

#* Return a data frame of random values
#* @param n size of data frame
#* @param format one of "json", "csv", or "rds"
#* @serializer switch
#* @get /random_df
function(n = 10, format = "json") {
  out <- data.frame(value = rnorm(n))
  attr(out, "serialize_format") <- format
  out
}
Noam Ross
  • 5,969
  • 5
  • 24
  • 40
  • 1
    I was going crazy trying to implement it. Thank you very much for this clear and helpful example! – Adrià Mar 29 '21 at 11:03
  • 1
    More and simpler options here: https://community.rstudio.com/t/switching-plumber-serialization-type-based-on-url-arguments/98535 – Noam Ross Mar 30 '21 at 13:37