Skip to content

Commit 6b08573

Browse files
authored
Cache the result of resp_body_json() (#351)
Part of #341.
1 parent a04dc72 commit 6b08573

File tree

5 files changed

+56
-5
lines changed

5 files changed

+56
-5
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# httr2 (development version)
22

3+
* `resp_body_json()` and `resp_body_xml()` now caches the parsed values so
4+
that you can use them repeatedly without worrying about the performance cost.
5+
36
* `req_url_query()` gains a `.multi` parameter that controls what happens when
47
you supply multiple values in a vector. The default will continue to error
58
but you can use `.multi = "comma"` to separate with commas, `"pipe"` to

R/resp-body.R

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
#'
1111
#' `resp_body_json()` and `resp_body_xml()` check that the content-type header
1212
#' is correct; if the server returns an incorrect type you can suppress the
13-
#' check with `check_type = FALSE`.
13+
#' check with `check_type = FALSE`. These two functions also cache the parsed
14+
#' object so the second and subsequent calls are low-cost.
1415
#'
1516
#' @param resp A response object.
1617
#' @returns
@@ -77,6 +78,11 @@ resp_body_string <- function(resp, encoding = NULL) {
7778
#' @rdname resp_body_raw
7879
#' @export
7980
resp_body_json <- function(resp, check_type = TRUE, simplifyVector = FALSE, ...) {
81+
key <- body_cache_key("json", simplifyVector = simplifyVector, ...)
82+
if (env_has(resp$cache, key)) {
83+
return(resp$cache[[key]])
84+
}
85+
8086
check_response(resp)
8187
check_installed("jsonlite")
8288
resp_check_content_type(
@@ -87,7 +93,8 @@ resp_body_json <- function(resp, check_type = TRUE, simplifyVector = FALSE, ...)
8793
)
8894

8995
text <- resp_body_string(resp, "UTF-8")
90-
jsonlite::fromJSON(text, simplifyVector = simplifyVector, ...)
96+
resp$cache[[key]] <- jsonlite::fromJSON(text, simplifyVector = simplifyVector, ...)
97+
resp$cache[[key]]
9198
}
9299

93100
#' @rdname resp_body_raw
@@ -107,6 +114,12 @@ resp_body_html <- function(resp, check_type = TRUE, ...) {
107114
#' @rdname resp_body_raw
108115
#' @export
109116
resp_body_xml <- function(resp, check_type = TRUE, ...) {
117+
key <- body_cache_key("xml", ...)
118+
if (env_has(resp$cache, key)) {
119+
return(resp$cache[[key]])
120+
}
121+
122+
110123
check_response(resp)
111124
check_installed("xml2")
112125
resp_check_content_type(
@@ -116,5 +129,11 @@ resp_body_xml <- function(resp, check_type = TRUE, ...) {
116129
check_type = check_type
117130
)
118131

119-
xml2::read_xml(resp$body, ...)
132+
resp$cache[[key]] <- xml2::read_xml(resp$body, ...)
133+
resp$cache[[key]]
134+
}
135+
136+
body_cache_key <- function(prefix, ...) {
137+
key <- hash(list(...))
138+
paste0(prefix, "-", substr(key, 1, 10))
120139
}

R/resp.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ new_response <- function(method,
9090
url = url,
9191
status_code = status_code,
9292
headers = headers,
93-
body = body
93+
body = body,
94+
cache = new_environment()
9495
),
9596
class = "httr2_response"
9697
)

man/resp_body_raw.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-resp-body.R

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,33 @@ test_that("can retrieve parsed body", {
3030
expect_s3_class(resp_body_xml(resp), "xml_document")
3131
})
3232

33+
test_that("resp_body_json stores parsed result", {
34+
resp <- request_test("/json") %>% req_perform()
35+
json1 <- resp_body_json(resp)
36+
# check it's saved
37+
expect_length(resp$cache, 1)
38+
39+
# check it's not recomputed
40+
json2 <- resp_body_json(resp)
41+
expect_true(is_reference(json2, json1))
42+
43+
# check the arguments matter
44+
json3 <- resp_body_json(resp, simplifyVector = TRUE)
45+
expect_false(is_reference(json3, json1))
46+
expect_length(resp$cache, 2)
47+
})
48+
49+
test_that("resp_body_xml stores parsed result", {
50+
resp <- request_test("/xml") %>% req_perform()
51+
xml1 <- resp_body_xml(resp)
52+
# check it's saved
53+
expect_length(resp$cache, 1)
54+
55+
# check it's not recomputed
56+
xml2 <- resp_body_xml(resp)
57+
expect_true(is_reference(xml2, xml1))
58+
})
59+
3360
test_that("content types are checked", {
3461
expect_snapshot(error = TRUE, {
3562
request_test("/xml") %>% req_perform() %>% resp_body_json()

0 commit comments

Comments
 (0)