5

Platform: - AWS Instance with 16 cores and 128 GIG RAM. - Redhat Enterprise 7.5. - R. - RStudio Server. - Plumber API exposes R functions as Web Service endpoints. - Client side is Excel VBA.

Problem: - Data table with different types of columns including double, integers, and strings data. - Right before R endpoint function sends the response (the table) and when I check the double data in the data table, all the entries are between 6 and 10 decimal-place long. - As soon as the table arrives in JSON format at the client side, 99% of the double columns are rounded to 4 decimal-place long.

Any idea what could be the problem - why do the doubles get rounded and where does the rounding take place and how can I prevent that? - I tried different request header settings and it did not work. - I tried to send the impacted double column/s as a vector/s or list/s but I get the same "enforced" rounding.

Thanks in advance

Tiger
  • 87
  • 7

2 Answers2

7

This wasn't terribly well-documented, but it turns out it's a result of using the defaults in the jsonlite::toJSON serializer (digits = 4). There's some details here:

https://www.rplumber.io/articles/rendering-output.html

I don't see how to pass an argument into that from your parameterization, but here's a workaround:

library(plumber)

#* @apiTitle A Test API

#* Run a simple function
#* @get /

function(req, res) {
  x <- rnorm(1)
  res$body <- jsonlite::toJSON(x, digits = NA)
  res
}


# plumb("plumber_1.R")$run(port = 5762)
# Save this file as e.g. "plumber_1.R" and run the commented line

Then you should be able to get a response like this:

library(httr)
y <- GET("http://127.0.0.1:5762/")
content(y, as = "text")
[1] "[-0.982448323838634]"

So whatever the result of your function is, pre-serialize it using jsonlite::toJSON(..., digits = NA), and store it directly in the body of the response, then return the response object.


It turns out there is a "correct" way of doing this, which I found out by filing this as a GitHub issue https://github.com/trestletech/plumber/issues/403. However, it looks like this version isn't on CRAN yet, so you can use the fix above in the meantime.

In your API definition, specify the serializer like this:

#' @serializer json list(digits = 12)

or for json specificially

#' @json(digits = 12)

Brian
  • 7,900
  • 1
  • 27
  • 41
  • Thanks Brian very much. But is there a better way of doing this instead of serializing the response myself? For example, is there a setting or some function in Plumber API I can call or manipulate to allow me to get 10 digits? – Tiger Mar 21 '19 at 20:37
  • Thanks Brian very much. It did not work. I tried both but they did not work (#' @serializer json list(digits = 12) or #' @json(digits = 12)). The double still get rounded. Could it be a version issue? I have Plumber 0.4.4 – Tiger Mar 21 '19 at 21:47
  • @Tiger I think those have only been added in the development version, 0.5.0, which is not on CRAN yet. – Brian Mar 21 '19 at 21:59
  • Thanks Brian. One last question while we are on the topic. Sometimes when I send a request to a Plumber endpoint, some characters do not pass through Plumber serialization smoothly and they cause the endpoint to not see the request body. Are you familiar with issue and if yes what would be the best approach? – Tiger Mar 21 '19 at 22:09
  • @Tiger, I haven't seen that with plumber but I have with other APIs. I think it's worth trying to isolate which characters cause the crash and opening a new question on here. – Brian Mar 21 '19 at 22:12
  • 2
    Thanks Brian. #' @serializer json list(digits = 12) works on Plumber API 0.4.7 – Tiger Mar 21 '19 at 22:40
  • "This wasn't terribly well-documented" is the understatement of the year... Thank you so much for your reply, it helped me SOO much...! – shghm Jul 14 '23 at 12:36
1

If you want to set this for all routes, you can also use the following (in plumber.R

#* PI
#* @get /pi
function() {
  pi
}

#* e
#* @get /e
function() {
  exp(1)
}

# Below is "new" and solves this
#* @plumber
function(pr) {
  pr <- pr |>
    # Overwrite the default serializer to return more digits
    pr_set_serializer(serializer_json(digits = 10))
}

Which returns

# print 10 digits
options(digits = 10)

# returned JSON has 10 digits
httr::content(httr::GET("http://127.0.0.1:8850/pi"), as = "text")
#> [1] "[3.1415926536]"

# automatically converting to R returns 10 digits
httr::content(httr::GET("http://127.0.0.1:8850/pi"))
#> [[1]]
#> [1] 3.1415926536
David
  • 9,216
  • 4
  • 45
  • 78