JSON array in "body" parameter of httr::POST() - r

I want to send a post request with a few variables in the body, using the httr package.
What the body would look like if it was in JSON format:
{a:"1", b:"2", c:[{d:"3", e:"4"}]}
What I tried with httr::POST()
r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = list(d=3, e=4)))
The error I got:
Error in curl::handle_setform(handle, .list = req$fields) :
Unsupported value type for form field 'c'.
How would I need to structure my POST() statement to send it in the format that I want mentioned above?
EDIT: On trying #renny's solution (I added verbose() for viewability) i.e. the following line
r <- POST("http://httpbin.org/post", body = json_array, encode="json", verbose())
I am able to observe that the JSON that's generated in the output is of the following format:
{"post":{"a":1,"b":2,"c":{"d":3,"e":4}}}
As you can see, the "c" variable does not have [] around it and there is a "post" variable. The following is what I want.
{"a":1,"b":2,"c":[{"d":3,"e":4}]}

I know it is an old question, but maybe someone will end up here like me. The problem was a missing list.
To create a json array instead of an object, list must be unnamed. So in your example:
> json_array <- list(a = 1, b = 2, c = list(list(d=3, e=4)))
> jsonlite::toJSON(json_array)
{"a":[1],"b":[2],"c":[{"d":[3],"e":[4]}]}
# auto_unbox extracts values from unnecessary arrays, not explicit lists
> jsonlite::toJSON(json_array, auto_unbox = T)
{"a":1,"b":2,"c":[{"d":3,"e":4}]}
Then you will not need to use jsonlite, since encode does the work:
httr::POST("http://httpbin.org/post", body = json_array, encode="json")
returning the response
{
"args": {},
"data": "{\"a\":1,\"b\":2,\"c\":[{\"d\":3,\"e\":4}]}",
"files": {},
"form": {},
"headers": {
"Accept": "application/json, text/xml, application/xml, */*",
"Accept-Encoding": "deflate, gzip",
"Content-Length": "33",
"Content-Type": "application/json",
...
}

library(httr)
json_array <- list(
post = list(a = 1, b = 2, c = list(d=3, e=4))
)
r <- POST("http://httpbin.org/post", body = json_array, encode="json")
app_data <- content(r)
Try this.
This might work out!

So the solution to this problem that I had to use was a JSON string in the body parameter.
If for example, the following is the JSON string under consideration:
json <- {"a":1,"b":2,"c":[{"d":3,"e":4}]}
I had to pass this JSON string as the value for the "body" parameter for httr::POST()
So the function call would look like:
r <- POST(url=url, body=json, encode="json", verbose())

Related

R Plumber API - programmatically specify JSON schema for request

I'm trying to build a plumber API in R. Have started with this example from the docs...
pr() %>%
pr_post("/echo", function(req, res) {
if (is.null(req$body)) return("No input")
list(
input = req$body
)
}) %>%
pr_run(port = 8080)
API starts OK. However, I want my handler function to use JSON in the body of the request as inputs.
Is it possible to programmatically define a JSON schema such that it's populated as the example in the swagger docs for the API?
Thanks.
Looks like this post Plumber: getting request body/schema in UI has the solution.
This is (unless anyone can tell me it's bad practice) the example I was looking for...
v <- data.frame(a = c(2, 3),
b = c(3, 4))
pr() %>%
pr_post("/add_up", function(input = v) {
input <- input[[1]]
return(list(rowSums(input)))
}) %>%
pr_run(port = 8080)
This gives the following example JSON in the swagger docs...
{
"input": [
[
{
"a": 2,
"b": 3
},
{
"a": 3,
"b": 4
}
]
]
}
...and returns the following response...
[
[
5,
7
]
]
Can anyone offer any improvement? Might be nice to remove the 'input' from the JSON schema if possible.

testthat 'expect_equal' returns an error - it isn't true though the actual output and exepected outputs are the same

I'm writing a testscript to test whether some modifications has been done according to my logic. My exepected output and actual output are json file which are exactly the same. My aim is to check whether the actual output is equal to the expected output. First my function is this, It has to read the json file from the location and it has to replace the parentTCode characters with null as we want only numbers in the parentTCode.
LoadData = function(inputPath) {
options(encoding = "UTF-8")
if(file.exists(inputPath)) {
setting = read_json(path = inputPath, simplifyVector = TRUE)
setting$ParentTCode = stri_replace(str = setting$ParentTCode, replacement = "", fixed = "T")
} else {
stop(paste0("There's no setting json file at ", inputPath))
}
return(setting)
}
My test script is this
test_that(desc = "Test for 'LoadData' Condition 1",
code = {
filePath = "./Test/Data/Input/setting.json"
expected_output = "./Test/Data/expected_output.json"
expect_equal(LoadData(inputPath = filePath), expected_output)
}
)
I'm confused as why this is throwing an error like this when both the actual and expected outputs are the same.
Error: LoadSettingJsonLDG(inputPath = filePath) not equal to `expected_output`.
Modes: list, character
names for target but not for current
Length mismatch: comparison on first 1 components
Component 1: 1 string mismatch
I will attach my sample of json file here.It is as follows
{
"ParentTCode": ["T2802"],
"Code": ["0001"],
"DataType": ["Diva"],
"FileExtention": [null],
"Currency": [false],
"OriginalUnit": [1000],
"ExportUnit": [1000000]
}
This is the input file for LoadData function. The output would look like this
{
"ParentTCode": ["2802"],
"Code": ["0001"],
"DataType": ["Diva"],
"FileExtention": [null],
"Currency": [false],
"OriginalUnit": [1000],
"ExportUnit": [1000000]
}
If anyone could help me I would be so glad. Thanks in advance.
The second argument to your expect_equal call is character, length 1, which is the path pointing to a file that contains what you expect your output to be. Since the first argument is a list, it should come as no surprise that a character and list are not equal.
I think you intend to compare against the parsed contents of that file. If you replace your test with:
test_that(desc = "Test for 'LoadData' Condition 1",
code = {
filePath = "./Test/Data/Input/setting.json"
expected_output = "./Test/Data/expected_output.json"
expect_equal(LoadData(inputPath = filePath),
fromJSON(expected_output))
}
)
it should work.

How to provide a Map() via httr

I have an api which calls for one of it's body to be a map, e.g.
{
"functionName": "",
"parameters": "Map[string,?]"
}
How do I do this with the R library httr? My instiction was to provide a nested list along the lines of
httr::POST(..., body = list("functionName" = "copy", "parameters" = list(a = 1, b = 2)))
This delivers an error, with message "Error in curl::handle_setform(handle, .list = req$fields) :
Unsupported value type for form field 'parameters'."
I have no real idea how to construct an object otherwise that will be accepted.
Apologies for the lack of a reprex for this question, the API is private, but hopefully my problem is simple enough that someone can find the correct syntax.
I discovered the solution to this, which is to use jsonlist::toJSON() to construct the body. For example
body = jsonlite::toJSON(
list(
"functionName" = "Rename",
"parameters" = (list(object = "pathtofile",
newName = "myfile",
newExtension = ".csv")
)),
auto_unbox = TRUE
)
This constructs a json object in the expected format, which I don't think httr can do on it's own.

Using httr to place orders through BitMex API

I'm trying to use the httr R package to place orders on BitMex through their API.
I found some guidance over here, and after specifying both my API key and secret in respectively the objects K and S, I've tried the following
verb <- 'POST'
expires <- floor(as.numeric(Sys.time() + 10000))
path <- '/api/v1/order'
data <- '{"symbol":"XBTUSD","price":4500,"orderQty":10}'
body <- paste0(verb, path, expires, data)
signature <- hmac(S, body, algo = 'sha256')
body_l <- list(verb = verb, expires = expires, path = path, data = data)
And then both:
msg <- POST('https://www.bitmex.com/api/v1/order', encode = 'json', body = body_l, add_headers('api-key' = K, 'api-signature' = signature, 'api-expires' = expires))
and:
msg <- POST('https://www.bitmex.com/api/v1/order', body = body, add_headers('api-key' = K, 'api-signature' = signature, 'api-expires' = expires))
Give me the same error message when checked:
rawToChar(msg$content)
[1] "{\"error\":{\"message\":\"Signature not valid.\",\"name\":\"HTTPError\"}}"
I've tried to set it up according to how BitMex explains to use their API, but I appear to be missing something. They list out a couple of issues that might underly my invalid signature issue, but they don't seem to help me out. When following their example I get the exact same hashes, so that seems to be in order.
bit late to the party here but hopefully this helps!
Your POST call just needs some minor changes:
add content_type_json()
include .headers = c('the headers') in add_headers(). See example below:
library(httr)
library(digest)
S <- "your api secret"
K <- "your api key"
verb <- 'POST'
expires <- floor(as.numeric(Sys.time() + 10))
path <- '/api/v1/order'
data <- '{"symbol":"XBTUSD","price":4500,"orderQty":10}'
body <- paste0(verb, path, expires, data)
signature <- hmac(S, body, algo = 'sha256')
msg <- POST('https://www.bitmex.com/api/v1/order',
encode = 'json',
body = data,
content_type_json(),
add_headers(.headers = c('api-key' = K,
'api-signature' = signature,
'api-expires' = expires)))
content(msg, "text")
I have a package on CRAN - bitmexr - that provides a wrapper around the majority of BitMEX's API endpoints that you might be interested in. Still quite a "young" package so I would welcome any feedback!

Retain information about requested URL when using curl::curl_fetch_multi

I'm using the following code to perform multiple simultaneous requests.
urls <- c("https://httpbin.org/status/301", "https://httpbin.org/status/302", "https://httpbin.org/status/200")
result <- list()
p <- curl::new_pool(total_con = 10, host_con = 5, multiplex = T)
cb <- function(res) {
result <<- append(result, list(res))
cat("requested URL: ", url, "last URL: ", res$url, "\n\n")
}
for (url in urls) {
curl::curl_fetch_multi(url, done = cb, handle = curl::new_handle(failonerror = F, nobody = F, followlocation = T, ssl_verifypeer = 0), pool = p)
}
curl::multi_run(pool = p)
As you can see, I would like to print to the console the requested URL and the URL, that finally answered with 200 ok.
The following is printed to the console:
requested URL: https://httpbin.org/status/200 last URL: https://httpbin.org/status/200
requested URL: https://httpbin.org/status/200 last URL: https://httpbin.org/get
requested URL: https://httpbin.org/status/200 last URL: https://httpbin.org/get
The requested URL in the console output is always https://httpbin.org/status/200, because it's the last URL that used in the for-loop. So, that is the wrong way to do it.
How can I retain information about the initial requested URL when using curl_fetch_multi to use it after multi_run returned? That means it would be ideal if the requested URL would be added to the res-list to query it with something like cat("requested URL: ", res$requested_url, "last URL: ", res$url, "\n\n").
I had a similar issue where I wanted to do asynchronous POST requests using curl_fetch_multi and check which requests succeeded and which failed. However, due to the structure of the POST statement (all fields are in the request body) there is no identifying information whatsoever in the response object. My solution was to generate custom callback functions which carried an identifier.
urls <- c("https://httpbin.org/status/301", "https://httpbin.org/status/302", "https://httpbin.org/status/200")
result <- list()
# create an identifier for each url
url.ids = paste0("request_", seq_along(urls))
# custom callback function generator: generate a unique function for each url
cb = function(id){
function(res){
result[[id]] <<- res
}
}
# create the list of callback functions
cbfuns = lapply(url.ids, cb)
p <- curl::new_pool(total_con = 10, host_con = 5, multiplex = T)
for (i in seq_along(urls)) {
curl::curl_fetch_multi(urls[i], done = cbfuns[[i]], handle = curl::new_handle(failonerror = F, nobody = F, followlocation = T, ssl_verifypeer = 0), pool = p)
}
curl::multi_run(pool = p)
In this example, the custom callback functions are simply used to name the elements of result:
names(result)
## [1] "request_3" "request_1" "request_2"
which can then be used to tie each response back to the original request.

Resources