Ok. There's more setup, but I think it has legs for adding to plumber.
This approach is modeled after as_attachment() where it only adjusts the return value and not the res object.
Additions:
- Register a "dynamic" serializer that will create the serializer on the fly
- The serializers are being created each time, which is slower than @meztez's solution above, but I don't think it's noticeable
- We could add more checks such as "type not allowed" by passing in allowed types to
#' @serializer dynamic list(allowed = c("json", "csv", "rds")) to help prevent trying to serializer to a bad type.
- Define a function (
dynamic_ser()) that can return a value that is understood by the "dynamic" serializer
library(plumber)
# Routes with `dynamic` serializer should return the result of this function
dynamic_ser <- function(value, type, ...) {
structure(
class = "serializer_dynamic_payload",
list(
value = value,
type = type,
args = list(...)
)
)
}
register_serializer(
"dynamic",
function(...) {
ellipsis::check_dots_empty()
function(val, req, res, errorHandler) {
if (!inherits(val, "serializer_dynamic_payload")) {
stop("Value returned from route did not return the result of `dynamic_ser(value, type)`")
}
# extract info
value <- val$value
type <- val$type
args <- val$args
# If this was submitted as a PR, this value is available within the plumber package
ser_factory <- plumber:::.globals$serializers[[type]]
if (!is.function(ser_factory)) {
stop("Dynamic serializer of type ", type, " not found. See `registered_serializers()` for known types.")
}
# generate serializer
ser <- do.call(ser_factory, args)
# serialize
ser(value, req, res, errorHandler)
}
}
)
#./plumber.R
#* @apiTitle Example Plumber API with dynamic serializer
#* Return a data frame of random values
#* @param n size of data frame
#* @param format one of "json", "csv", or "rds"
#* @get /random_df
#* @serializer dynamic
function(n = 10, format = c("json", "csv", "rds"), res) {
format <- match.arg(format)
value <- data.frame(value = rnorm(n))
switch(format,
"json" = {
# able to pass through arguments
dynamic_ser(value, "json", auto_unbox = TRUE)
},
# default case
{
dynamic_ser(
# also download as a file!
as_attachment(value, filename = paste0("random_data.", format)),
format
)
}
)
}
Thoughts on this approach? Or should we promote using the serializer dictionary?
The serializer dictionary approach could also have extra values in the list() to support serializers with different arguments. Ex: json1 has auto_unbox = TRUE and json2 has auto_unbox = FALSE. So there is no functional advantage of either approach. The serializer dictionary approach also works with returning as_attachment(value)` objects. You as the author would have to know which ones to prep.