Please say if I'm wrong, but I don't think one can achieve what I described with Plumber natively. I thought your example was showing me exactly how to take direct control of the json serialization. (For which I'm thinking yeah, I could have thought of that myself, but for which I'm also very grateful.)
As I understand Plumber, for a single defined route, if you want a json return, you get two options.
-
Boxed json by default or with the @json
annotation; equivalent to jsonlite::toJSON()
-
Unboxed json with the @serializer unboxedJSON
annotation; equivalent to jsonlite::toJSON(auto_unbox=TRUE)
But I want to choose which of these to use in multiple return()
statements in a single defined route. Here is my scenario for why I want to do this:
(I've not tried to generalize this, it's easier just to use actual code.)
#* @html # text/html; no additional serialization
#* @param modelspec
#* @post /create_model
function(res, modelspec) {
time_requested <- as.numeric(Sys.time())
# We verify modelspec as early as possible ====
precheck <- check_modelspec(modelspec)
# returns a list with $test, $error_code, $error_msg, and $advice fields
# precheck$test is a logical vector of tests passed
if (!all(precheck$test)) {
simple_err("Received a request for model_creation with a badly formed modelspec")
failure <- list()
failure$time_precheck_fail <- as.numeric(Sys.time())
failure$error_code <- precheck$error_code[!precheck$test]
failure$error_msg <- precheck$error_msg[!precheck$test]
failure$advice <- precheck$advice[!precheck$test]
res$status <- 400
return(jsonlite::toJSON(failure, auto_unbox = FALSE))
}
So far I've done some validation of the modelspec
object. If any tests have failed I populate a failure
list. I want to return this as consistently boxed json, regardless of whether a single test failed, or multiple tests failed.
e.g. single test failed
> toJSON(failure, auto_unbox = FALSE)
{"time_precheck_fail":[1582421348.5493],"error_code":[["mpc:18"]],"error_msg":[["base_data must have no invariant valued columns"]],"advice":[["Check your modelspec$base_data argument. Columns [1, 2, 7] are invariant."]]}
multiple tests failed
> toJSON(failure, auto_unbox = FALSE)
{"time_precheck_fail":[1582421578.9645],"error_code":[["mpc:7"],["mpc:18"]],"error_msg":[["update must be a single boolean or a single int [0|1]"],["base_data must have no invariant valued columns"]],"advice":[["Check your modelspec$req_metadata argument."],["Check your modelspec$base_data argument. Columns [1, 2, 7] are invariant."]]}
If no tests failed then we drop through the above if
condition. I now want to return an unboxed response because the elements here are always scalars, and the python devs don't have to deal with me saying "but they're actually vectors of length 1".
# some code populating a response object
# but it's always scalar so we want to return as unboxed json
res$status <- 202
return(jsonlite::toJSON(response, auto_unbox = TRUE))
> toJSON(response, auto_unbox = TRUE)
{"status":"precheck","status_url":"http://localhost:8010/maintenance/get_status?job_id=0b57236f"}
So to achieve this consistency for the dev team, I need to bypass Plumber's native serialization and take control of it myself. I thought that is what your example was Illustrating. Have I misunderstood?