R POST response using Plumber API converts 10-decimal-places double (desired precision) to 4-decimal-places double at client side
Asked Answered
K

2

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

Khania answered 21/3, 2019 at 18:51 Comment(0)
C
8

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)

Caffrey answered 21/3, 2019 at 19:58 Comment(7)
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?Khania
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.4Khania
@Khania I think those have only been added in the development version, 0.5.0, which is not on CRAN yet.Caffrey
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?Khania
@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.Caffrey
Thanks Brian. #' @serializer json list(digits = 12) works on Plumber API 0.4.7Khania
"This wasn't terribly well-documented" is the understatement of the year... Thank you so much for your reply, it helped me SOO much...!Caleb
B
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
Boling answered 4/5, 2023 at 14:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.