Skip to content

Commit

Permalink
Merge pull request #24 from andrjohns/json-functions
Browse files Browse the repository at this point in the history
Add separate to_json and from_json functions
  • Loading branch information
andrjohns authored May 14, 2024
2 parents 240d157 + 6ee69ca commit 8448c8c
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 64 deletions.
3 changes: 2 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

export(JSContext)
export(cxxflags)
export(from_json)
export(ldflags)
export(qjs_eval)
export(qjs_passthrough)
export(quickjs_version)
export(to_json)
importFrom(jsonlite,fromJSON)
useDynLib(QuickJSR, .registration = TRUE)
25 changes: 18 additions & 7 deletions R/qjs.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,26 @@ qjs_validate <- function(ctx_ptr, function_name) {
.Call(`qjs_validate_`, ctx_ptr, function_name)
}

#' qjs_passthrough
#' to_json
#'
#' Test function to pass through arguments
#' Use the QuickJS C API to convert an R object to a JSON string
#'
#' @param args Args to pass through
#' @param json Whether to return a JSON string
#' @return The input argument unchanged
#' @param arg Argument to convert to JSON
#' @return JSON string
#'
#' @export
qjs_passthrough <- function(args, json = TRUE) {
.Call(`qjs_passthrough_`, args, json)
to_json <- function(arg) {
.Call(`to_json_`, arg)
}

#' from_json
#'
#' Use the QuickJS C API to convert a JSON string to an R object
#'
#' @param json JSON string to convert to an R object
#' @return R object
#'
#' @export
from_json <- function(json) {
.Call(`from_json_`, json)
}
2 changes: 2 additions & 0 deletions inst/include/quickjsr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
#include <quickjsr/SEXP_to_JSValue.hpp>
#include <quickjsr/JSValue_to_SEXP.hpp>
#include <quickjsr/JSValue_to_JSON.hpp>
#include <quickjsr/JSON_to_JSValue.hpp>
#include <quickjsr/type_traits.hpp>

#endif
30 changes: 30 additions & 0 deletions inst/include/quickjsr/JSON_to_JSValue.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef QUICKJSR_JSON_TO_JSVALUE_HPP
#define QUICKJSR_JSON_TO_JSVALUE_HPP

#include <cpp11.hpp>
#include <quickjs-libc.h>

namespace quickjsr {

JSValue JSON_to_JSValue(JSContext* ctx, const std::string& json) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue json_obj = JS_GetPropertyStr(ctx, global, "JSON");
JSValue parse = JS_GetPropertyStr(ctx, json_obj, "parse");

JSValue json_str = JS_NewString(ctx, json.c_str());
JSValue result = JS_Call(ctx, parse, global, 1, &json_str);

if (JS_IsException(result)) {
js_std_dump_error(ctx);
}

JS_FreeValue(ctx, json_str);
JS_FreeValue(ctx, parse);
JS_FreeValue(ctx, json_obj);
JS_FreeValue(ctx, global);

return result;
}

} // namespace quickjsr
#endif
2 changes: 1 addition & 1 deletion inst/include/quickjsr/JSValue_to_JSON.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace quickjsr {

std::string JS_ValToJSON(JSContext* ctx, JSValue* val) {
std::string JSValue_to_JSON(JSContext* ctx, JSValue* val) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
JSValue stringify = JS_GetPropertyStr(ctx, json, "stringify");
Expand Down
5 changes: 3 additions & 2 deletions inst/tinytest/test_data_conversion.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# - The outputs are returned as JSON strings
# - If the conversion is consistent, the output should match jsonlite's conversion
expect_eq_jsonlite <- function(x) {
expect_equal(qjs_passthrough(x), as.character(jsonlite::toJSON(x)))
expect_equal(to_json(x), as.character(jsonlite::toJSON(x)))
}
expect_eq_jsonlite(1)
expect_eq_jsonlite(1:3)
Expand Down Expand Up @@ -31,7 +31,8 @@ expect_eq_jsonlite(list(list(a = "e", b = "f"), list(c = "g", d = "h")))

# Test that the full round-trip conversion is consistent.
expect_eq_jsonlite_full <- function(x) {
expect_equal(qjs_passthrough(x, FALSE), jsonlite::fromJSON(jsonlite::toJSON(x)))
x_json <- jsonlite::toJSON(x)
expect_equal(from_json(x_json), jsonlite::fromJSON(x_json))
}
expect_eq_jsonlite_full(1)
expect_eq_jsonlite_full(1:3)
Expand Down
17 changes: 17 additions & 0 deletions man/from_json.Rd

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

19 changes: 0 additions & 19 deletions man/qjs_passthrough.Rd

This file was deleted.

17 changes: 17 additions & 0 deletions man/to_json.Rd

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

6 changes: 4 additions & 2 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ extern "C" {
SEXP qjs_validate_(SEXP ctx_ptr_, SEXP code_string_);
SEXP qjs_call_(SEXP ctx_ptr_, SEXP function_name_, SEXP args_json_);
SEXP qjs_eval_(SEXP eval_string_);
SEXP qjs_passthrough_(SEXP args_, SEXP json_rtn_);
SEXP to_json_(SEXP arg_);
SEXP from_json_(SEXP json_);


static const R_CallMethodDef CallEntries[] = {
Expand All @@ -19,7 +20,8 @@ extern "C" {
{"qjs_eval_", (DL_FUNC) &qjs_eval_, 1},
{"qjs_source_", (DL_FUNC) &qjs_source_, 2},
{"qjs_validate_", (DL_FUNC) &qjs_validate_, 2},
{"qjs_passthrough_", (DL_FUNC) &qjs_passthrough_, 2},
{"to_json_", (DL_FUNC) &to_json_, 1},
{"from_json_", (DL_FUNC) &from_json_, 1},
{NULL, NULL, 0}
};

Expand Down
55 changes: 23 additions & 32 deletions src/quickjsr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ extern "C" SEXP qjs_call_(SEXP ctx_ptr_, SEXP function_name_, SEXP args_json_) {
js_std_dump_error(ctx);
result = "Error!";
} else {
result = quickjsr::JS_ValToJSON(ctx, &result_js);
result = quickjsr::JSValue_to_JSON(ctx, &result_js);
}

JS_FreeValue(ctx, result_js);
Expand All @@ -114,7 +114,7 @@ extern "C" SEXP qjs_eval_(SEXP eval_string_) {
js_std_dump_error(ctx);
result = "Error!";
} else {
result = quickjsr::JS_ValToJSON(ctx, &val);
result = quickjsr::JSValue_to_JSON(ctx, &val);
}

JS_FreeValue(ctx, val);
Expand All @@ -125,44 +125,35 @@ extern "C" SEXP qjs_eval_(SEXP eval_string_) {
END_CPP11
}

extern "C" SEXP qjs_passthrough_(SEXP args_, SEXP json_rtn_) {
extern "C" SEXP to_json_(SEXP arg_) {
BEGIN_CPP11
JSRuntime* rt = JS_NewRuntime();
JSContext* ctx = JS_NewContext(rt);

std::string function_string = "function passthrough(x) { return x; }";
JSValue tmp = JS_Eval(ctx, function_string.c_str(), function_string.size(), "", 0);
bool failed = JS_IsException(tmp);
JS_FreeValue(ctx, tmp);
if (failed) {
js_std_dump_error(ctx);
return cpp11::as_sexp("Error!");
}
std::string wrapped_name = "passthrough";
JSValue global = JS_GetGlobalObject(ctx);
JSValue function_wrapper = JS_GetPropertyStr(ctx, global, wrapped_name.c_str());
JSValue args[] = { quickjsr::SEXP_to_JSValue(ctx, args_) };
JSValue result_js = JS_Call(ctx, function_wrapper, global, 1, args);
JSValue arg = quickjsr::SEXP_to_JSValue(ctx, arg_);
std::string result = quickjsr::JSValue_to_JSON(ctx, &arg);

SEXP result;
if (JS_IsException(result_js)) {
js_std_dump_error(ctx);
result = cpp11::as_sexp("Error!");
} else {
if (cpp11::as_cpp<bool>(json_rtn_)) {
result = cpp11::as_sexp(quickjsr::JS_ValToJSON(ctx, &result_js));
} else {
result = quickjsr::JSValue_to_SEXP(ctx, result_js);
}
}

JS_FreeValue(ctx, result_js);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, function_wrapper);
JS_FreeValue(ctx, global);
JS_FreeValue(ctx, arg);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);

return cpp11::as_sexp(result);
END_CPP11
}

extern "C" SEXP from_json_(SEXP json_) {
BEGIN_CPP11
JSRuntime* rt = JS_NewRuntime();
JSContext* ctx = JS_NewContext(rt);

std::string json = cpp11::as_cpp<std::string>(json_);
JSValue result = quickjsr::JSON_to_JSValue(ctx, json);
SEXP rtn = quickjsr::JSValue_to_SEXP(ctx, result);

JS_FreeValue(ctx, result);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);

return rtn;
END_CPP11
}

0 comments on commit 8448c8c

Please sign in to comment.