diff --git a/.Rbuildignore b/.Rbuildignore index 0a00875..aa00f90 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,4 +6,7 @@ ^LICENSE-yyjson.txt$ ^.devcontainer$ ^\.github$ -^pkgdown$ \ No newline at end of file +^pkgdown$ +^LICENSE\.md$ +^doc$ +^Meta$ diff --git a/.gitignore b/.gitignore index 535c5d4..2a20177 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ inst/doc doc Meta pkgdown -working \ No newline at end of file +working +/doc/ +/Meta/ diff --git a/DESCRIPTION b/DESCRIPTION index a1d2152..02fe68a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,16 +1,24 @@ Package: yyjsonr Type: Package -Title: Fast JSON Parsing and Serialisation +Title: A Fast JSON Parser and Generator Version: 0.1.13 Authors@R: c( - person("Mike", "FC", role = c("aut", "cre"), email = "mikefc@coolbutuseless.com"), + person("Mike", "Cheng", role = c("aut", "cre", 'cph'), + email = "mikefc@coolbutuseless.com"), person("Yao", "Yuan", role = "cph", email = "ibireme@gmail.com", - comment="author of bundled yyjson")) -Maintainer: mikefc -Description: Wrapper for yyjson - a JSON parsing and serialisation C library. + comment="Author of bundled yyjson")) +Maintainer: Mike Cheng +Description: A fast JSON parser, generator and validator which converts JSON + data to/from R objects. The standard R data types are supported + (e.g. logical, numeric, integer) with configurable handling of NULL and NA + values. Data frames, atomic vectors and lists are all supported as data + containers translated to/from JSON. + This implementation is a wrapper around the 'yyjson' C library. License: MIT + file LICENSE +URL: https://github.com/coolbutuseless/yyjsonr, https://coolbutuseless.github.io/package/yyjsonr/ +BugReports: https://github.com/coolbutuseless/yyjsonr/issues Encoding: UTF-8 -LazyData: true +Language: en-AU Roxygen: list(markdown = TRUE) RoxygenNote: 7.2.3 Suggests: diff --git a/LICENSE b/LICENSE index ff49783..b013456 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,2 @@ -MIT License - -Copyright (c) 2023 mikefc@coolbutuseless.com - - Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +YEAR: 2023,2024 +COPYRIGHT HOLDER: Mike FC diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..7c666e5 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023,2024 mikefc@coolbutuseless.com + + Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/NAMESPACE b/NAMESPACE index 8a857b1..4868e67 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,14 +2,14 @@ export(opts_read_json) export(opts_write_json) -export(read_flag) export(read_json_conn) export(read_json_file) export(read_json_raw) export(read_json_str) export(validate_json_file) export(validate_json_str) -export(write_flag) export(write_json_file) export(write_json_str) +export(yyjson_read_flag) +export(yyjson_write_flag) useDynLib(yyjsonr, .registration=TRUE) diff --git a/R/json-opts.R b/R/json-opts.R index 24eb0ce..6d465ec 100644 --- a/R/json-opts.R +++ b/R/json-opts.R @@ -9,7 +9,7 @@ #' the translation of JSON values to R. #' #" Pass multiple options with -#' \code{opts_read_json(yyjson_read_flag = c(read_flag$x, read_flag$y, ...))} +#' \code{opts_read_json(yyjson_read_flag = c(yyjson_read_flag$x, yyjson_read_flag$y, ...))} #' #' \describe{ #' \item{YYJSON_READ_NOFLAG}{ @@ -42,7 +42,7 @@ #' #' \item{YYJSON_READ_ALLOW_TRAILING_COMMAS}{ #' Allow single trailing comma at the end of an object or array, -#' such as \code{"[1,2,3,]"}, "{"a":1,"b":2,}" (non-standard). +#' such as \code{"[1,2,3,]"}, '{"a":1,"b":2,}' (non-standard). #' } #' #' \item{YYJSON_READ_ALLOW_COMMENTS}{ @@ -76,16 +76,16 @@ #' The flag will be overridden by "YYJSON_READ_NUMBER_AS_RAW" flag. #' } #' } -#' -#' @examples -#' \dontrun{ -#' read_json_str(str, opts = opts_read_json(yyjson_read_flag = read_flag$YYJSON_READ_NUMBER_AS_RAW)) -#' } #' -#' #' @export +#' +#' @examples +#' read_json_str( +#' '[12.3]', +#' opts = opts_read_json(yyjson_read_flag = yyjson_read_flag$YYJSON_READ_ALLOW_TRAILING_COMMAS) +#' ) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -read_flag <- list( +yyjson_read_flag <- list( YYJSON_READ_NOFLAG = 0L, YYJSON_READ_INSITU = 1L, YYJSON_READ_STOP_WHEN_DONE = 2L, @@ -138,17 +138,14 @@ read_flag <- list( #' This flag will override `YYJSON_WRITE_PRETTY` flag.} #' } #' -#' +#' @export +#' #' @examples -#' \dontrun{ -#' write_json_str(str, opts = opts_write_json( -#' yyjson_write_flag = write_flag$YYJSON_WRITE_ESCAPE_SLASHES +#' write_json_str("hello/there", opts = opts_write_json( +#' yyjson_write_flag = yyjson_write_flag$YYJSON_WRITE_ESCAPE_SLASHES #' )) -#' } -#' -#' @export #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -write_flag <- list( +yyjson_write_flag <- list( YYJSON_WRITE_NOFLAG = 0L, YYJSON_WRITE_PRETTY = 1L, YYJSON_WRITE_ESCAPE_UNICODE = 2L, @@ -178,12 +175,12 @@ write_flag <- list( #' a data.frame? Default: TRUE. If FALSE, then results will be read as a #' list-of-lists. #' @param yyjson_read_flag integer vector of internal \code{yyjson} -#' options. See \code{read_flag} in this package, and read +#' options. See \code{yyjson_read_flag} in this package, and read #' the yyjson API documentation for more information. This is considered #' an advanced option. #' @param str_specials Should \code{'NA'} in a JSON string be converted to the \code{'special'} #' \code{NA} value in R, or left as a \code{'string'}. Default: 'string' -#' @param num_specials Should jsong strings 'NA'/'Inf'/'NaN' in a numeric context +#' @param num_specials Should JSON strings 'NA'/'Inf'/'NaN' in a numeric context #' be converted to the \code{'special'} R numeric values #' \code{NA, Inf, NaN}, or left as a \code{'string'}. Default: 'special' #' @param promote_num_to_string Should numeric values be promoted to strings @@ -196,9 +193,12 @@ write_flag <- list( #' @param length1_array_asis logical. Should JSON arrays with length = 1 be #' marked with class \code{AsIs}. Default: FALSE #' -#' @seealso [read_flag()] -#' @return Named list of options +#' @seealso [yyjson_read_flag()] +#' @return Named list of options for reading JSON #' @export +#' +#' @examples +#' opts_read_json() #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ opts_read_json <- function( int64 = c('string', 'bit64'), @@ -255,19 +255,16 @@ opts_read_json <- function( #' converted to a JSON \code{null} value or converted to a string #' representation e.g. "NA"/"NaN" etc. Default: 'null' #' @param yyjson_write_flag integer vector corresponding to internal \code{yyjson} -#' options. See \code{write_flag} in this package, and read +#' options. See \code{yyjson_write_flag} in this package, and read #' the yyjson API documentation for more information. This is considered #' an advanced option. #' -#' @examples -#' \dontrun{ -#' write_json_str(iris, opts = opts_write_json(factor = 'integer')) -#' } -#' -#' -#' @seealso [write_flag()] -#' @return Named list of options +#' @seealso [yyjson_write_flag()] +#' @return Named list of options for writing JSON #' @export +#' +#' @examples +#' write_json_str(head(iris, 3), opts = opts_write_json(factor = 'integer')) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ opts_write_json <- function( digits = -1, diff --git a/R/json.R b/R/json.R index 15d4210..9cf025e 100644 --- a/R/json.R +++ b/R/json.R @@ -8,15 +8,12 @@ #' @param ... Other named options can be used to override any options in \code{opts}. #' The valid named options are identical to arguments to [opts_read_json()] #' -#' -#' @examples -#' \dontrun{ -#' read_json_str(str, opts = parse_opts(int64 = 'string')) -#' } -#' #' @family JSON Parsers #' @return R object #' @export +#' +#' @examples +#' read_json_str("4294967297", opts = opts_read_json(int64 = 'string')) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ read_json_str <- function(str, opts = list(), ...) { .Call( @@ -31,17 +28,14 @@ read_json_str <- function(str, opts = list(), ...) { #' #' @inheritParams read_json_str #' @param raw_vec raw vector -#' -#' -#' @examples -#' \dontrun{ -#' a <- nanonext::ncurl("https://postman-echo.com/get", convert = FALSE) -#' read_json_raw(a$raw) -#' } #' #' @family JSON Parsers #' @return R object #' @export +#' +#' @examples +#' raw_str <- as.raw(utf8ToInt('[1, 2, 3, "four"]')) +#' read_json_raw(raw_str) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ read_json_raw <- function(raw_vec, opts = list(), ...) { .Call( @@ -58,15 +52,14 @@ read_json_raw <- function(raw_vec, opts = list(), ...) { #' @inheritParams read_json_str #' @param filename full path to text file containing JSON. #' -#' -#' @examples -#' \dontrun{ -#' read_json_file("myfile.json") -#' } -#' #' @family JSON Parsers #' @return R object #' @export +#' +#' @examples +#' tmp <- tempfile() +#' write_json_file(head(iris, 3), tmp) +#' read_json_file(tmp) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ read_json_file <- function(filename, opts = list(), ...) { .Call( @@ -80,7 +73,7 @@ read_json_file <- function(filename, opts = list(), ...) { #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #' Parse JSON from an R connection object. #' -#' Currently, this isn't very efficient as the entire contents of the connection are +#' Currently, this is not very efficient as the entire contents of the connection are #' read into R as a string and then the JSON parsed from there. #' #' For plain text files it is faster to use @@ -92,7 +85,9 @@ read_json_file <- function(filename, opts = list(), ...) { #' #' @examples #' \dontrun{ -#' read_json_conn(url("https://api.github.com/users/hadley/repos")) +#' if (interactive()) { +#' read_json_conn(url("https://api.github.com/users/hadley/repos")) +#' } #' } #' #' @@ -116,14 +111,11 @@ read_json_conn <- function(conn, opts = list(), ...) { #' #' @return Character string #' -#' @examples -#' \dontrun{ -#' write_json_str(iris, pretty = TRUE) -#' write_json_str(iris, opts = opts_write_json(auto_unbox = FALSE)) -#' } -#' #' @family JSON Serializer #' @export +#' +#' @examples +#' write_json_str(head(iris, 3), pretty = TRUE) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ write_json_str <- function(x, opts = list(), ...) { .Call( @@ -140,13 +132,14 @@ write_json_str <- function(x, opts = list(), ...) { #' @inheritParams write_json_str #' @param filename filename #' -#' @examples -#' \dontrun{ -#' write_json_str(iris, filename = "iris.json") -#' } -#' +#' @return NULL #' @family JSON Serializer #' @export +#' +#' @examples +#' tmp <- tempfile() +#' write_json_file(head(iris, 3), tmp) +#' read_json_file(tmp) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ write_json_file <- function(x, filename, opts = list(), ...) { .Call( @@ -171,6 +164,11 @@ write_json_file <- function(x, filename, opts = list(), ...) { #' #' @return Logical value. TRUE if JSON validates as OK, otherwise FALSE #' @export +#' +#' @examples +#' tmp <- tempfile() +#' write_json_file(head(iris, 3), tmp) +#' validate_json_file(tmp) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ validate_json_file <- function(filename, verbose = FALSE, opts = list(), ...) { opts <- modify_list(opts, list(...)) @@ -186,6 +184,10 @@ validate_json_file <- function(filename, verbose = FALSE, opts = list(), ...) { #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #' @rdname validate_json_file #' @export +#' +#' @examples +#' str <- write_json_str(iris) +#' validate_json_str(str) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ validate_json_str <- function(str, verbose = FALSE, opts = list(), ...) { opts <- modify_list(opts, list(...)) diff --git a/README.Rmd b/README.Rmd index 5e9701b..04b1f26 100644 --- a/README.Rmd +++ b/README.Rmd @@ -152,9 +152,9 @@ yyjsonr::read_json_str(str) * GeoJSON support was removed for the initial CRAN release for the sake of my sanity. * See the `geojson` branch of this repository -## Limitiations +## Limitations -* Some datatypes not currently supported. Please file an issue on github if +* Some datatypes not currently supported. Please file an issue on GitHub if these types are critical for yoy: * Complex numbers * POSIXlt diff --git a/man/opts_read_json.Rd b/man/opts_read_json.Rd index 73ae91c..17f57fc 100644 --- a/man/opts_read_json.Rd +++ b/man/opts_read_json.Rd @@ -41,7 +41,7 @@ marked with class \code{AsIs}. Default: FALSE} \item{str_specials}{Should \code{'NA'} in a JSON string be converted to the \code{'special'} \code{NA} value in R, or left as a \code{'string'}. Default: 'string'} -\item{num_specials}{Should jsong strings 'NA'/'Inf'/'NaN' in a numeric context +\item{num_specials}{Should JSON strings 'NA'/'Inf'/'NaN' in a numeric context be converted to the \code{'special'} R numeric values \code{NA, Inf, NaN}, or left as a \code{'string'}. Default: 'special'} @@ -54,16 +54,19 @@ string values and returned as an atomic character vector. Set this to \code{TRU if you want to emulate the behaviour of \code{jsonlite::fromJSON()}} \item{yyjson_read_flag}{integer vector of internal \code{yyjson} -options. See \code{read_flag} in this package, and read +options. See \code{yyjson_read_flag} in this package, and read the yyjson API documentation for more information. This is considered an advanced option.} } \value{ -Named list of options +Named list of options for reading JSON } \description{ Create named list of options for parsing R from JSON } +\examples{ +opts_read_json() +} \seealso{ -\code{\link[=read_flag]{read_flag()}} +\code{\link[=yyjson_read_flag]{yyjson_read_flag()}} } diff --git a/man/opts_write_json.Rd b/man/opts_write_json.Rd index fe2edbf..79372f6 100644 --- a/man/opts_write_json.Rd +++ b/man/opts_write_json.Rd @@ -49,23 +49,19 @@ be converted to a JSON \code{null} value, or converted to a string "NA"? Default: 'null'} \item{yyjson_write_flag}{integer vector corresponding to internal \code{yyjson} -options. See \code{write_flag} in this package, and read +options. See \code{yyjson_write_flag} in this package, and read the yyjson API documentation for more information. This is considered an advanced option.} } \value{ -Named list of options +Named list of options for writing JSON } \description{ Create named list of options for serializing R to JSON } \examples{ -\dontrun{ -write_json_str(iris, opts = opts_write_json(factor = 'integer')) -} - - +write_json_str(head(iris, 3), opts = opts_write_json(factor = 'integer')) } \seealso{ -\code{\link[=write_flag]{write_flag()}} +\code{\link[=yyjson_write_flag]{yyjson_write_flag()}} } diff --git a/man/read_json_conn.Rd b/man/read_json_conn.Rd index df91a30..2889db5 100644 --- a/man/read_json_conn.Rd +++ b/man/read_json_conn.Rd @@ -18,7 +18,7 @@ The valid named options are identical to arguments to \code{\link[=opts_read_jso R object } \description{ -Currently, this isn't very efficient as the entire contents of the connection are +Currently, this is not very efficient as the entire contents of the connection are read into R as a string and then the JSON parsed from there. } \details{ @@ -27,7 +27,9 @@ For plain text files it is faster to use } \examples{ \dontrun{ -read_json_conn(url("https://api.github.com/users/hadley/repos")) +if (interactive()) { + read_json_conn(url("https://api.github.com/users/hadley/repos")) +} } diff --git a/man/read_json_file.Rd b/man/read_json_file.Rd index 45ae481..112e59d 100644 --- a/man/read_json_file.Rd +++ b/man/read_json_file.Rd @@ -21,10 +21,9 @@ R object Convert JSON to R } \examples{ -\dontrun{ -read_json_file("myfile.json") -} - +tmp <- tempfile() +write_json_file(head(iris, 3), tmp) +read_json_file(tmp) } \seealso{ Other JSON Parsers: diff --git a/man/read_json_raw.Rd b/man/read_json_raw.Rd index 4b853c0..68629b2 100644 --- a/man/read_json_raw.Rd +++ b/man/read_json_raw.Rd @@ -21,11 +21,8 @@ R object Convert JSON in a raw vector to R } \examples{ -\dontrun{ -a <- nanonext::ncurl("https://postman-echo.com/get", convert = FALSE) -read_json_raw(a$raw) -} - +raw_str <- as.raw(utf8ToInt('[1, 2, 3, "four"]')) +read_json_raw(raw_str) } \seealso{ Other JSON Parsers: diff --git a/man/read_json_str.Rd b/man/read_json_str.Rd index 1856a7f..2607f2a 100644 --- a/man/read_json_str.Rd +++ b/man/read_json_str.Rd @@ -21,10 +21,7 @@ R object Convert JSON in a character string to R } \examples{ -\dontrun{ -read_json_str(str, opts = parse_opts(int64 = 'string')) -} - +read_json_str("4294967297", opts = opts_read_json(int64 = 'string')) } \seealso{ Other JSON Parsers: diff --git a/man/validate_json_file.Rd b/man/validate_json_file.Rd index c3d292d..cefdf2a 100644 --- a/man/validate_json_file.Rd +++ b/man/validate_json_file.Rd @@ -28,3 +28,10 @@ Logical value. TRUE if JSON validates as OK, otherwise FALSE \description{ Validate JSON in file or string } +\examples{ +tmp <- tempfile() +write_json_file(head(iris, 3), tmp) +validate_json_file(tmp) +str <- write_json_str(iris) +validate_json_str(str) +} diff --git a/man/write_json_file.Rd b/man/write_json_file.Rd index 5885c87..7f7e79b 100644 --- a/man/write_json_file.Rd +++ b/man/write_json_file.Rd @@ -20,10 +20,9 @@ The valid named options are identical to arguments to \code{\link[=opts_write_js Convert R object to JSON file } \examples{ -\dontrun{ -write_json_str(iris, filename = "iris.json") -} - +tmp <- tempfile() +write_json_file(head(iris, 3), tmp) +read_json_file(tmp) } \seealso{ Other JSON Serializer: diff --git a/man/write_json_str.Rd b/man/write_json_str.Rd index 48eb0f2..12fbf1c 100644 --- a/man/write_json_str.Rd +++ b/man/write_json_str.Rd @@ -21,11 +21,7 @@ Character string Convert R object to JSON string } \examples{ -\dontrun{ -write_json_str(iris, pretty = TRUE) -write_json_str(iris, opts = opts_write_json(auto_unbox = FALSE)) -} - +write_json_str(head(iris, 3), pretty = TRUE) } \seealso{ Other JSON Serializer: diff --git a/man/read_flag.Rd b/man/yyjson_read_flag.Rd similarity index 89% rename from man/read_flag.Rd rename to man/yyjson_read_flag.Rd index 6dc9f88..5b32bd4 100644 --- a/man/read_flag.Rd +++ b/man/yyjson_read_flag.Rd @@ -1,14 +1,14 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/json-opts.R \docType{data} -\name{read_flag} -\alias{read_flag} +\name{yyjson_read_flag} +\alias{yyjson_read_flag} \title{Advanced: Values for setting internal options directly on YYJSON library} \format{ An object of class \code{list} of length 9. } \usage{ -read_flag +yyjson_read_flag } \description{ This is a list of integer values used for setting flags on the \code{yyjson} @@ -18,7 +18,7 @@ code directly. This is an ADVANCED option and should be used with caution. Some of these settings overlap and conflict with code needed to handle the translation of JSON values to R. -\code{opts_read_json(yyjson_read_flag = c(read_flag$x, read_flag$y, ...))} +\code{opts_read_json(yyjson_read_flag = c(yyjson_read_flag$x, yyjson_read_flag$y, ...))} \describe{ \item{YYJSON_READ_NOFLAG}{ @@ -51,7 +51,7 @@ in larger data, such as "NDJSON" \item{YYJSON_READ_ALLOW_TRAILING_COMMAS}{ Allow single trailing comma at the end of an object or array, -such as \code{"[1,2,3,]"}, "{"a":1,"b":2,}" (non-standard). +such as \code{"[1,2,3,]"}, '{"a":1,"b":2,}' (non-standard). } \item{YYJSON_READ_ALLOW_COMMENTS}{ @@ -87,10 +87,9 @@ The flag will be overridden by "YYJSON_READ_NUMBER_AS_RAW" flag. } } \examples{ -\dontrun{ -read_json_str(str, opts = opts_read_json(yyjson_read_flag = read_flag$YYJSON_READ_NUMBER_AS_RAW)) -} - - +read_json_str( + '[12.3]', + opts = opts_read_json(yyjson_read_flag = yyjson_read_flag$YYJSON_READ_ALLOW_TRAILING_COMMAS) + ) } \keyword{datasets} diff --git a/man/write_flag.Rd b/man/yyjson_write_flag.Rd similarity index 90% rename from man/write_flag.Rd rename to man/yyjson_write_flag.Rd index 5d6d0e9..c42f93c 100644 --- a/man/write_flag.Rd +++ b/man/yyjson_write_flag.Rd @@ -1,14 +1,14 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/json-opts.R \docType{data} -\name{write_flag} -\alias{write_flag} +\name{yyjson_write_flag} +\alias{yyjson_write_flag} \title{Advanced: Values for setting internal options directly on YYJSON library} \format{ An object of class \code{list} of length 8. } \usage{ -write_flag +yyjson_write_flag } \description{ This is a list of integer values used for setting flags on the \code{yyjson} @@ -49,11 +49,8 @@ This flag will override \code{YYJSON_WRITE_PRETTY} flag.} } } \examples{ -\dontrun{ -write_json_str(str, opts = opts_write_json( - yyjson_write_flag = write_flag$YYJSON_WRITE_ESCAPE_SLASHES +write_json_str("hello/there", opts = opts_write_json( + yyjson_write_flag = yyjson_write_flag$YYJSON_WRITE_ESCAPE_SLASHES )) -} - } \keyword{datasets} diff --git a/src/R-yyjson-parse.c b/src/R-yyjson-parse.c index e7d7980..7b8c224 100644 --- a/src/R-yyjson-parse.c +++ b/src/R-yyjson-parse.c @@ -14,11 +14,21 @@ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Forward declarations +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +SEXP json_array_of_objects_to_data_frame(yyjson_val *arr, parse_options *opt); +SEXP json_as_robj(yyjson_val *val, parse_options *opt); + + //=========================================================================== // Pare the R list of options into the 'parse_options' struct +// +// @param parse_opts_ An R named list of options. Passed in from the user. //=========================================================================== parse_options create_parse_options(SEXP parse_opts_) { + // Set default options parse_options opt = { .int64 = INT64_AS_STR, .missing_list_elem = MISSING_AS_NULL, @@ -31,6 +41,7 @@ parse_options create_parse_options(SEXP parse_opts_) { .yyjson_read_flag = 0 }; + // Sanity check and extract option names from the named list if (isNull(parse_opts_) || length(parse_opts_) == 0) { return opt; } @@ -44,6 +55,7 @@ parse_options create_parse_options(SEXP parse_opts_) { error("'parse_opts' must be a named list"); } + // Loop over options in R named list and assign to C struct for (int i = 0; i < length(parse_opts_); i++) { const char *opt_name = CHAR(STRING_ELT(nms_, i)); SEXP val_ = VECTOR_ELT(parse_opts_, i); @@ -82,14 +94,6 @@ parse_options create_parse_options(SEXP parse_opts_) { - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Forward declarations -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -SEXP json_array_of_objects_to_data_frame(yyjson_val *arr, parse_options *opt); -SEXP json_as_robj(yyjson_val *val, parse_options *opt); - - //=========================================================================== // ### ## // # # # @@ -98,12 +102,16 @@ SEXP json_as_robj(yyjson_val *val, parse_options *opt); // # # #### # #### # // # # # # # # # # # # // ### ### #### ### #### # +// +// The following functions parse a single yyjson JSON value to a +// scalar R value //=========================================================================== //=========================================================================== -// Convert JSON value to logical (for inclusion in LGLSXP) -// This function *ONLY* returns a value valid for an INTSXP +// Convert JSON value to logical +// +// @return value valid for inclusion in LGLSXP vector //=========================================================================== int32_t json_val_to_logical(yyjson_val *val, parse_options *opt) { @@ -137,8 +145,9 @@ int32_t json_val_to_logical(yyjson_val *val, parse_options *opt) { //=========================================================================== -// Convert JSON value to integer (for inclusion in INTSXP) -// This function *ONLY* returns a value valid for an INTSXP +// Convert JSON value to integer +// +// @return value valid for inclusion in INTSXP vector //=========================================================================== int32_t json_val_to_integer(yyjson_val *val, parse_options *opt) { @@ -180,8 +189,9 @@ int32_t json_val_to_integer(yyjson_val *val, parse_options *opt) { //=========================================================================== -// Convert JSON value to DOUBLE (for inclusion in REALSXP) -// This function *ONLY* returns a value valid for a REALSXP +// Convert JSON value to DOUBLE +// +// @return value valid for inclusion in REALSXP vector //=========================================================================== double json_val_to_double(yyjson_val *val, parse_options *opt) { @@ -233,9 +243,10 @@ double json_val_to_double(yyjson_val *val, parse_options *opt) { //=========================================================================== -// Convert JSON value to DOUBLE (for inclusion in REALSXP) -// This function *ONLY* returns a value valid for an REALSXP (which will -// get classed as a bit64::integer64 object) +// Convert JSON value to DOUBLE for Integet64 vector +// +// @return value valid for inclusion in REALSXP vector +// This vector will be classed as bit64::integer64 object //=========================================================================== long long json_val_to_integer64(yyjson_val *val, parse_options *opt) { @@ -279,7 +290,8 @@ long long json_val_to_integer64(yyjson_val *val, parse_options *opt) { //=========================================================================== // Convert JSON value to CHARSXP -// This function *ONLY* returns a value valid for inclusion in a STRSXP vec +// +// @return value valid for inclusion in STRSXP vector //=========================================================================== SEXP json_val_to_charsxp(yyjson_val *val, parse_options *opt) { @@ -351,11 +363,28 @@ SEXP json_val_to_charsxp(yyjson_val *val, parse_options *opt) { // # ## # # ## # # # # # # # # # # // # # # ### #### ### ## #### ### ## // # # # -// ### # +// ### # +// +// JSON []-arrays and {}-objects can hold any other JSON type. +// When trying to match a JSON []-array or {}-object to an R type, we first iterate +// over the JSON []-array or {}-object and use a bitset to keep track of +// which JSON types have been seen. +// +// In simple cases only a single bit in the bitset will be turned on - +// which indicates that the []-array of {}-object only contains a single +// type of value and is thus easily matched to an R vector. +// +// In more complex cases, the bitset has multiple bit sets indicating that +// there are multiple types within the container. Then the bitset must +// be examined to determine what R container should be used. +// e.g. if a []-array contains integers and doubles we could either +// (1) promote all the integers to doubles and return an R REALSXP +// (2) use a list to store the values so that their original types are +// maintained. //=========================================================================== //=========================================================================== -// Helper functon to dump out the contents of a type_bitset +// Helper function: dump out the contents of a `type_bitset` // Used only for debugging. //=========================================================================== void dump_type_bitset(unsigned int type_bitset) { @@ -545,7 +574,7 @@ unsigned int update_type_bitset(unsigned int type_bitset, yyjson_val *val, parse //=========================================================================== // Find the best SEXP type to represent values in a non-nested array. -// Non-nested means that it is known a-priori thatthis array does +// Non-nested means that it is known a-priori that this array does // not contain any JSON []-arrays or {}-objects. // // Arrays which contain only arrays could be a matrix (otherwise a list) @@ -598,7 +627,7 @@ unsigned int get_json_array_sub_container_types(yyjson_val *arr, parse_options * //=========================================================================== -// Could this array be parsed as a matrix? +// Could this non-nested array be parsed as a matrix? // * Prior to this call, this []-Array has already been shown // to only contain other []-arrays. // * Now check in this function: @@ -675,6 +704,9 @@ unsigned int get_best_sexp_type_for_matrix(yyjson_val *arr, parse_options *opt) // # # # # #### # // # # // ### +// +// The following functions parse JSON []-arrays to +// R lists or atomic vectors. //=========================================================================== //=========================================================================== @@ -1294,7 +1326,7 @@ SEXP json_array_as_robj(yyjson_val *arr, parse_options *opt) { // // Pre-requisite: // 'arr' is a JSON []-array -// []-arry only contains {}-objects +// []-array only contains {}-objects //=========================================================================== //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1412,7 +1444,7 @@ SEXP json_array_of_objects_to_vecsxp(yyjson_val *arr, const char *key_name, pars if (val == NULL) { if (opt->missing_list_elem) { - SET_VECTOR_ELT(vec_, idx, ScalarLogical(INT32_MIN)); + SET_VECTOR_ELT(vec_, idx, ScalarLogical(INT32_MIN)); // NA_logical_ } else { SET_VECTOR_ELT(vec_, idx, R_NilValue); } @@ -1582,6 +1614,8 @@ SEXP json_array_of_objects_to_data_frame(yyjson_val *arr, parse_options *opt) { // # # ### # // # # # # # // ##### ### #### ## +// +// JSON {}-object to R List //=========================================================================== SEXP json_object_as_list(yyjson_val *obj, parse_options *opt) { unsigned int nprotect = 0; @@ -1608,7 +1642,7 @@ SEXP json_object_as_list(yyjson_val *obj, parse_options *opt) { Rf_setAttrib(res_, R_NamesSymbol, nms_); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Test if list is promotable to a data.frame + // Test if list is promote-able to a data.frame // // * Opt to promote {}-object of []-arrays to data.frame // * All elements are atomic arrays or vecsxp @@ -1649,9 +1683,6 @@ SEXP json_object_as_list(yyjson_val *obj, parse_options *opt) { } } - - - UNPROTECT(nprotect); return res_; } @@ -1868,7 +1899,7 @@ SEXP parse_from_raw_(SEXP raw_, SEXP parse_opts_) { const char *str = (const char *)RAW(raw_); parse_options opt = create_parse_options(parse_opts_); - // Raw string may or may not have a NULL byter terminator. + // Raw string may or may not have a NULL byte terminator. // So ask 'yyjson' to stop parsing when the json parsing naturally ends // rather than running over into dead space after the raw string ends opt.yyjson_read_flag |= YYJSON_READ_STOP_WHEN_DONE; diff --git a/src/R-yyjson-serialize.c b/src/R-yyjson-serialize.c index 5de936f..35518cc 100644 --- a/src/R-yyjson-serialize.c +++ b/src/R-yyjson-serialize.c @@ -14,6 +14,11 @@ #include "R-yyjson-serialize.h" +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Forward declaration +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +yyjson_mut_val *serialize_core(SEXP robj_, yyjson_mut_doc *doc, serialize_options *opt); + //=========================================================================== // Parse the options from R list into a C struct @@ -34,6 +39,7 @@ serialize_options parse_serialize_options(SEXP serialize_opts_) { .yyjson_write_flag = 0, }; + // Sanity check and get option names if (isNull(serialize_opts_) || length(serialize_opts_) == 0) { return opt; } @@ -47,6 +53,7 @@ serialize_options parse_serialize_options(SEXP serialize_opts_) { error("'serialize_opts' must be a named list"); } + // Iterate over R options to populate C options struct for (int i = 0; i < length(serialize_opts_); i++) { const char *opt_name = CHAR(STRING_ELT(nms_, i)); SEXP val_ = VECTOR_ELT(serialize_opts_, i); @@ -86,11 +93,6 @@ serialize_options parse_serialize_options(SEXP serialize_opts_) { return opt; } -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Forward declaration -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -yyjson_mut_val *serialize_core(SEXP robj_, yyjson_mut_doc *doc, serialize_options *opt); - //=========================================================================== // ### ## @@ -146,7 +148,7 @@ yyjson_mut_val *scalar_integer_to_json_val(int32_t rint, yyjson_mut_doc *doc, se } //=========================================================================== -// Scalar bit64::integer64 stored in REALSXP to JSON value +// Scalar bit64::integer64 (stored in REALSXP) to JSON value //=========================================================================== yyjson_mut_val *scalar_integer64_to_json_val(SEXP vec_, unsigned int idx, yyjson_mut_doc *doc, serialize_options *opt) { @@ -367,6 +369,7 @@ yyjson_mut_val *vector_lglsxp_to_json_array(SEXP vec_, yyjson_mut_doc *doc, seri return arr; } + //=========================================================================== // Serialize factor to JSON []-array //=========================================================================== @@ -382,8 +385,6 @@ yyjson_mut_val *vector_factor_to_json_array(SEXP vec_, yyjson_mut_doc *doc, seri } - - //=========================================================================== // Serialize RAWSXP to JSON []-array //=========================================================================== @@ -415,7 +416,6 @@ yyjson_mut_val *vector_posixct_to_json_array(SEXP vec_, yyjson_mut_doc *doc, ser } - //=========================================================================== // Serialize Date stored in REALSXP or INTSXP to JSON []-array //=========================================================================== @@ -431,7 +431,6 @@ yyjson_mut_val *vector_date_to_json_array(SEXP vec_, yyjson_mut_doc *doc, serial } - //=========================================================================== // Serialize integer64 stored in REALSXP to JSON []-array //=========================================================================== @@ -670,7 +669,7 @@ yyjson_mut_val *dim3_matrix_to_col_major_array(SEXP mat_, yyjson_mut_doc *doc, s //=========================================================================== yyjson_mut_val *env_to_json_object(SEXP env_, yyjson_mut_doc *doc, serialize_options *opt) { if (!isEnvironment(env_)) { - error("env_to_json_object(): Expected list. got %s", type2char(TYPEOF(env_))); + error("env_to_json_object(): Expected environment. got %s", type2char(TYPEOF(env_))); } unsigned int nprotect = 0; @@ -705,7 +704,7 @@ yyjson_mut_val *env_to_json_object(SEXP env_, yyjson_mut_doc *doc, serialize_opt // # # # # # # # # # # # # # ## # # # # # // ### # # # # #### # # ### ## # ##### ### #### ## // -// Serialize an unnamed list to an array +// Serialize an unnamed list to a JSON []-array //=========================================================================== yyjson_mut_val *unnamed_list_to_json_array(SEXP list_, yyjson_mut_doc *doc, serialize_options *opt) { if (!isNewList(list_)) { @@ -992,13 +991,17 @@ yyjson_mut_val *data_frame_row_to_json_array(SEXP df_, unsigned int *col_type, u } +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Special case for an unnamed-data.frame conversion to an +// array of arrays +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ yyjson_mut_val *data_frame_to_json_array_of_arrays(SEXP df_, yyjson_mut_doc *doc, serialize_options *opt) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Sanity check //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if (!Rf_inherits(df_, "data.frame")) { - error("data_frame_to_json_array_of_objects(). Not a data.frame!! %s", type2char(TYPEOF(df_))); + error("data_frame_to_json_array_of_arrays(). Not a data.frame!! %s", type2char(TYPEOF(df_))); } @@ -1031,6 +1034,9 @@ yyjson_mut_val *data_frame_to_json_array_of_arrays(SEXP df_, yyjson_mut_doc *doc } +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ yyjson_mut_val *data_frame_to_json_array_of_objects(SEXP df_, yyjson_mut_doc *doc, serialize_options *opt) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/testthat/test-yyjson-flags.R b/tests/testthat/test-yyjson-flags.R index 726e19e..6e6872f 100644 --- a/tests/testthat/test-yyjson-flags.R +++ b/tests/testthat/test-yyjson-flags.R @@ -8,7 +8,7 @@ test_that("yyjson flags work", { ) expect_equal( - read_json_str('[1,2,3,]', yyjson_read_flag = read_flag$YYJSON_READ_ALLOW_TRAILING_COMMAS), + read_json_str('[1,2,3,]', yyjson_read_flag = yyjson_read_flag$YYJSON_READ_ALLOW_TRAILING_COMMAS), c(1L, 2L, 3L) )