diff --git a/NAMESPACE b/NAMESPACE index 012bb32..422581f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -39,9 +39,11 @@ export(resp_body_tsv) export(resp_parse) export(resp_tidy) export(resp_tidy_json) +export(resp_tidy_json_tibblify) export(resp_tidy_unknown) export(tidy_policy_body_auto) export(tidy_policy_json) +export(tidy_policy_json_tibblify) export(tidy_policy_prepare) export(tidy_policy_unknown) export(url_normalize) diff --git a/R/resp_tidy_json.R b/R/resp_tidy_json.R index 143eee1..a94470e 100644 --- a/R/resp_tidy_json.R +++ b/R/resp_tidy_json.R @@ -1,15 +1,16 @@ -#' Extract and clean a JSON API response +#' Extract and optionally subset a JSON API response #' -#' Parse the body of a response with [httr2::resp_body_json()], extract a named -#' subset of that body, and tidy the result with [tibblify::tibblify()]. +#' Parse the body of a response with [httr2::resp_body_json()] and optionally +#' extract a named subset of that body. #' #' @inheritParams .shared-params +#' @inheritParams httr2::resp_body_json #' -#' @returns The tibblified response body. +#' @returns The parsed response body, or `NULL` for an empty result. #' @family opinionated response parsers #' @export #' -#' @examplesIf rlang::is_installed("tibblify") +#' @examples #' resp <- httr2::response_json( #' body = list(list(id = 1, name = "Alice"), list(id = 2, name = "Bob")) #' ) @@ -20,28 +21,13 @@ #' body = list(data = list(list(id = 1), list(id = 2))) #' ) #' resp_tidy_json(resp_nested, subset_path = "data") -resp_tidy_json <- function( - resp, - spec = NULL, - unspecified = "list", - subset_path = NULL -) { - rlang::check_installed( - "tibblify", - "to tidy the JSON response body." - ) - # Let httr2 and tibblify validate their respective inputs, but check ours. +resp_tidy_json <- function(resp, subset_path = NULL, simplifyVector = FALSE) { + # Let httr2 validate its own inputs, but check ours. subset_path <- stbl::to_chr(subset_path) - result <- httr2::resp_body_json(resp) + result <- httr2::resp_body_json(resp, simplifyVector = simplifyVector) result <- purrr::pluck(result, !!!subset_path) if (length(result)) { - return( - tibblify::tibblify( - result, - spec = spec, - unspecified = unspecified - ) - ) + return(result) } return(NULL) } @@ -51,22 +37,18 @@ resp_tidy_json <- function( #' Create a reusable tidy policy that applies [resp_tidy_json()]. #' #' @inheritParams .shared-params +#' @inheritParams httr2::resp_body_json #' @returns A list with class `"nectar_tidy_policy"` and elements `tidy_fn` and #' `tidy_args`. #' @family opinionated response parsers #' @export #' -#' @examplesIf rlang::is_installed("tibblify") +#' @examples #' tidy_policy_json(subset_path = "data") -tidy_policy_json <- function( - spec = NULL, - unspecified = "list", - subset_path = NULL -) { +tidy_policy_json <- function(subset_path = NULL, simplifyVector = FALSE) { tidy_policy_prepare( resp_tidy_json, - spec = spec, - unspecified = unspecified, - subset_path = subset_path + subset_path = subset_path, + simplifyVector = simplifyVector ) } diff --git a/R/resp_tidy_json_tibblify.R b/R/resp_tidy_json_tibblify.R new file mode 100644 index 0000000..a4edb77 --- /dev/null +++ b/R/resp_tidy_json_tibblify.R @@ -0,0 +1,73 @@ +#' Extract and clean a JSON API response with tibblify +#' +#' Parse the body of a response with [resp_tidy_json()] and tidy the result with +#' [tibblify::tibblify()]. +#' +#' @inheritParams .shared-params +#' +#' @returns The tibblified response body. +#' @family opinionated response parsers +#' @export +#' +#' @examplesIf rlang::is_installed("tibblify") +#' resp <- httr2::response_json( +#' body = list(list(id = 1, name = "Alice"), list(id = 2, name = "Bob")) +#' ) +#' resp_tidy_json_tibblify(resp) +#' +#' # Extract a nested subset of the response body +#' resp_nested <- httr2::response_json( +#' body = list(data = list(list(id = 1), list(id = 2))) +#' ) +#' resp_tidy_json_tibblify(resp_nested, subset_path = "data") +resp_tidy_json_tibblify <- function( + resp, + spec = NULL, + unspecified = "list", + subset_path = NULL +) { + result <- resp_tidy_json( + resp = resp, + subset_path = subset_path, + simplifyVector = FALSE + ) + if (length(result)) { + rlang::check_installed( + "tibblify", + "to tidy the JSON response body." + ) + return( + tibblify::tibblify( + result, + spec = spec, + unspecified = unspecified + ) + ) + } + return(NULL) +} + +#' A policy to parse a response body as JSON +#' +#' Create a reusable tidy policy that applies [resp_tidy_json_tibblify()]. +#' +#' @inheritParams .shared-params +#' @returns A list with class `"nectar_tidy_policy"` and elements `tidy_fn` and +#' `tidy_args`. +#' @family opinionated response parsers +#' @export +#' +#' @examplesIf rlang::is_installed("tibblify") +#' tidy_policy_json_tibblify(subset_path = "data") +tidy_policy_json_tibblify <- function( + spec = NULL, + unspecified = "list", + subset_path = NULL +) { + tidy_policy_prepare( + resp_tidy_json_tibblify, + spec = spec, + unspecified = unspecified, + subset_path = subset_path + ) +} diff --git a/README.Rmd b/README.Rmd index 06c1e06..8579f1f 100644 --- a/README.Rmd +++ b/README.Rmd @@ -46,7 +46,7 @@ req <- req_prepare( query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), - tidy_policy = tidy_policy_json(subset_path = c("message", "items")), + tidy_policy = tidy_policy_json_tibblify(subset_path = c("message", "items")), pagination_fn = iterate_with_json_cursor( param_name = "cursor", next_cursor_path = c("message", "next-cursor") diff --git a/README.md b/README.md index 5f06ccb..1c42449 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ req <- req_prepare( query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), - tidy_policy = tidy_policy_json(subset_path = c("message", "items")), + tidy_policy = tidy_policy_json_tibblify(subset_path = c("message", "items")), pagination_fn = iterate_with_json_cursor( param_name = "cursor", next_cursor_path = c("message", "next-cursor") diff --git a/man/req_tidy_policy.Rd b/man/req_tidy_policy.Rd index 4fee397..c335456 100644 --- a/man/req_tidy_policy.Rd +++ b/man/req_tidy_policy.Rd @@ -49,9 +49,11 @@ Other opinionated request functions: Other opinionated response parsers: \code{\link[=resp_tidy]{resp_tidy()}}, \code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, \code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, \code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, \code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} } diff --git a/man/resp_tidy.Rd b/man/resp_tidy.Rd index 193f78b..aed112f 100644 --- a/man/resp_tidy.Rd +++ b/man/resp_tidy.Rd @@ -41,9 +41,11 @@ resp_tidy(resp) Other opinionated response parsers: \code{\link[=req_tidy_policy]{req_tidy_policy()}}, \code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, \code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, \code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, \code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} } diff --git a/man/resp_tidy_json.Rd b/man/resp_tidy_json.Rd index a4cf377..3104a51 100644 --- a/man/resp_tidy_json.Rd +++ b/man/resp_tidy_json.Rd @@ -2,41 +2,32 @@ % Please edit documentation in R/resp_tidy_json.R \name{resp_tidy_json} \alias{resp_tidy_json} -\title{Extract and clean a JSON API response} +\title{Extract and optionally subset a JSON API response} \usage{ -resp_tidy_json(resp, spec = NULL, unspecified = "list", subset_path = NULL) +resp_tidy_json(resp, subset_path = NULL, simplifyVector = FALSE) } \arguments{ \item{resp}{(\code{httr2_response}) A single \code{\link[httr2:response]{httr2::response()}} object (as returned by \code{\link[httr2:req_perform]{httr2::req_perform()}}).} -\item{spec}{(\code{tspec} or \code{NULL}) A specification used by -\code{\link[tibblify:tibblify]{tibblify::tibblify()}} to parse the extracted body of \code{resp}. When \code{spec} -is \code{NULL} (the default), \code{\link[tibblify:tibblify]{tibblify::tibblify()}} will attempt to guess a -spec.} - -\item{unspecified}{(\verb{length-1 character}) A string that describes what -happens if the extracted body of \code{resp} contains fields that are not -specified in \code{spec}. While \code{\link[tibblify:tibblify]{tibblify::tibblify()}} defaults to \code{NULL} for -this value, we set it to \code{list} so that the body will still parse when -\code{resp} contains extra data without throwing errors.} - \item{subset_path}{(\code{character}) An optional vector indicating the path to the "real" object within the body of \code{resp}. For example, many APIs return a body with information about the status of the response, cache information, perhaps pagination information, and then the actual data in a field such as \code{data}. If the desired part of the response body is in \code{data$objects}, the value of this argument should be \code{c("data", "object")}.} + +\item{simplifyVector}{Should JSON arrays containing only primitives (i.e. +booleans, numbers, and strings) be caused to atomic vectors?} } \value{ -The tibblified response body. +The parsed response body, or \code{NULL} for an empty result. } \description{ -Parse the body of a response with \code{\link[httr2:resp_body_json]{httr2::resp_body_json()}}, extract a named -subset of that body, and tidy the result with \code{\link[tibblify:tibblify]{tibblify::tibblify()}}. +Parse the body of a response with \code{\link[httr2:resp_body_json]{httr2::resp_body_json()}} and optionally +extract a named subset of that body. } \examples{ -\dontshow{if (rlang::is_installed("tibblify")) withAutoprint(\{ # examplesIf} resp <- httr2::response_json( body = list(list(id = 1, name = "Alice"), list(id = 2, name = "Bob")) ) @@ -47,15 +38,16 @@ resp_nested <- httr2::response_json( body = list(data = list(list(id = 1), list(id = 2))) ) resp_tidy_json(resp_nested, subset_path = "data") -\dontshow{\}) # examplesIf} } \seealso{ Other opinionated response parsers: \code{\link[=req_tidy_policy]{req_tidy_policy()}}, \code{\link[=resp_tidy]{resp_tidy()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, \code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, \code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, \code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} } diff --git a/man/resp_tidy_json_tibblify.Rd b/man/resp_tidy_json_tibblify.Rd new file mode 100644 index 0000000..952ad82 --- /dev/null +++ b/man/resp_tidy_json_tibblify.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_tidy_json_tibblify.R +\name{resp_tidy_json_tibblify} +\alias{resp_tidy_json_tibblify} +\title{Extract and clean a JSON API response with tibblify} +\usage{ +resp_tidy_json_tibblify( + resp, + spec = NULL, + unspecified = "list", + subset_path = NULL +) +} +\arguments{ +\item{resp}{(\code{httr2_response}) A single \code{\link[httr2:response]{httr2::response()}} object (as +returned by \code{\link[httr2:req_perform]{httr2::req_perform()}}).} + +\item{spec}{(\code{tspec} or \code{NULL}) A specification used by +\code{\link[tibblify:tibblify]{tibblify::tibblify()}} to parse the extracted body of \code{resp}. When \code{spec} +is \code{NULL} (the default), \code{\link[tibblify:tibblify]{tibblify::tibblify()}} will attempt to guess a +spec.} + +\item{unspecified}{(\verb{length-1 character}) A string that describes what +happens if the extracted body of \code{resp} contains fields that are not +specified in \code{spec}. While \code{\link[tibblify:tibblify]{tibblify::tibblify()}} defaults to \code{NULL} for +this value, we set it to \code{list} so that the body will still parse when +\code{resp} contains extra data without throwing errors.} + +\item{subset_path}{(\code{character}) An optional vector indicating the path to +the "real" object within the body of \code{resp}. For example, many APIs return +a body with information about the status of the response, cache +information, perhaps pagination information, and then the actual data in a +field such as \code{data}. If the desired part of the response body is in +\code{data$objects}, the value of this argument should be \code{c("data", "object")}.} +} +\value{ +The tibblified response body. +} +\description{ +Parse the body of a response with \code{\link[=resp_tidy_json]{resp_tidy_json()}} and tidy the result with +\code{\link[tibblify:tibblify]{tibblify::tibblify()}}. +} +\examples{ +\dontshow{if (rlang::is_installed("tibblify")) withAutoprint(\{ # examplesIf} +resp <- httr2::response_json( + body = list(list(id = 1, name = "Alice"), list(id = 2, name = "Bob")) +) +resp_tidy_json_tibblify(resp) + +# Extract a nested subset of the response body +resp_nested <- httr2::response_json( + body = list(data = list(list(id = 1), list(id = 2))) +) +resp_tidy_json_tibblify(resp_nested, subset_path = "data") +\dontshow{\}) # examplesIf} +} +\seealso{ +Other opinionated response parsers: +\code{\link[=req_tidy_policy]{req_tidy_policy()}}, +\code{\link[=resp_tidy]{resp_tidy()}}, +\code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, +\code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, +\code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, +\code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, +\code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} +} +\concept{opinionated response parsers} diff --git a/man/resp_tidy_unknown.Rd b/man/resp_tidy_unknown.Rd index 38ffbe4..0910544 100644 --- a/man/resp_tidy_unknown.Rd +++ b/man/resp_tidy_unknown.Rd @@ -35,8 +35,10 @@ Other opinionated response parsers: \code{\link[=req_tidy_policy]{req_tidy_policy()}}, \code{\link[=resp_tidy]{resp_tidy()}}, \code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, \code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, \code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} } diff --git a/man/tidy_policy_body_auto.Rd b/man/tidy_policy_body_auto.Rd index 7f11382..3b5009c 100644 --- a/man/tidy_policy_body_auto.Rd +++ b/man/tidy_policy_body_auto.Rd @@ -21,8 +21,10 @@ Other opinionated response parsers: \code{\link[=req_tidy_policy]{req_tidy_policy()}}, \code{\link[=resp_tidy]{resp_tidy()}}, \code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, \code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, \code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} } diff --git a/man/tidy_policy_json.Rd b/man/tidy_policy_json.Rd index 064c085..950f23a 100644 --- a/man/tidy_policy_json.Rd +++ b/man/tidy_policy_json.Rd @@ -4,26 +4,18 @@ \alias{tidy_policy_json} \title{A policy to parse a response body as JSON} \usage{ -tidy_policy_json(spec = NULL, unspecified = "list", subset_path = NULL) +tidy_policy_json(subset_path = NULL, simplifyVector = FALSE) } \arguments{ -\item{spec}{(\code{tspec} or \code{NULL}) A specification used by -\code{\link[tibblify:tibblify]{tibblify::tibblify()}} to parse the extracted body of \code{resp}. When \code{spec} -is \code{NULL} (the default), \code{\link[tibblify:tibblify]{tibblify::tibblify()}} will attempt to guess a -spec.} - -\item{unspecified}{(\verb{length-1 character}) A string that describes what -happens if the extracted body of \code{resp} contains fields that are not -specified in \code{spec}. While \code{\link[tibblify:tibblify]{tibblify::tibblify()}} defaults to \code{NULL} for -this value, we set it to \code{list} so that the body will still parse when -\code{resp} contains extra data without throwing errors.} - \item{subset_path}{(\code{character}) An optional vector indicating the path to the "real" object within the body of \code{resp}. For example, many APIs return a body with information about the status of the response, cache information, perhaps pagination information, and then the actual data in a field such as \code{data}. If the desired part of the response body is in \code{data$objects}, the value of this argument should be \code{c("data", "object")}.} + +\item{simplifyVector}{Should JSON arrays containing only primitives (i.e. +booleans, numbers, and strings) be caused to atomic vectors?} } \value{ A list with class \code{"nectar_tidy_policy"} and elements \code{tidy_fn} and @@ -33,17 +25,17 @@ A list with class \code{"nectar_tidy_policy"} and elements \code{tidy_fn} and Create a reusable tidy policy that applies \code{\link[=resp_tidy_json]{resp_tidy_json()}}. } \examples{ -\dontshow{if (rlang::is_installed("tibblify")) withAutoprint(\{ # examplesIf} tidy_policy_json(subset_path = "data") -\dontshow{\}) # examplesIf} } \seealso{ Other opinionated response parsers: \code{\link[=req_tidy_policy]{req_tidy_policy()}}, \code{\link[=resp_tidy]{resp_tidy()}}, \code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, \code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, \code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} } diff --git a/man/tidy_policy_json_tibblify.Rd b/man/tidy_policy_json_tibblify.Rd new file mode 100644 index 0000000..8ec25e5 --- /dev/null +++ b/man/tidy_policy_json_tibblify.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp_tidy_json_tibblify.R +\name{tidy_policy_json_tibblify} +\alias{tidy_policy_json_tibblify} +\title{A policy to parse a response body as JSON} +\usage{ +tidy_policy_json_tibblify( + spec = NULL, + unspecified = "list", + subset_path = NULL +) +} +\arguments{ +\item{spec}{(\code{tspec} or \code{NULL}) A specification used by +\code{\link[tibblify:tibblify]{tibblify::tibblify()}} to parse the extracted body of \code{resp}. When \code{spec} +is \code{NULL} (the default), \code{\link[tibblify:tibblify]{tibblify::tibblify()}} will attempt to guess a +spec.} + +\item{unspecified}{(\verb{length-1 character}) A string that describes what +happens if the extracted body of \code{resp} contains fields that are not +specified in \code{spec}. While \code{\link[tibblify:tibblify]{tibblify::tibblify()}} defaults to \code{NULL} for +this value, we set it to \code{list} so that the body will still parse when +\code{resp} contains extra data without throwing errors.} + +\item{subset_path}{(\code{character}) An optional vector indicating the path to +the "real" object within the body of \code{resp}. For example, many APIs return +a body with information about the status of the response, cache +information, perhaps pagination information, and then the actual data in a +field such as \code{data}. If the desired part of the response body is in +\code{data$objects}, the value of this argument should be \code{c("data", "object")}.} +} +\value{ +A list with class \code{"nectar_tidy_policy"} and elements \code{tidy_fn} and +\code{tidy_args}. +} +\description{ +Create a reusable tidy policy that applies \code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}. +} +\examples{ +\dontshow{if (rlang::is_installed("tibblify")) withAutoprint(\{ # examplesIf} +tidy_policy_json_tibblify(subset_path = "data") +\dontshow{\}) # examplesIf} +} +\seealso{ +Other opinionated response parsers: +\code{\link[=req_tidy_policy]{req_tidy_policy()}}, +\code{\link[=resp_tidy]{resp_tidy()}}, +\code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, +\code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, +\code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, +\code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}}, +\code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} +} +\concept{opinionated response parsers} diff --git a/man/tidy_policy_prepare.Rd b/man/tidy_policy_prepare.Rd index 429ffa5..9e8d882 100644 --- a/man/tidy_policy_prepare.Rd +++ b/man/tidy_policy_prepare.Rd @@ -28,9 +28,11 @@ Other opinionated response parsers: \code{\link[=req_tidy_policy]{req_tidy_policy()}}, \code{\link[=resp_tidy]{resp_tidy()}}, \code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, \code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, \code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_unknown]{tidy_policy_unknown()}} } \concept{opinionated response parsers} diff --git a/man/tidy_policy_unknown.Rd b/man/tidy_policy_unknown.Rd index 04537ca..27cf463 100644 --- a/man/tidy_policy_unknown.Rd +++ b/man/tidy_policy_unknown.Rd @@ -22,9 +22,11 @@ Other opinionated response parsers: \code{\link[=req_tidy_policy]{req_tidy_policy()}}, \code{\link[=resp_tidy]{resp_tidy()}}, \code{\link[=resp_tidy_json]{resp_tidy_json()}}, +\code{\link[=resp_tidy_json_tibblify]{resp_tidy_json_tibblify()}}, \code{\link[=resp_tidy_unknown]{resp_tidy_unknown()}}, \code{\link[=tidy_policy_body_auto]{tidy_policy_body_auto()}}, \code{\link[=tidy_policy_json]{tidy_policy_json()}}, +\code{\link[=tidy_policy_json_tibblify]{tidy_policy_json_tibblify()}}, \code{\link[=tidy_policy_prepare]{tidy_policy_prepare()}} } \concept{opinionated response parsers} diff --git a/tests/testthat/test-resp_tidy_json.R b/tests/testthat/test-resp_tidy_json.R index ce23daa..8fd5116 100644 --- a/tests/testthat/test-resp_tidy_json.R +++ b/tests/testthat/test-resp_tidy_json.R @@ -1,4 +1,4 @@ -test_that("resp_tidy_json fails gracefully with a bad subset_path (#40)", { +test_that("resp_tidy_json fails gracefully with a bad subset_path (#95)", { stbl::expect_pkg_error_classes( resp_tidy_json(subset_path = list(a = 1:10, b = mean), resp = NULL), package = "stbl", @@ -7,98 +7,46 @@ test_that("resp_tidy_json fails gracefully with a bad subset_path (#40)", { ) }) -test_that("resp_tidy_json returns NULL for an empty body (#40)", { +test_that("resp_tidy_json returns NULL for an empty body (#95)", { mock_response <- httr2::response_json(body = list()) expect_null(resp_tidy_json(mock_response)) }) -test_that("resp_tidy_json tidies a response (#40)", { - target_tibble <- tibble::tibble( - a = letters, - b = LETTERS, - c = 1:26 - ) - mock_response <- httr2::response_json( - body = target_tibble - ) - expect_identical( - resp_tidy_json(mock_response), - target_tibble +test_that("resp_tidy_json returns parsed JSON directly (#95)", { + target <- list( + list(a = "x", b = 1L), + list(a = "y", b = 2L) ) + mock_response <- httr2::response_json(body = target) + expect_identical(resp_tidy_json(mock_response), target) }) -test_that("resp_tidy_json subsets a response (#40)", { - target_tibble <- tibble::tibble( - a = letters, - b = LETTERS, - c = 1:26 - ) +test_that("resp_tidy_json subsets a response (#95)", { + target <- list(list(a = "x"), list(a = "y")) mock_response <- httr2::response_json( - body = list( - ok = TRUE, - data = list( - target_tibble = target_tibble - ) - ) + body = list(ok = TRUE, data = list(target = target)) ) expect_identical( - resp_tidy_json(mock_response, subset_path = c("data", "target_tibble")), - target_tibble + resp_tidy_json(mock_response, subset_path = c("data", "target")), + target ) }) -test_that("resp_tidy_json tidies a response with a spec (#40)", { - source_tibble <- tibble::tibble( - a = letters, - b = LETTERS, - c = 1:26 - ) - target_tibble <- tibble::tibble( - lc = letters, - uc = LETTERS, - n = 1:26 - ) +test_that("resp_tidy_json passes simplifyVector to httr2::resp_body_json (#95)", { + target <- data.frame(a = c("x", "y"), b = c(1, 2)) mock_response <- httr2::response_json( - body = source_tibble - ) - spec <- tibblify::tspec_df( - lc = tibblify::tib_chr("a"), - uc = tibblify::tib_chr("b"), - n = tibblify::tib_int("c"), - ) - expect_identical( - resp_tidy_json(mock_response, spec = spec), - target_tibble + body = list(list(a = "x", b = 1), list(a = "y", b = 2)) ) + expect_equal(resp_tidy_json(mock_response, simplifyVector = TRUE), target) }) -test_that("tidy_policy_json() prepares resp_tidy_json for resp_tidy() (#40, #86)", { - source_tibble <- tibble::tibble( - a = letters, - b = LETTERS, - c = 1:26 - ) - target_tibble <- tibble::tibble( - lc = letters, - uc = LETTERS, - n = 1:26 - ) - mock_response <- httr2::response_json( - body = source_tibble - ) +test_that("tidy_policy_json() prepares parser for resp_tidy() (#95)", { + target <- list(list(a = "x"), list(a = "y")) + mock_response <- httr2::response_json(body = list(data = target)) mock_response$request <- list( policies = list( - resp_tidy = tidy_policy_json( - spec = tibblify::tspec_df( - lc = tibblify::tib_chr("a"), - uc = tibblify::tib_chr("b"), - n = tibblify::tib_int("c"), - ) - ) + resp_tidy = tidy_policy_json(subset_path = "data") ) ) - expect_identical( - resp_tidy(mock_response), - target_tibble - ) + expect_identical(resp_tidy(mock_response), target) }) diff --git a/tests/testthat/test-resp_tidy_json_tibblify.R b/tests/testthat/test-resp_tidy_json_tibblify.R new file mode 100644 index 0000000..abcdf1d --- /dev/null +++ b/tests/testthat/test-resp_tidy_json_tibblify.R @@ -0,0 +1,126 @@ +test_that("resp_tidy_json_tibblify fails gracefully with a bad subset_path (#40, #95)", { + stbl::expect_pkg_error_classes( + resp_tidy_json_tibblify( + subset_path = list(a = 1:10, b = mean), + resp = NULL + ), + package = "stbl", + "coerce", + "character" + ) +}) + +test_that("resp_tidy_json_tibblify returns NULL for an empty body (#40, #95)", { + mock_response <- httr2::response_json(body = list()) + expect_null(resp_tidy_json_tibblify(mock_response)) +}) + +test_that("resp_tidy_json_tibblify tidies a response (#40, #95)", { + target_tibble <- tibble::tibble( + a = letters, + b = LETTERS, + c = 1:26 + ) + mock_response <- httr2::response_json( + body = target_tibble + ) + expect_identical( + resp_tidy_json_tibblify(mock_response), + target_tibble + ) +}) + +test_that("resp_tidy_json_tibblify subsets a response (#40, #95)", { + target_tibble <- tibble::tibble( + a = letters, + b = LETTERS, + c = 1:26 + ) + mock_response <- httr2::response_json( + body = list( + ok = TRUE, + data = list( + target_tibble = target_tibble + ) + ) + ) + expect_identical( + resp_tidy_json_tibblify( + mock_response, + subset_path = c("data", "target_tibble") + ), + target_tibble + ) +}) + +test_that("resp_tidy_json_tibblify tidies a response with a spec (#40, #95)", { + source_tibble <- tibble::tibble( + a = letters, + b = LETTERS, + c = 1:26 + ) + target_tibble <- tibble::tibble( + lc = letters, + uc = LETTERS, + n = 1:26 + ) + mock_response <- httr2::response_json( + body = source_tibble + ) + spec <- tibblify::tspec_df( + lc = tibblify::tib_chr("a"), + uc = tibblify::tib_chr("b"), + n = tibblify::tib_int("c"), + ) + expect_identical( + resp_tidy_json_tibblify(mock_response, spec = spec), + target_tibble + ) +}) + +test_that("resp_tidy_json_tibblify calls resp_tidy_json with simplifyVector = FALSE (#40, #95)", { + mock_response <- httr2::response_json(body = list(list(a = 1))) + local_mocked_bindings( + resp_tidy_json = function(resp, subset_path, simplifyVector) { + expect_identical(resp, mock_response) + expect_null(subset_path) + expect_false(simplifyVector) + list(list(a = 1)) + } + ) + expect_identical( + resp_tidy_json_tibblify(mock_response), + tibble::tibble(a = 1) + ) +}) + +test_that("tidy_policy_json_tibblify() prepares parser for resp_tidy() (#40, #95)", { + source_tibble <- tibble::tibble( + a = letters, + b = LETTERS, + c = 1:26 + ) + target_tibble <- tibble::tibble( + lc = letters, + uc = LETTERS, + n = 1:26 + ) + mock_response <- httr2::response_json( + body = source_tibble + ) + mock_response$request <- list( + policies = list( + resp_tidy = tidy_policy_json_tibblify( + spec = tibblify::tspec_df( + lc = tibblify::tib_chr("a"), + uc = tibblify::tib_chr("b"), + n = tibblify::tib_int("c"), + ) + ) + ) + ) + expect_identical( + resp_tidy(mock_response), + target_tibble + ) +}) diff --git a/vignettes/nectar.Rmd b/vignettes/nectar.Rmd index e13156b..dee387c 100644 --- a/vignettes/nectar.Rmd +++ b/vignettes/nectar.Rmd @@ -60,11 +60,11 @@ req <- req_auth_api_key( ) ``` -## Response tidying with `resp_tidy_json()` +## Response tidying with `resp_tidy_json_tibblify()` -The Crossref API returns JSON. nectar's `resp_tidy_json()` function parses the JSON response body and converts the result to a tibble using `tibblify::tibblify()`. You can extract a nested path from the response at the same time with the `subset_path` argument. +The Crossref API returns JSON. nectar's `resp_tidy_json_tibblify()` function parses the JSON response body and converts the result to a tibble using `tibblify::tibblify()`. You can extract a nested path from the response at the same time with the `subset_path` argument. -For Crossref, the actual work items live at `message$items` in the response body. To have `req_prepare()` automatically tidy each response when you call `resp_parse()` later, supply a prepared tidying policy object via the `tidy_policy` argument, such as `tidy_policy_json(subset_path = c("message", "items"))`: +For Crossref, the actual work items live at `message$items` in the response body. To have `req_prepare()` automatically tidy each response when you call `resp_parse()` later, supply a prepared tidying policy object via the `tidy_policy` argument, such as `tidy_policy_json_tibblify(subset_path = c("message", "items"))`: ```{r tidy, eval = FALSE} req <- req_prepare( @@ -72,7 +72,7 @@ req <- req_prepare( query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), - tidy_policy = tidy_policy_json(subset_path = c("message", "items")) + tidy_policy = tidy_policy_json_tibblify(subset_path = c("message", "items")) ) ``` @@ -95,7 +95,7 @@ req <- req_prepare( query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), - tidy_policy = tidy_policy_json(subset_path = c("message", "items")), + tidy_policy = tidy_policy_json_tibblify(subset_path = c("message", "items")), pagination_fn = iterate_xref ) ``` @@ -114,7 +114,7 @@ The result is always a list of `httr2_response` objects with additional class `n ## Parsing the response with `resp_parse()` -`resp_parse()` converts the raw responses into a usable R object. Because the request was prepared with `tidy_policy = tidy_policy_json(...)`, `resp_parse()` will find that function automatically and apply it to each response, then combine the results: +`resp_parse()` converts the raw responses into a usable R object. Because the request was prepared with `tidy_policy = tidy_policy_json_tibblify(...)`, `resp_parse()` will find that function automatically and apply it to each response, then combine the results: ```{r parse, eval = FALSE} result <- resp_parse(resps) @@ -131,7 +131,7 @@ result <- req_prepare( query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), - tidy_policy = tidy_policy_json(subset_path = c("message", "items")), + tidy_policy = tidy_policy_json_tibblify(subset_path = c("message", "items")), pagination_fn = iterate_with_json_cursor( param_name = "cursor", next_cursor_path = c("message", "next-cursor") @@ -176,7 +176,7 @@ works <- function( "https://api.crossref.org/works", query = list(rows = rows, cursor = "*", select = select), auth = auth_api_key("mailto", api_key = mailto, location = "query"), - tidy_policy = tidy_policy_json(subset_path = c("message", "items")), + tidy_policy = tidy_policy_json_tibblify(subset_path = c("message", "items")), pagination_fn = iterate_with_json_cursor( param_name = "cursor", next_cursor_path = c("message", "next-cursor")