From cd28d2e39cbe9a99c292425f558f8e2cfdd7c3c1 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 12 Sep 2023 20:18:09 +1000 Subject: [PATCH] Handle unnamed data.frames like jsonlite. Fixes #11 --- DESCRIPTION | 2 +- NEWS.md | 7 ++ src/R-yyjson-serialize.c | 111 +++++++++++++++++- .../test-dataframe-without-colnames.R | 15 +++ 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/test-dataframe-without-colnames.R diff --git a/DESCRIPTION b/DESCRIPTION index 2704580..37a5274 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: yyjsonr Type: Package Title: Fast JSON, GeoJSON and NDJSON Parsing and Serialisation -Version: 0.1.7 +Version: 0.1.8 Authors@R: c( person("Mike", "FC", role = c("aut", "cre"), email = "mikefc@coolbutuseless.com"), person("Yao", "Yuan", role = "cph", email = "ibireme@gmail.com", diff --git a/NEWS.md b/NEWS.md index 8841089..47e701a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,11 @@ +# yyjsonr 0.1.8 2023-09-12 + +* Added `path.expand()` when handling filenames. Thanks to + * https://github.com/shikokuchuo + * https://github.com/hrbrmstr +* Added support for data.frames without column names to match + behaviour of `jsonlite` (when working within the `plotly` package) # yyjsonr 0.1.7 2023-09-10 diff --git a/src/R-yyjson-serialize.c b/src/R-yyjson-serialize.c index 5d2a317..d6d240f 100644 --- a/src/R-yyjson-serialize.c +++ b/src/R-yyjson-serialize.c @@ -835,7 +835,7 @@ yyjson_mut_val *data_frame_row_to_json_object(SEXP df_, unsigned int row, int sk val = scalar_rawsxp_to_json_val(col_, row, doc, opt); break; default: - error("data_frame_to_json_array_of_objects(): Unhandled scalar SEXP: %s\n", type2char(TYPEOF(col_))); + error("data_frame_row_to_json_object(): Unhandled scalar SEXP: %s\n", type2char(TYPEOF(col_))); } // Add value to row obj if (val != NULL) { @@ -847,6 +847,110 @@ yyjson_mut_val *data_frame_row_to_json_object(SEXP df_, unsigned int row, int sk } +//=========================================================================== +yyjson_mut_val *data_frame_row_to_json_array(SEXP df_, unsigned int row, int skip_col, yyjson_mut_doc *doc, serialize_options *opt) { + + // get data.frame names + unsigned int ncols = length(df_); + + yyjson_mut_val *arr = yyjson_mut_arr(doc); + + for (int col = 0; col < ncols; col++) { + if (col == skip_col) continue; + yyjson_mut_val *val; + SEXP col_ = VECTOR_ELT(df_, col); + + switch(TYPEOF(col_)) { + case LGLSXP: + val = scalar_logical_to_json_val(INTEGER(col_)[row], doc, opt); + break; + case INTSXP: + if (isFactor(col_)) { + val = scalar_factor_to_json_val(col_, row, doc, opt); + } else if (inherits(col_, "Date")) { + val = scalar_date_to_json_val(col_, row, doc, opt); + } else if (inherits(col_, "POSIXct")) { + val = scalar_posixct_to_json_val(col_, row, doc, opt); + } else { + val = scalar_integer_to_json_val(INTEGER(col_)[row], doc, opt); + } + break; + case REALSXP: { + if (inherits(col_, "Date")) { + val = scalar_date_to_json_val(col_, row, doc, opt); + } else if (inherits(col_, "POSIXct")) { + val = scalar_posixct_to_json_val(col_, row, doc, opt); + } else if (inherits(col_, "integer64")) { + val = scalar_integer64_to_json_val(col_, row, doc, opt); + } else { + val = scalar_double_to_json_val(REAL(col_)[row], doc, opt); + } + } + break; + case STRSXP: { + val = scalar_strsxp_to_json_val(col_, row, doc, opt); + } + break; + case VECSXP: { + if (inherits(col_, "data.frame")) { + val = data_frame_row_to_json_object(col_, row, -1, doc, opt); + } else { + val = serialize_core(VECTOR_ELT(col_, row), doc, opt); + } + } + break; + case RAWSXP: + val = scalar_rawsxp_to_json_val(col_, row, doc, opt); + break; + default: + error("data_frame_row_to_json_array(): Unhandled scalar SEXP: %s\n", type2char(TYPEOF(col_))); + } + // Add value to row obj + if (val != NULL) { + yyjson_mut_arr_add_val(arr, val); + } + } + + return arr; +} + + +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_))); + } + + + // Create an array + yyjson_mut_val *arr = yyjson_mut_arr(doc); + + // get size of data.frame + unsigned int nrows = length(VECTOR_ELT(df_, 0)); // length of first column + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // For each row + // create an array + // for each column + // add the value to the array + // add the array to the overall array + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + for (int row = 0; row < nrows; row++) { + + yyjson_mut_val *obj = data_frame_row_to_json_array(df_, row, -1, doc, opt); + + // Add row obj to array + yyjson_mut_arr_append(arr, obj); + } + + // Return the array of row objects + return arr; +} + + yyjson_mut_val *data_frame_to_json_array_of_objects(SEXP df_, yyjson_mut_doc *doc, serialize_options *opt) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -855,6 +959,11 @@ yyjson_mut_val *data_frame_to_json_array_of_objects(SEXP df_, yyjson_mut_doc *do if (!Rf_inherits(df_, "data.frame")) { error("data_frame_to_json_array_of_objects(). Not a data.frame!! %s", type2char(TYPEOF(df_))); } + + + if (isNull(getAttrib(df_, R_NamesSymbol))) { + return data_frame_to_json_array_of_arrays(df_, doc, opt); + } // Create an array yyjson_mut_val *arr = yyjson_mut_arr(doc); diff --git a/tests/testthat/test-dataframe-without-colnames.R b/tests/testthat/test-dataframe-without-colnames.R new file mode 100644 index 0000000..4cb828a --- /dev/null +++ b/tests/testthat/test-dataframe-without-colnames.R @@ -0,0 +1,15 @@ + + + +test_that("data.frames without column names work like jsonlite", { + + # output an array of arrays. + # the inner array represents a row of the data.frame + + aa <- data.frame(x = 1:2, y = c('y', 'n')) + colnames(aa) <- NULL + + s <- write_json_str(aa) + expect_equal(s, '[[1,"y"],[2,"n"]]') + +})