Skip to content

Commit

Permalink
internal refactor of j_patch_op(); use jsoncons for pretty printing
Browse files Browse the repository at this point in the history
  • Loading branch information
mtmorgan committed Feb 28, 2024
1 parent 72eb87f commit 1595185
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 49 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: rjsoncons
Title: 'C++' Header-Only 'jsoncons' Library for 'JSON' Queries
Version: 1.2.0.9502
Version: 1.2.0.9503
Authors@R: c(
person(
"Martin", "Morgan", role = c("aut", "cre"),
Expand Down
3 changes: 2 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Generated by roxygen2: do not edit by hand

S3method(as.character,j_patch_op)
S3method(c,j_patch_op)
S3method(j_patch_op,default)
S3method(j_patch_op,j_patch_op)
Expand All @@ -19,4 +18,6 @@ export(jsonpointer)
export(version)
importFrom(cli,cli_progress_bar)
importFrom(cli,cli_progress_done)
importFrom(utils,head)
importFrom(utils,tail)
useDynLib(rjsoncons, .registration = TRUE)
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# rjsoncons 1.3.0

- (1.2.0.9502) add JSON patch support with `j_patch_apply()`,
- (1.2.0.9503) add JSON patch support with `j_patch_apply()`,
`j_patch_from()`, and `j_patch_op()`.
- (1.2.0.9401) internal C++ code cleanup and refactoring.
- (1.2.0.9300) add 'Examples' web-only vignette.
Expand Down
4 changes: 4 additions & 0 deletions R/cpp11.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ cpp_j_patch_from <- function(data_x, data_type_x, data_y, data_type_y, as) {
.Call(`_rjsoncons_cpp_j_patch_from`, data_x, data_type_x, data_y, data_type_y, as)
}

cpp_j_patch_print <- function(patch, indent, width) {
.Call(`_rjsoncons_cpp_j_patch_print`, patch, indent, width)
}

cpp_version <- function() {
.Call(`_rjsoncons_cpp_version`)
}
Expand Down
39 changes: 19 additions & 20 deletions R/patch.R
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,19 @@ j_patch_op.default <-
})

patch <- j_query(patch, ...)
structure(patch, class = "j_patch_op")
structure(c("[", patch, "]"), class = "j_patch_op")
}

#' @rdname patch
#'
#' @importFrom utils head tail
#'
#' @export
j_patch_op.j_patch_op <-
function(op, ...)
{
c(op, j_patch_op(...))
patch <- c(head(op, -1), ",", tail(j_patch_op(...), -1))
structure(patch, class = "j_patch_op")
}

#' @rdname patch
Expand All @@ -331,32 +334,28 @@ j_patch_op.j_patch_op <-
c.j_patch_op <-
function(..., recursive = FALSE)
{
structure(NextMethod("c"), class = "j_patch_op")
args <- list(...)
args[-1] <- lapply(args[-1], tail, -1L)
args[-length(args)] <- lapply(args[-length(args)], \(x) {
x[length(x)] = ","
x
})
args <- lapply(args, unclass)
result <- do.call("c", args)
structure(result, class = "j_patch_op")
}

#' @rdname patch
#'
#' @param x An object produced by `j_patch_op()`.
#'
#' @export
as.character.j_patch_op <-
function(x, ...)
{
paste0("[", paste(x, collapse = ","), "]")
}

#' @rdname patch
#'
#' @export
print.j_patch_op <-
function(x, ...)
{
cat(
"[",
if (length(x)) "\n ",
if (length(x)) paste(x, collapse = "\n "),
if (length(x)) "\n",
"]\n",
sep = ""
)
width <- as.integer(getOption("width"))
indent <- 2L
stopifnot(.is_scalar_numeric(width))
patch <- paste(x, collapse = "\n")
cat(cpp_j_patch_print(patch, indent, width), "\n")
}
61 changes: 40 additions & 21 deletions inst/tinytest/test_patch.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,42 +61,61 @@ value1 <- list(name = jsonlite::unbox("Ginger Nut"))
path <- "/biscuits/1"

expect_identical(
as.character(j_patch_op("add", path, value = value0)),
'[{"op":"add","path":"/biscuits/1","value":{"name":["Ginger Nut"]}}]'
unclass(j_patch_op("add", path, value = value0)),
c('[',
'{"op":"add","path":"/biscuits/1","value":{"name":["Ginger Nut"]}}',
']')
)
expect_identical(
as.character(j_patch_op("add", path, value = value1)),
'[{"op":"add","path":"/biscuits/1","value":{"name":"Ginger Nut"}}]'
unclass(j_patch_op("add", path, value = value1)),
c('[',
'{"op":"add","path":"/biscuits/1","value":{"name":"Ginger Nut"}}',
']')
)
expect_identical(
as.character(j_patch_op("add", path, value = value0, auto_unbox = TRUE)),
'[{"op":"add","path":"/biscuits/1","value":{"name":"Ginger Nut"}}]'
unclass(j_patch_op("add", path, value = value0, auto_unbox = TRUE)),
c('[',
'{"op":"add","path":"/biscuits/1","value":{"name":"Ginger Nut"}}',
']')
)

expect_identical(
as.character(j_patch_op("remove", path)),
'[{"op":"remove","path":"/biscuits/1"}]'
unclass(j_patch_op("remove", path)),
c('[', '{"op":"remove","path":"/biscuits/1"}', ']')
)

expect_identical(
as.character(j_patch_op("replace", path, value = value1)),
'[{"op":"replace","path":"/biscuits/1","value":{"name":"Ginger Nut"}}]'
unclass(j_patch_op("replace", path, value = value1)),
c('[',
'{"op":"replace","path":"/biscuits/1","value":{"name":"Ginger Nut"}}',
']')
)

expect_identical(
as.character(j_patch_op("copy", path, from = path)),
'[{"op":"copy","path":"/biscuits/1","from":"/biscuits/1"}]'
unclass(j_patch_op("copy", path, from = path)),
c('[', '{"op":"copy","path":"/biscuits/1","from":"/biscuits/1"}', ']')
)

expect_identical(
as.character(j_patch_op("move", path, from = path)),
'[{"op":"move","path":"/biscuits/1","from":"/biscuits/1"}]'
unclass(j_patch_op("move", path, from = path)),
c('[', '{"op":"move","path":"/biscuits/1","from":"/biscuits/1"}', ']')
)

expect_identical(
as.character(j_patch_op("test", path, value = value1)),
'[{"op":"test","path":"/biscuits/1","value":{"name":"Ginger Nut"}}]'
unclass(j_patch_op("test", path, value = value1)),
c('[',
'{"op":"test","path":"/biscuits/1","value":{"name":"Ginger Nut"}}',
']')
)

## concatenation and piping
patch <- j_patch_op("add", path, value = value1)
expected <- c(
'[',
'{"op":"add","path":"/biscuits/1","value":{"name":"Ginger Nut"}}',
',',
'{"op":"add","path":"/biscuits/1","value":{"name":"Ginger Nut"}}',
']'
)
expect_identical(unclass(c(patch, patch)), expected)

patch1 <- patch |> j_patch_op("add", path, value = value1)
expect_identical(unclass(patch1), expected)

expect_error(j_patch_op())
expect_error(j_patch_op("add")) # no 'path'
Expand Down
3 changes: 0 additions & 3 deletions man/patch.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/cpp11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ extern "C" SEXP _rjsoncons_cpp_j_patch_from(SEXP data_x, SEXP data_type_x, SEXP
return cpp11::as_sexp(cpp_j_patch_from(cpp11::as_cpp<cpp11::decay_t<const std::string&>>(data_x), cpp11::as_cpp<cpp11::decay_t<const std::string&>>(data_type_x), cpp11::as_cpp<cpp11::decay_t<const std::string&>>(data_y), cpp11::as_cpp<cpp11::decay_t<const std::string&>>(data_type_y), cpp11::as_cpp<cpp11::decay_t<const std::string&>>(as)));
END_CPP11
}
// patch.cpp
std::string cpp_j_patch_print(const std::string& patch, const int indent, const int width);
extern "C" SEXP _rjsoncons_cpp_j_patch_print(SEXP patch, SEXP indent, SEXP width) {
BEGIN_CPP11
return cpp11::as_sexp(cpp_j_patch_print(cpp11::as_cpp<cpp11::decay_t<const std::string&>>(patch), cpp11::as_cpp<cpp11::decay_t<const int>>(indent), cpp11::as_cpp<cpp11::decay_t<const int>>(width)));
END_CPP11
}
// rjsoncons.cpp
std::string cpp_version();
extern "C" SEXP _rjsoncons_cpp_version() {
Expand Down Expand Up @@ -75,6 +82,7 @@ static const R_CallMethodDef CallEntries[] = {
{"_rjsoncons_cpp_as_r_con", (DL_FUNC) &_rjsoncons_cpp_as_r_con, 5},
{"_rjsoncons_cpp_j_patch_apply", (DL_FUNC) &_rjsoncons_cpp_j_patch_apply, 4},
{"_rjsoncons_cpp_j_patch_from", (DL_FUNC) &_rjsoncons_cpp_j_patch_from, 5},
{"_rjsoncons_cpp_j_patch_print", (DL_FUNC) &_rjsoncons_cpp_j_patch_print, 3},
{"_rjsoncons_cpp_j_pivot", (DL_FUNC) &_rjsoncons_cpp_j_pivot, 6},
{"_rjsoncons_cpp_j_pivot_con", (DL_FUNC) &_rjsoncons_cpp_j_pivot_con, 8},
{"_rjsoncons_cpp_j_query", (DL_FUNC) &_rjsoncons_cpp_j_query, 6},
Expand Down
18 changes: 18 additions & 0 deletions src/patch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,21 @@ sexp cpp_j_patch_from(
auto patch = jsonpatch::from_diff(data_x_, data_y_);
return j_as(patch, enum_index(as_map, as));
}

[[cpp11::register]]
std::string cpp_j_patch_print(
const std::string& patch,
const int indent, const int width)
{
auto j = ojson::parse(patch);
std::string result;

json_options options;
options.indent_size(indent);
options.line_length_limit(width);
options.array_object_line_splits(line_split_kind::new_line);
options.object_object_line_splits(line_split_kind::new_line);
j.dump(result, options, indenting::indent);

return result;
}
3 changes: 1 addition & 2 deletions vignettes/a_rjsoncons.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,7 @@ The `j_patch_from()` function constructs a patch from the difference
between two documents
```{r}
j_patch_from(j_patch_apply(json, patch), json) |>
jsonlite::prettify()
j_patch_from(j_patch_apply(json, patch), json)
```

[JSON Patch]: https://jsonpatch.com/
Expand Down

0 comments on commit 1595185

Please sign in to comment.