diff --git a/DESCRIPTION b/DESCRIPTION index a9d428b..5aa13cd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -15,3 +15,4 @@ BugReports: https://github.com/mrc-ide/tinyjson/issues Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 +Language: en-GB diff --git a/NAMESPACE b/NAMESPACE index a5a5f2d..ba815ff 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,4 +1,10 @@ # Generated by roxygen2: do not edit by hand export(from_json) +export(json_patch_add) +export(json_patch_copy) +export(json_patch_move) +export(json_patch_remove) +export(json_patch_replace) +export(json_patch_test) export(to_json) diff --git a/R/patch.R b/R/patch.R index 4f67ca1..874cf69 100644 --- a/R/patch.R +++ b/R/patch.R @@ -1,18 +1,125 @@ +##' Patch json documents. This set of functions presents an R-ish +##' interface to all the json patch verbs (add, remove, copy, replace, +##' move, and test). It does not implement the json-format patch +##' (i.e., a patch represented itself as a json document). The primary +##' use case here is to allow expressing modifications to a json +##' document from R without having to deserialise the document into +##' R's data structures, so that a "json -> R -> json" roundtrip can +##' be done losslessly, with modification in R. +##' +##' This interface is designed to play nicely with R's piping +##' operator, see examples. +##' +##' The verbs are: +##' +##' ## Add (`json_patch_add`) +##' +##' Adds a value to an object or inserts it into an array. In the case +##' of an array, the value is inserted before the given index. The `-` +##' character can be used instead of an index to insert at the end of +##' an array. Note that the first element has index `0` (not `1`). +##' +##' ## Remove (`json_patch_remove`) +##' +##' Removes a value from an object or array. +##' +##' ## Replace (`json_patch_replace`) +##' +##' Replaces a value. Equivalent to a `remove` followed by an `add`. +##' +##' ## Copy (`json_patch_copy`) +##' +##' Copies a value from one location to another within the JSON +##' document. +##' +##' ## Move (`json_patch_move`) +##' +##' Moves a value from one location to the other. +##' +##' ## Test (`json_patch_test`) +##' +##' Tests that the specified value is set in the document. If the test +##' fails, then an error is thrown, so can be used within a pipeline +##' to prevent a patch applying. +##' +##' @title JSON Patch +##' +##' @param json The json document to work with +##' +##' @param path The patch to modify (or for `json_patch_test`, to test) +##' +##' @param value The value to add (`json_patch_add`), replace an +##' existing value with (`json_patch_replace`) or test +##' (`json_patch_test`) +##' +##' @param from The source value for `json_patch_move` and +##' `json_patch_copy` +##' +##' @rdname json_patch +##' +##' @seealso The json patch specification for more details, at +##' https://jsonpatch.com/ +##' +##' @return The modified json document (in parsed form) or throws if +##' impossible +##' +##' @export +##' @examples +##' +##' # The example doc used in https://jsonpatch.com/ +##' doc <- tinyjson::from_json('{ +##' "biscuits": [ +##' { "name": "Digestive" }, +##' { "name": "Choco Leibniz" } +##' ] +##' }') +##' +##' # add +##' tinyjson::json_patch_add(doc, "/biscuits/1", '{"name": "Ginger Nut"}') |> +##' tinyjson::to_json() +##' +##' # remove +##' tinyjson::json_patch_remove(doc, "/biscuits") |> tinyjson::to_json() +##' tinyjson::json_patch_remove(doc, "/biscuits/0") |> tinyjson::to_json() +##' +##' # replace (note the extra quotes here, the 'value' gets parsed as +##' # json and is not an R string) +##' tinyjson::json_patch_replace( +##' doc, "/biscuits/0/name", '"Chocolate Digestive"') |> tinyjson::to_json() +##' +##' # copy +##' tinyjson::json_patch_copy(doc, "/biscuits/0", "/best_biscuit") |> +##' tinyjson::to_json() +##' +##' # move +##' tinyjson::json_patch_copy(doc, "/biscuits", "/cookies") |> +##' tinyjson::to_json() +##' +##' # test +##' tinyjson::json_patch_test(doc, "/biscuits/1/name", '"Choco Leibniz"') |> +##' tinyjson::to_json() +##' json_patch_remove <- function(json, path) { patch_remove(json, json_pointer(json, path)) } +##' @export +##' @rdname json_patch json_patch_replace <- function(json, path, value) { patch_replace(json, json_pointer(json, path), unclass(from_json(value))) } +##' @export +##' @rdname json_patch json_patch_add <- function(json, path, value) { patch_add(json, json_pointer(json, path), unclass(from_json(value))) } +##' @export +##' @rdname json_patch json_patch_copy <- function(json, from, path) { ptr_from <- json_pointer(json, from) ptr_path <- json_pointer(json, path) @@ -20,6 +127,8 @@ json_patch_copy <- function(json, from, path) { } +##' @export +##' @rdname json_patch json_patch_move <- function(json, from, path) { ptr_from <- json_pointer(json, from) if (from == path) { @@ -30,6 +139,8 @@ json_patch_move <- function(json, from, path) { } +##' @export +##' @rdname json_patch json_patch_test <- function(json, path, value) { patch_test(json, json_pointer(json, path), from_json(value)) } diff --git a/README.md b/README.md index 0e5fb8d..ccaaa4b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This package solves a very specific goal - read json into R with almost no deserialisation, into some intermediate format that does not suffer from the information loss typically encountered with arrays (are they scalar or are they a vector of length 1?). -Unlike `jsonlite::serialzeJSON` which aims for a lossless `R -> JSON -> R` roundtrip, we aim for a lossless `JSON -> R -> JSON` roundtrip, so that R can safely ammend existing JSON that might be consumed by other applications without breaking it. +Unlike `jsonlite::serialzeJSON` which aims for a lossless `R -> JSON -> R` roundtrip, we aim for a lossless `JSON -> R -> JSON` roundtrip, so that R can safely amend existing JSON that might be consumed by other applications without breaking it. The package is not intended to be fast (it is written in pure R) nor provide good errors (use jsonlite first to ensure the json is valid) nor provide good output formatting (use jsonlite's pretty formatter). diff --git a/inst/WORDLIST b/inst/WORDLIST new file mode 100644 index 0000000..c50fcaf --- /dev/null +++ b/inst/WORDLIST @@ -0,0 +1,16 @@ +JSON +R's +codecov +com +deserialisation +deserialise +formatter +https +io +ish +json +jsonlite +jsonlite's +jsonpatch +losslessly +roundtrip diff --git a/man/json_patch.Rd b/man/json_patch.Rd new file mode 100644 index 0000000..59f8e92 --- /dev/null +++ b/man/json_patch.Rd @@ -0,0 +1,130 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/patch.R +\name{json_patch_remove} +\alias{json_patch_remove} +\alias{json_patch_replace} +\alias{json_patch_add} +\alias{json_patch_copy} +\alias{json_patch_move} +\alias{json_patch_test} +\title{JSON Patch} +\usage{ +json_patch_remove(json, path) + +json_patch_replace(json, path, value) + +json_patch_add(json, path, value) + +json_patch_copy(json, from, path) + +json_patch_move(json, from, path) + +json_patch_test(json, path, value) +} +\arguments{ +\item{json}{The json document to work with} + +\item{path}{The patch to modify (or for \code{json_patch_test}, to test)} + +\item{value}{The value to add (\code{json_patch_add}), replace an +existing value with (\code{json_patch_replace}) or test +(\code{json_patch_test})} + +\item{from}{The source value for \code{json_patch_move} and +\code{json_patch_copy}} +} +\value{ +The modified json document (in parsed form) or throws if +impossible +} +\description{ +Patch json documents. This set of functions presents an R-ish +interface to all the json patch verbs (add, remove, copy, replace, +move, and test). It does not implement the json-format patch +(i.e., a patch represented itself as a json document). The primary +use case here is to allow expressing modifications to a json +document from R without having to deserialise the document into +R's data structures, so that a "json -> R -> json" roundtrip can +be done losslessly, with modification in R. +} +\details{ +This interface is designed to play nicely with R's piping +operator, see examples. + +The verbs are: +\subsection{Add (\code{json_patch_add})}{ + +Adds a value to an object or inserts it into an array. In the case +of an array, the value is inserted before the given index. The \code{-} +character can be used instead of an index to insert at the end of +an array. Note that the first element has index \code{0} (not \code{1}). +} + +\subsection{Remove (\code{json_patch_remove})}{ + +Removes a value from an object or array. +} + +\subsection{Replace (\code{json_patch_replace})}{ + +Replaces a value. Equivalent to a \code{remove} followed by an \code{add}. +} + +\subsection{Copy (\code{json_patch_copy})}{ + +Copies a value from one location to another within the JSON +document. +} + +\subsection{Move (\code{json_patch_move})}{ + +Moves a value from one location to the other. +} + +\subsection{Test (\code{json_patch_test})}{ + +Tests that the specified value is set in the document. If the test +fails, then an error is thrown, so can be used within a pipeline +to prevent a patch applying. +} +} +\examples{ + +# The example doc used in https://jsonpatch.com/ +doc <- tinyjson::from_json('{ + "biscuits": [ + { "name": "Digestive" }, + { "name": "Choco Leibniz" } + ] +}') + +# add +tinyjson::json_patch_add(doc, "/biscuits/1", '{"name": "Ginger Nut"}') |> + tinyjson::to_json() + +# remove +tinyjson::json_patch_remove(doc, "/biscuits") |> tinyjson::to_json() +tinyjson::json_patch_remove(doc, "/biscuits/0") |> tinyjson::to_json() + +# replace (note the extra quotes here, the 'value' gets parsed as +# json and is not an R string) +tinyjson::json_patch_replace( + doc, "/biscuits/0/name", '"Chocolate Digestive"') |> tinyjson::to_json() + +# copy +tinyjson::json_patch_copy(doc, "/biscuits/0", "/best_biscuit") |> + tinyjson::to_json() + +# move +tinyjson::json_patch_copy(doc, "/biscuits", "/cookies") |> + tinyjson::to_json() + +# test +tinyjson::json_patch_test(doc, "/biscuits/1/name", '"Choco Leibniz"') |> + tinyjson::to_json() + +} +\seealso{ +The json patch specification for more details, at +https://jsonpatch.com/ +}