JSON nest to be removed from just one list - r

I have JSON content that looks like this:
myJSON = '{
"GeneralInputs":
[
{"apples":12,"pears":41},
{"apples":13,"pears":42}
],
"Assumptions":
["jabberwocky.json"]
}'
But what I need is to remove the [ and ] around ["jabberwocky.json"]
This is dictated by the use of someone else's code downstream.
I've tried ...
https://stackoverflow.com/questions/17164715/how-to-remove-a-level-of-lists-from-a-list-of-lists but this applies to all the lists.
..and ...
before = ":[\"jabberwocky.json\"]}"
after = ":\"jabberwocky.json\"}"
str_replace(myJSON, before, after)
How do I do this on just one of the two lists??
BR

Not sure how generalizable this is, but still:
library(jsonlite)
new_json <- toJSON(fromJSON(myJSON),
auto_unbox = TRUE,
pretty = TRUE)
gives:
{
"GeneralInputs": [
{
"apples": 12,
"pears": 41
},
{
"apples": 13,
"pears": 42
}
],
"Assumptions": "jabberwocky.json"
}

You can try -
before = "[\"jabberwocky.json\"]"
after = "jabberwocky.json"
stringr::str_replace(myJSON, fixed(before), after)
#In base R use sub
#sub(before, after, myJSON, fixed = TRUE)
#[1] "{\n\"GeneralInputs\":\n[\n{\"apples\":12,\"pears\":41},\n{\"apples\":13,\"pears\":42}\n],\n\n\"Assumptions\":\njabberwocky.json\n}"
Using cat for display purpose -
cat(str_replace(myJSON, fixed(before), after))
#{
#"GeneralInputs":
#[
#{"apples":12,"pears":41},
#{"apples":13,"pears":42}
#],
#
#"Assumptions":
#jabberwocky.json
#}

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.

dealing with lists and arrays when using toJson function - in R

I am working with a pre-specified API definition, which I need to adhere to:
"myTable": {
"split": [
{
"total": 0,
"perItem": [
0,
0,
0
]
}
]
the results from my function are a list (since I am using an apply):
Export
[[1]]
[[1]]$total
[1] 13
[[1]]$perItem
1 2 3
5 7 0
but when I convert this to .json, it is not the same format as the predefined API definition:
toJSON(Export)
[{"total":[13],"perPlan":[5,7,0]}]
I was wondering how I can convert the output of the apply to have the predefined API?
I tried converting this to array:
toJSON(array(Export), simplify = TRUE)
[{"total":[13],"perPlan":[5,7,0]}]
but this still has the additional [] around the content of total.
According to the API specification your input should also "embed" your data into this split and mytable list, which can be done with:
Export <- list(list(total = 13,
perItem = c(5, 7, 0)))
for_JSON <- list(mytable = list(split = Export))
toJSON(for_JSON, simplify = TRUE, pretty = TRUE)
which gives:
{
"mytable": {
"split": [
{
"total": 13,
"perItem": [5, 7, 0]
}
]
}
}
This looks like what the API wants.

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.

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

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())

In R how to sort array of R6 objects

How can I sort/order R6 objects based on an own function value or a compare function value?
I have made up a little example with rectangles that I would like to sort by their area:
library('R6')
Rectangle <- R6Class(
"Rectangle",
public = list(
initialize = function(width, height) {
private$width = width
private$height = height
},
get_area = function(){
private$width*private$height
}
),
private = list(
width = NULL,
height = NULL
)
)
array_of_rects = c( Rectangle$new(7,3), Rectangle$new(5,2), Rectangle$new(3,4))
I would like to sort array_of_rects by their area given by the get_area() function.
I tried different things like:
`>.Rectangle` <- function(e1, e2) { e1[[1]]$get_area() > e2[[1]]$get_area() }
`==.Rectangle` <- function(e1, e2) { e1[[1]]$get_area() == e2[[1]]$get_area() }
sort(array_of_rects)
but without luck (I get an 'x' must be atomic error message).
I tried without the [[1]] (like this e1$get_area()) but this didn't work either.
Searched around but haven't found anything leading me to a solution.
Any suggestions? Thanks in advance!
Well, inspired by https://stackoverflow.com/a/23647092/1935801
I found the following nice and elegant solution
area = function(rect){ rect$get_area() }
sorted_rects = array_of_rects[ order( sapply(array_of_rects, FUN = area) ) ]
At the end of the day works with R6 like with any other class/object.

Resources