diff --git a/DESCRIPTION b/DESCRIPTION index ea38e75..d3bf3da 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,7 +4,7 @@ Description: Client for 'jq', a 'JSON' processor (= 3.1.2) License: MIT + file LICENSE diff --git a/NAMESPACE b/NAMESPACE index e3aa9bb..2be4d61 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -12,6 +12,10 @@ export(allj) export(anyj) export(at) export(at_) +export(build_array) +export(build_array_) +export(build_object) +export(build_object_) export(combine) export(contains) export(contains_) diff --git a/R/actions.R b/R/actions.R deleted file mode 100644 index 6eea8b7..0000000 --- a/R/actions.R +++ /dev/null @@ -1,17 +0,0 @@ -######## hmmmm, these two aren't making sense yet -array <- function(.data) { - array_(.data, dots = "[%s]") -} - -array_ <- function(.data, dots) { - structure(list(data=.data, structure(dots, type="array")), class="jqr") -} - -hash <- function(.data) { - array_(.data, dots = "{%s}") -} - -hash_ <- function(.data, dots) { - structure(list(data=.data, structure(dots, type="hash")), class="jqr") -} -######### diff --git a/R/constructors.R b/R/constructors.R new file mode 100644 index 0000000..5838e48 --- /dev/null +++ b/R/constructors.R @@ -0,0 +1,73 @@ +#' Build arrays and objects +#' +#' @name build +#' @template args +#' @examples +#' ## BUILD ARRAYS +#' x <- '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' +#' jq(x, "[.user, .projects[]]") +#' x %>% build_array(.user, .projects[]) +#' +#' jq('[1, 2, 3]', '[ .[] | . * 2]') +#' '[1, 2, 3]' %>% build_array(.[] | . * 2) +#' +#' +#' ## BUILD OBJECTS +#' '{"foo": 5, "bar": 7}' %>% build_object(a = .foo) %>% peek +#' '{"foo": 5, "bar": 7}' %>% build_object(a = .foo) +#' +#' # using json dataset, just first element +#' x <- commits %>% index(0) +#' x %>% +#' build_object(message = .commit.message, name = .commit.committer.name) +#' x %>% build_object(sha = .commit.tree.sha, author = .author.login) +#' +#' # using json dataset, all elements +#' x <- index(commits) +#' x %>% build_object(message = .commit.message, name = .commit.committer.name) +#' x %>% build_object(sha = .sha, name = .commit.committer.name) +#' +#' # many JSON inputs +#' '{"foo": 5, "bar": 7} {"foo": 50, "bar": 7} {"foo": 500, "bar": 7}' %>% +#' build_object(hello = .foo) + +#' @export +#' @rdname build +build_array <- function(.data, ...) { + build_array_(.data, .dots = lazyeval::lazy_dots(...)) +} + +#' @export +#' @rdname build +build_array_ <- function(.data, ..., .dots) { + pipe_autoexec(toggle = TRUE) + tmp <- lazyeval::all_dots(.dots, ...) + tmp <- lapply(tmp, function(x) deparse(x$expr)) + z <- paste0("[", paste0(tmp, collapse = ", "), "]") + dots <- comb(tryargs(.data), structure(z, type = "array")) + structure(list(data = getdata(.data), args = dots), class = "jqr") +} + + +#' @export +#' @rdname build +build_object <- function(.data, ...) { + build_object_(.data, .dots = lazyeval::lazy_dots(...)) +} + +#' @export +#' @rdname build +build_object_ <- function(.data, ..., .dots) { + pipe_autoexec(toggle = TRUE) + tmp <- lazyeval::all_dots(.dots, ...) + vals <- unname(Map(function(x,y) { + if (nchar(x) == 0) { + as.character(y$expr) + } else { + sprintf("%s: %s", x, as.character(y$expr)) + } + }, names(tmp), tmp)) + z <- paste0("{", paste0(vals, collapse = ", "), "}") + dots <- comb(tryargs(.data), structure(z, type = "object")) + structure(list(data = getdata(.data), args = dots), class = "jqr") +} diff --git a/R/recurse.R b/R/recurse.R index 88407c4..5f90ded 100644 --- a/R/recurse.R +++ b/R/recurse.R @@ -10,8 +10,8 @@ #' {"name": "/home", "children": [ #' {"name": "/home/stephen", "children": [ #' {"name": "/home/stephen/jq", "children": []}]}]}]}' -#' x %>% recurse(.children[]) %>% select(name) -#' x %>% recurse(.children[]) %>% select(name) %>% string +#' x %>% recurse(.children[]) %>% build_object(name) +#' x %>% recurse(.children[]) %>% build_object(name) %>% string recurse <- function(.data, ...) { recurse_(.data, .dots = lazyeval::lazy_dots(...)) } diff --git a/R/select.R b/R/select.R index d4eea4a..f330cf1 100644 --- a/R/select.R +++ b/R/select.R @@ -1,25 +1,34 @@ -#' Select variables +#' Select - filtering +#' +#' The function \code{select(foo)} produces its input unchanged if +#' \code{foo} returns TRUE for that input, and produces no output otherwise #' #' @export #' @template args +#' @note this function has changed what it does dramatically. we were +#' using this function for object construction, which is now done with +#' \code{\link{build_object}} #' @examples -#' '{"foo": 5, "bar": 7}' %>% select(a = .foo) %>% peek -#' '{"foo": 5, "bar": 7}' %>% select(a = .foo) -#' -#' # using json dataset, just first element -#' x <- commits %>% index(0) -#' x %>% -#' select(message = .commit.message, name = .commit.committer.name) -#' x %>% select(sha = .commit.tree.sha, author = .author.login) -#' -#' # using json dataset, all elements -#' x <- index(commits) -#' x %>% select(message = .commit.message, name = .commit.committer.name) -#' x %>% select(sha = .sha, name = .commit.committer.name) -#' -#' # many JSON inputs -#' '{"foo": 5, "bar": 7} {"foo": 50, "bar": 7} {"foo": 500, "bar": 7}' %>% -#' select(hello = .foo) +#' jq('[1,5,3,0,7]', 'map(select(. >= 2))') +#' '[1,5,3,0,7]' %>% map(select(. >= 2)) +#' +#' +#' '{"foo": 4, "bar": 7}' %>% select(.foo == 4) +#' '{"foo": 5, "bar": 7} {"foo": 4, "bar": 7}' %>% select(.foo == 4) +#' '[{"foo": 5, "bar": 7}, {"foo": 4, "bar": 7}]' %>% index() %>% +#' select(.foo == 4) +#' '{"foo": 4, "bar": 7} {"foo": 5, "bar": 7} {"foo": 8, "bar": 7}' %>% +#' select(.foo < 6) +#' +#' x <- '{"foo": 4, "bar": 2} {"foo": 5, "bar": 4} {"foo": 8, "bar": 12}' +#' jq(x, 'select((.foo < 6) and (.bar > 3))') +#' jq(x, 'select((.foo < 6) or (.bar > 3))') +#' x %>% select((.foo < 6) && (.bar > 3)) +#' x %>% select((.foo < 6) || (.bar > 3)) +#' +#' x <- '[{"foo": 5, "bar": 7}, {"foo": 4, "bar": 7}, {"foo": 4, "bar": 9}]' +#' jq(x, '.[] | select(.foo == 4) | {user: .bar}') +#' x %>% index() %>% select(.foo == 4) %>% build_object(user = .bar) select <- function(.data, ...) { select_(.data, .dots = lazyeval::lazy_dots(...)) } @@ -29,14 +38,19 @@ select <- function(.data, ...) { select_ <- function(.data, ..., .dots) { pipe_autoexec(toggle = TRUE) tmp <- lazyeval::all_dots(.dots, ...) - vals <- unname(Map(function(x,y) { - if (nchar(x) == 0) { - as.character(y$expr) - } else { - sprintf("%s: %s", x, as.character(y$expr)) - } - }, names(tmp), tmp)) - z <- paste0("{", paste0(vals, collapse = ", "), "}") - dots <- comb(tryargs(.data), structure(z, type = "select")) + z <- paste0(unlist(lapply(tmp, function(x) { + sub_ops_sel(sub_ops(deparse(x$expr))) + })), collapse = ", ") + dots <- comb(tryargs(.data), structure(sprintf("select(%s)", z), + type = "select")) structure(list(data = getdata(.data), args = dots), class = "jqr") } + +sub_ops_sel <- function(x) { + ops <- c("&&", "\\|\\|") + use <- c("and", "or") + if (base::any(vapply(ops, grepl, logical(1), x = x))) { + for (i in seq_along(ops)) x <- gsub(ops[[i]], use[[i]], x) + } + return(x) +} diff --git a/R/vars.R b/R/vars.R index 7eec70a..b532204 100644 --- a/R/vars.R +++ b/R/vars.R @@ -17,7 +17,7 @@ #' x %>% dotstr(posts[]) #' x %>% dotstr(posts[]) %>% string #' x %>% vars(realnames = names) %>% dotstr(posts[]) %>% -#' select(title, author = "$names[.author]") +#' build_object(title, author = "$names[.author]") vars <- function(.data, ...) { vars_(.data, .dots = lazyeval::lazy_dots(...)) } diff --git a/README.Rmd b/README.Rmd index 2b3cac2..512a4cd 100644 --- a/README.Rmd +++ b/README.Rmd @@ -177,7 +177,7 @@ Show the query to be used using `peek()` x <- '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}' jq(x, '{user, title: .titles[]}') x %>% index() -x %>% select(user, title = `.titles[]`) +x %>% build_object(user, title = `.titles[]`) jq(x, '{user, title: .titles[]}') %>% jsonlite::toJSON() %>% jsonlite::validate() ``` @@ -236,6 +236,28 @@ unique '[1,2,5,3,5,3,1,3]' %>% uniquej ``` + +#### filter + +With filtering via `select()` you can use various operators, like `==`, +`&&`, `||`. We translate these internally for you to what `jq` wants +to see (`==`, `and`, `or`). + +Simple, one condition + +```{r} +'{"foo": 4, "bar": 7}' %>% select(.foo == 4) +``` + +More complicated. Combine more than one condition; combine each individual +filtering task in parentheses + +```{r} +x <- '{"foo": 4, "bar": 2} {"foo": 5, "bar": 4} {"foo": 8, "bar": 12}' +x %>% select((.foo < 6) && (.bar > 3)) +x %>% select((.foo < 6) || (.bar > 3)) +``` + #### types get type information for each element @@ -274,18 +296,18 @@ str3 %>% haskey(2) str3 %>% haskey(1,2) ``` -Select variables by name, and rename +Build an object, selecting variables by name, and rename ```{r} -'{"foo": 5, "bar": 7}' %>% select(a = .foo) +'{"foo": 5, "bar": 7}' %>% build_object(a = .foo) ``` -More complicated `select()`, using the included dataset `commits` +More complicated `build_object()`, using the included dataset `commits` ```{r} commits %>% index() %>% - select(sha = .sha, name = .commit.committer.name) + build_object(sha = .sha, name = .commit.committer.name) ``` #### Maths @@ -356,7 +378,7 @@ This outputs a few pieces of JSON ```{r} (x <- commits %>% index() %>% - select(sha = .sha, name = .commit.committer.name)) + build_object(sha = .sha, name = .commit.committer.name)) ``` Use `combine()` to put them together. diff --git a/README.md b/README.md index d34ea06..c79cc76 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ jqr ======= + [![Build Status](https://travis-ci.org/ropensci/jqr.svg?branch=master)](https://travis-ci.org/ropensci/jqr) [![Build status](https://ci.appveyor.com/api/projects/status/tfwpiaotu24sotxg?svg=true)](https://ci.appveyor.com/project/sckott/jqr) [![Coverage Status](https://coveralls.io/repos/ropensci/jqr/badge.svg?branch=master)](https://coveralls.io/r/ropensci/jqr?branch=master) @@ -306,7 +307,7 @@ x %>% index() #> "More JQ" #> ] #> ] -x %>% select(user, title = `.titles[]`) +x %>% build_object(user, title = `.titles[]`) #> [ #> { #> "user": "stedolan", @@ -373,6 +374,14 @@ startswith #> true, #> false #> ] +'["fo", "foo"] ["barfoo", "foobar", "barfoob"]' %>% index %>% startswith(foo) +#> [ +#> false, +#> true, +#> false, +#> true, +#> false +#> ] ``` endswith @@ -444,6 +453,52 @@ unique #> ] ``` + +#### filter + +With filtering via `select()` you can use various operators, like `==`, +`&&`, `||`. We translate these internally for you to what `jq` wants +to see (`==`, `and`, `or`). + +Simple, one condition + + +```r +'{"foo": 4, "bar": 7}' %>% select(.foo == 4) +#> { +#> "foo": 4, +#> "bar": 7 +#> } +``` + +More complicated. Combine more than one condition; combine each individual +filtering task in parentheses + + +```r +x <- '{"foo": 4, "bar": 2} {"foo": 5, "bar": 4} {"foo": 8, "bar": 12}' +x %>% select((.foo < 6) && (.bar > 3)) +#> { +#> "foo": 5, +#> "bar": 4 +#> } +x %>% select((.foo < 6) || (.bar > 3)) +#> [ +#> { +#> "foo": 4, +#> "bar": 2 +#> }, +#> { +#> "foo": 5, +#> "bar": 4 +#> }, +#> { +#> "foo": 8, +#> "bar": 12 +#> } +#> ] +``` + #### types get type information for each element @@ -523,23 +578,23 @@ str3 %>% haskey(1,2) #> ] ``` -Select variables by name, and rename +Build an object, selecting variables by name, and rename ```r -'{"foo": 5, "bar": 7}' %>% select(a = .foo) +'{"foo": 5, "bar": 7}' %>% build_object(a = .foo) #> { #> "a": 5 #> } ``` -More complicated `select()`, using the included dataset `commits` +More complicated `build_object()`, using the included dataset `commits` ```r commits %>% index() %>% - select(sha = .sha, name = .commit.committer.name) + build_object(sha = .sha, name = .commit.committer.name) #> [ #> { #> "sha": [ @@ -744,7 +799,7 @@ This outputs a few pieces of JSON ```r (x <- commits %>% index() %>% - select(sha = .sha, name = .commit.committer.name)) + build_object(sha = .sha, name = .commit.committer.name)) #> [ #> { #> "sha": [ diff --git a/man/build.Rd b/man/build.Rd new file mode 100644 index 0000000..f4ff07c --- /dev/null +++ b/man/build.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/constructors.R +\name{build} +\alias{build} +\alias{build_array} +\alias{build_array_} +\alias{build_object} +\alias{build_object_} +\title{Build arrays and objects} +\usage{ +build_array(.data, ...) + +build_array_(.data, ..., .dots) + +build_object(.data, ...) + +build_object_(.data, ..., .dots) +} +\arguments{ +\item{.data}{input. This can be JSON input, or an object of class +\code{jqr} that has JSON and query params combined, which is passed +from function to function when using the jqr DSL.} + +\item{...}{Comma separated list of unquoted variable names} + +\item{.dots}{Used to work around non-standard evaluation} + +\item{dots}{dots} +} +\description{ +Build arrays and objects +} +\examples{ +## BUILD ARRAYS +x <- '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' +jq(x, "[.user, .projects[]]") +x \%>\% build_array(.user, .projects[]) + +jq('[1, 2, 3]', '[ .[] | . * 2]') +'[1, 2, 3]' \%>\% build_array(.[] | . * 2) + + +## BUILD OBJECTS +'{"foo": 5, "bar": 7}' \%>\% build_object(a = .foo) \%>\% peek +'{"foo": 5, "bar": 7}' \%>\% build_object(a = .foo) + +# using json dataset, just first element +x <- commits \%>\% index(0) +x \%>\% + build_object(message = .commit.message, name = .commit.committer.name) +x \%>\% build_object(sha = .commit.tree.sha, author = .author.login) + +# using json dataset, all elements +x <- index(commits) +x \%>\% build_object(message = .commit.message, name = .commit.committer.name) +x \%>\% build_object(sha = .sha, name = .commit.committer.name) + +# many JSON inputs +'{"foo": 5, "bar": 7} {"foo": 50, "bar": 7} {"foo": 500, "bar": 7}' \%>\% + build_object(hello = .foo) +} diff --git a/man/recurse.Rd b/man/recurse.Rd index 0fc9d3a..9c3c8e1 100644 --- a/man/recurse.Rd +++ b/man/recurse.Rd @@ -31,6 +31,6 @@ x <- '{"name": "/", "children": [ {"name": "/home", "children": [ {"name": "/home/stephen", "children": [ {"name": "/home/stephen/jq", "children": []}]}]}]}' -x \%>\% recurse(.children[]) \%>\% select(name) -x \%>\% recurse(.children[]) \%>\% select(name) \%>\% string +x \%>\% recurse(.children[]) \%>\% build_object(name) +x \%>\% recurse(.children[]) \%>\% build_object(name) \%>\% string } diff --git a/man/select.Rd b/man/select.Rd index aca37b8..18491ba 100644 --- a/man/select.Rd +++ b/man/select.Rd @@ -3,7 +3,7 @@ \name{select} \alias{select} \alias{select_} -\title{Select variables} +\title{Select - filtering} \usage{ select(.data, ...) @@ -21,24 +21,33 @@ from function to function when using the jqr DSL.} \item{dots}{dots} } \description{ -Select variables +The function \code{select(foo)} produces its input unchanged if +\code{foo} returns TRUE for that input, and produces no output otherwise +} +\note{ +this function has changed what it does dramatically. we were +using this function for object construction, which is now done with +\code{\link{build_object}} } \examples{ -'{"foo": 5, "bar": 7}' \%>\% select(a = .foo) \%>\% peek -'{"foo": 5, "bar": 7}' \%>\% select(a = .foo) - -# using json dataset, just first element -x <- commits \%>\% index(0) -x \%>\% - select(message = .commit.message, name = .commit.committer.name) -x \%>\% select(sha = .commit.tree.sha, author = .author.login) - -# using json dataset, all elements -x <- index(commits) -x \%>\% select(message = .commit.message, name = .commit.committer.name) -x \%>\% select(sha = .sha, name = .commit.committer.name) - -# many JSON inputs -'{"foo": 5, "bar": 7} {"foo": 50, "bar": 7} {"foo": 500, "bar": 7}' \%>\% - select(hello = .foo) +jq('[1,5,3,0,7]', 'map(select(. >= 2))') +'[1,5,3,0,7]' \%>\% map(select(. >= 2)) + + +'{"foo": 4, "bar": 7}' \%>\% select(.foo == 4) +'{"foo": 5, "bar": 7} {"foo": 4, "bar": 7}' \%>\% select(.foo == 4) +'[{"foo": 5, "bar": 7}, {"foo": 4, "bar": 7}]' \%>\% index() \%>\% + select(.foo == 4) +'{"foo": 4, "bar": 7} {"foo": 5, "bar": 7} {"foo": 8, "bar": 7}' \%>\% + select(.foo < 6) + +x <- '{"foo": 4, "bar": 2} {"foo": 5, "bar": 4} {"foo": 8, "bar": 12}' +jq(x, 'select((.foo < 6) and (.bar > 3))') +jq(x, 'select((.foo < 6) or (.bar > 3))') +x \%>\% select((.foo < 6) && (.bar > 3)) +x \%>\% select((.foo < 6) || (.bar > 3)) + +x <- '[{"foo": 5, "bar": 7}, {"foo": 4, "bar": 7}, {"foo": 4, "bar": 9}]' +jq(x, '.[] | select(.foo == 4) | {user: .bar}') +x \%>\% index() \%>\% select(.foo == 4) \%>\% build_object(user = .bar) } diff --git a/man/vars.Rd b/man/vars.Rd index 699d595..42cceb7 100644 --- a/man/vars.Rd +++ b/man/vars.Rd @@ -38,5 +38,5 @@ x <- '{ x \%>\% dotstr(posts[]) x \%>\% dotstr(posts[]) \%>\% string x \%>\% vars(realnames = names) \%>\% dotstr(posts[]) \%>\% - select(title, author = "$names[.author]") + build_object(title, author = "$names[.author]") } diff --git a/tests/testthat/test-dsl.R b/tests/testthat/test-dsl.R index 583e909..eae9939 100644 --- a/tests/testthat/test-dsl.R +++ b/tests/testthat/test-dsl.R @@ -160,20 +160,20 @@ test_that("maths", { }) -test_that("select variables", { - expect_equal(ac('{"foo": 5, "bar": 7}' %>% select(a = .foo) %>% jq), '{"a":5}') +test_that("construct objects", { + expect_equal(ac('{"foo": 5, "bar": 7}' %>% build_object(a = .foo) %>% jq), '{"a":5}') # using json dataset, just first element x <- commits %>% index(0) - expect_equal(ac(x %>% select(message = .commit.message, name = .commit.committer.name) %>% jq), + expect_equal(ac(x %>% build_object(message = .commit.message, name = .commit.committer.name) %>% jq), '{\"message\":[\"Add wrapping and clamping to jv_array_slice\\n\\nFix #716. Fix #717.\"],\"name\":[\"Nicolas Williams\"]}') - expect_equal(ac(x %>% select(sha = .commit.tree.sha, author = .author.login) %>% jq), + expect_equal(ac(x %>% build_object(sha = .commit.tree.sha, author = .author.login) %>% jq), '{\"sha\":[\"a52a4b412c3ba4bd2e237f37a5f11fd565e74bae\"],\"author\":[\"tgockel\"]}') # using json dataset, all elements x <- index(commits) zz <- x %>% - select(message = .commit.message, name = .commit.committer.name) + build_object(message = .commit.message, name = .commit.committer.name) expect_is(zz, "jqson") expect_equal(base::length(zz), 5) })