Skip to content

Commit

Permalink
nan: add support for JSON::Parse & Stringify (#651)
Browse files Browse the repository at this point in the history
* nan: add support for JSON::Parse & Stringify

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: entry points need a Nan::HandleScope

Add a Nan::HandleScope to each static entry point.

The private singleton class constructor does not need a Nan::HandleScope,
because it can only be called from one of the static entry points.

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: ditch the singleton and remove all persistence

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: we *always* need a HandleScope

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: use Nan::To<v8::String> rather than ->ToString()

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: use Nan::Get rather than v8::Object::Get

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: use Nan::To<v8::Object> rather than ->ToObject()

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: proper HandleScope placement for Parse()

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* doc/json.md: Nan::To<v8::Object> returns a MaybeLocal<v8::Object>

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: dont call ToLocalChecked on an empty MaybeLocal

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: replace v8::MaybeLocal with Nan::MaybeLocal

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: check MaybeLocal returned by Nan::To<v8::Object>

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* remove rvagg from notifications

* nan_json.h: replace v8::MaybeLocal with Nan::MaybeLocal

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* convert Nan::JSON to an RAII class

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: all entry points need a Nan::EscapableHandleScope

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: v8::JSON::Parse() that takes no context returns v8::Local<v8::Value>

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: check !Nan::Callback.IsEmpty() before calling it

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* Revert "remove rvagg from notifications"

This reverts commit 00a98ec.

* nan_json.h: rename for consistency: jsonObject->json_object, jsonString->json_string

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-parse.cpp: fix memory leak

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: rename m_cb_parse->parse_cb_ & m_cb_stringify->stringify_cb_

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: remove unnecessary destructor

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: identify the #endif's

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: rename parseMethod->parse_method & stringifyMethod->stringify_method

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: use Local<Value>.As<Function> rather than Local<Function>::Cast

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: convert camelCase locals to snake_case

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: remove parens from precompiler directives

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: check NODE_MODULE_VERSION rather than NODE_MAJOR_VERSION & NODE_MINOR_VERSION

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* doc/json.md: check MaybeLocal before extraction

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* doc/json.md: clean up 'Use JSON.Api(arg) to ...' short description

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* doc/json.md: Nan::JSON.[Api] instead of Nan::JSON::[Api]

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-parse.cpp: check MaybeLocal before proceeding

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-stringify.cpp: improve argument checks

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/strings.cpp: check MaybeLocal before proceeding

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-*.cpp: return Nan::Undefined() in the MaybeLocal.IsEmpty() case

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/js/json-parse-test.js: add more test cases

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/js/json-stringify-test.js: add more test cases

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/js/json-parse-test.js: add spaces near square brackets in arrays

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-stringify.cpp: check gap argument before extraction

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-stringify.cpp: replace v8::MaybeLocal with Nan::MaybeLocal

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: JSON constructor needs a Nan::HandleScope

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-*.cpp: prefer the fast primitive setters where available

replace ReturnValue::Set(Nan::Undefined()) with ReturnValue::SetUndefined()

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: add assertions to constructor sanity checks

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: convert remaining sanity checks to assertions

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* test/cpp/json-*.cpp: no need to set return value to Undefined as it is the default

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>

* nan_json.h: remove superfluous newlines after assert

Signed-off-by: Michael Ira Krufky <mkrufky@gmail.com>
  • Loading branch information
mkrufky authored and kkoopa committed Apr 4, 2017
1 parent 5b7083b commit b533226
Show file tree
Hide file tree
Showing 12 changed files with 479 additions and 3 deletions.
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ LINT_SOURCES = \
nan_converters_pre_43_inl.h \
nan_implementation_12_inl.h \
nan_implementation_pre_12_inl.h \
nan_json.h \
nan_maybe_43_inl.h \
nan_maybe_pre_43_inl.h \
nan_new.h \
Expand All @@ -46,6 +47,8 @@ LINT_SOURCES = \
test/cpp/indexedinterceptors.cpp \
test/cpp/converters.cpp \
test/cpp/isolatedata.cpp \
test/cpp/json-parse.cpp \
test/cpp/json-stringify.cpp \
test/cpp/makecallback.cpp \
test/cpp/morenews.cpp \
test/cpp/multifile1.cpp \
Expand Down Expand Up @@ -90,14 +93,14 @@ forcetest:
docs: README.md doc/.build.sh doc/asyncworker.md doc/buffers.md doc/callback.md \
doc/converters.md doc/errors.md doc/maybe_types.md doc/methods.md doc/new.md \
doc/node_misc.md doc/persistent.md doc/scopes.md doc/script.md doc/string_bytes.md \
doc/v8_internals.md doc/v8_misc.md
doc/v8_internals.md doc/json.md doc/v8_misc.md
doc/.build.sh


$(ADDONS): nan.h nan_new.h nan_implementation_pre_12_inl.h nan_implementation_12_inl.h \
nan_callbacks.h nan_callbacks_12_inl.h nan_callbacks_pre_12_inl.h \
nan_converters.h nan_converters_43_inl.h nan_converters_pre_43_inl.h \
nan_maybe_43_inl.h nan_maybe_pre_43_inl.h \
nan_json.h nan_maybe_43_inl.h nan_maybe_pre_43_inl.h \
nan_persistent_12_inl.h nan_persistent_pre_12_inl.h nan_private.h \
nan_weak.h nan_string_bytes.h test/binding.gyp $(SOURCES)
cd test/ && ../node_modules/.bin/node-gyp rebuild
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,15 @@ NAN provides a `v8::Script` helpers as the API has changed over the supported ve
- <a href="doc/script.md#api_nan_run_script"><b><code>Nan::RunScript()</code></b></a>


### JSON

The _JSON_ object provides the c++ versions of the methods offered by the `JSON` object in javascript. V8 exposes these methods via the `v8::JSON` object.

- <a href="doc/json.md#api_nan_json_parse"><b><code>Nan::JSON.Parse</code></b></a>
- <a href="doc/json.md#api_nan_json_stringify"><b><code>Nan::JSON.Stringify</code></b></a>

Refer to the V8 JSON object in the [V8 documentation](https://v8docs.nodesource.com/node-7.4/da/d6f/classv8_1_1_j_s_o_n.html) for more information about these methods and their arguments.

### Errors

NAN includes helpers for creating, throwing and catching Errors as much of this functionality varies across the supported versions of V8 and must be abstracted.
Expand Down
1 change: 1 addition & 0 deletions doc/.build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ files=" \
converters.md \
maybe_types.md \
script.md \
json.md \
errors.md \
buffers.md \
callback.md \
Expand Down
62 changes: 62 additions & 0 deletions doc/json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## JSON

The _JSON_ object provides the c++ versions of the methods offered by the `JSON` object in javascript. V8 exposes these methods via the `v8::JSON` object.

- <a href="#api_nan_json_parse"><b><code>Nan::JSON.Parse</code></b></a>
- <a href="#api_nan_json_stringify"><b><code>Nan::JSON.Stringify</code></b></a>

Refer to the V8 JSON object in the [V8 documentation](https://v8docs.nodesource.com/node-7.4/da/d6f/classv8_1_1_j_s_o_n.html) for more information about these methods and their arguments.

<a name="api_nan_json_parse"></a>

### Nan::JSON.Parse

A simple wrapper around [`v8::JSON::Parse`](https://v8docs.nodesource.com/node-7.4/da/d6f/classv8_1_1_j_s_o_n.html#a936310d2540fb630ed37d3ee3ffe4504).

Definition:

```c++
Nan::MaybeLocal<v8::Value> Nan::JSON::Parse(v8::Local<v8::String> json_string);
```
Use `JSON.Parse(json_string)` to parse a string into a `v8::Value`.
Example:
```c++
v8::Local<v8::String> json_string = Nan::New("{ \"JSON\": \"object\" }").ToLocalChecked();
Nan::JSON NanJSON;
Nan::MaybeLocal<v8::Value> result = NanJSON.Parse(json_string);
if (!result.IsEmpty()) {
v8::Local<v8::Value> val = result.ToLocalChecked();
}
```

<a name="api_nan_json_stringify"></a>

### Nan::JSON.Stringify

A simple wrapper around [`v8::JSON::Stringify`](https://v8docs.nodesource.com/node-7.4/da/d6f/classv8_1_1_j_s_o_n.html#a44b255c3531489ce43f6110209138860).

Definition:

```c++
Nan::MaybeLocal<v8::String> Nan::JSON::Stringify(v8::Local<v8::Object> json_object, v8::Local<v8::String> gap = v8::Local<v8::String>());
```
Use `JSON.Stringify(value)` to stringify a `v8::Object`.
Example:
```c++
// using `v8::Local<v8::Value> val` from the `JSON::Parse` example
v8::Local<v8::Object> obj = Nan::To<v8::Object>(val).ToLocalChecked();
Nan::JSON NanJSON;
Nan::MaybeLocal<v8::String> result = NanJSON.Stringify(obj);
if (!result.IsEmpty()) {
v8::Local<v8::String> stringified = result.ToLocalChecked();
}
```

4 changes: 4 additions & 0 deletions nan.h
Original file line number Diff line number Diff line change
Expand Up @@ -2310,6 +2310,10 @@ MakeMaybe(MaybeMaybe<T> v) {

#include "nan_typedarray_contents.h" // NOLINT(build/include)

//=== JSON =====================================================================

#include "nan_json.h" // NOLINT(build/include)

} // end of namespace Nan

#endif // NAN_H_
155 changes: 155 additions & 0 deletions nan_json.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*********************************************************************
* NAN - Native Abstractions for Node.js
*
* Copyright (c) 2017 NAN contributors
*
* MIT License <https://github.com/nodejs/nan/blob/master/LICENSE.md>
********************************************************************/

#ifndef NAN_JSON_H_
#define NAN_JSON_H_

#if NODE_MODULE_VERSION < NODE_0_12_MODULE_VERSION
#define NAN_JSON_H_NEED_PARSE 1
#else
#define NAN_JSON_H_NEED_PARSE 0
#endif // NODE_MODULE_VERSION < NODE_0_12_MODULE_VERSION

#if NODE_MODULE_VERSION > NODE_6_0_MODULE_VERSION
#define NAN_JSON_H_NEED_STRINGIFY 0
#else
#define NAN_JSON_H_NEED_STRINGIFY 1
#endif // NODE_MODULE_VERSION > NODE_6_0_MODULE_VERSION

class JSON {
public:
JSON() {
#if NAN_JSON_H_NEED_PARSE + NAN_JSON_H_NEED_STRINGIFY
Nan::HandleScope scope;

Nan::MaybeLocal<v8::Value> maybe_global_json = Nan::Get(
Nan::GetCurrentContext()->Global(),
Nan::New("JSON").ToLocalChecked()
);

assert(!maybe_global_json.IsEmpty() && "global JSON is empty");
v8::Local<v8::Value> val_global_json = maybe_global_json.ToLocalChecked();

assert(val_global_json->IsObject() && "global JSON is not an object");
Nan::MaybeLocal<v8::Object> maybe_obj_global_json =
Nan::To<v8::Object>(val_global_json);

assert(!maybe_obj_global_json.IsEmpty() && "global JSON object is empty");
v8::Local<v8::Object> global_json = maybe_obj_global_json.ToLocalChecked();

#if NAN_JSON_H_NEED_PARSE
Nan::MaybeLocal<v8::Value> maybe_parse_method = Nan::Get(
global_json, Nan::New("parse").ToLocalChecked()
);

assert(!maybe_parse_method.IsEmpty() && "JSON.parse is empty");
v8::Local<v8::Value> parse_method = maybe_parse_method.ToLocalChecked();

assert(parse_method->IsFunction() && "JSON.parse is not a function");
parse_cb_.Reset(parse_method.As<v8::Function>());
#endif // NAN_JSON_H_NEED_PARSE

#if NAN_JSON_H_NEED_STRINGIFY
Nan::MaybeLocal<v8::Value> maybe_stringify_method = Nan::Get(
global_json, Nan::New("stringify").ToLocalChecked()
);

assert(!maybe_stringify_method.IsEmpty() && "JSON.stringify is empty");
v8::Local<v8::Value> stringify_method =
maybe_stringify_method.ToLocalChecked();

assert(
stringify_method->IsFunction() && "JSON.stringify is not a function"
);
stringify_cb_.Reset(stringify_method.As<v8::Function>());
#endif // NAN_JSON_H_NEED_STRINGIFY
#endif // NAN_JSON_H_NEED_PARSE + NAN_JSON_H_NEED_STRINGIFY
}

inline
Nan::MaybeLocal<v8::Value> Parse(v8::Local<v8::String> json_string) {
Nan::EscapableHandleScope scope;
#if NAN_JSON_H_NEED_PARSE
return scope.Escape(parse(json_string));
#else
#if NODE_MODULE_VERSION > NODE_6_0_MODULE_VERSION
Nan::MaybeLocal<v8::Value> result =
v8::JSON::Parse(Nan::GetCurrentContext(), json_string);

if (result.IsEmpty()) return v8::Local<v8::Value>();
return scope.Escape(result.ToLocalChecked());
#else
return scope.Escape(v8::JSON::Parse(json_string));
#endif // NODE_MODULE_VERSION > NODE_6_0_MODULE_VERSION
#endif // NAN_JSON_H_NEED_PARSE
}

inline
Nan::MaybeLocal<v8::String> Stringify(v8::Local<v8::Object> json_object) {
Nan::EscapableHandleScope scope;
Nan::MaybeLocal<v8::String> result =
#if NAN_JSON_H_NEED_STRINGIFY
Nan::To<v8::String>(stringify(json_object));
#else
v8::JSON::Stringify(Nan::GetCurrentContext(), json_object);
#endif // NAN_JSON_H_NEED_STRINGIFY
if (result.IsEmpty()) return v8::Local<v8::String>();
return scope.Escape(result.ToLocalChecked());
}

inline
Nan::MaybeLocal<v8::String> Stringify(v8::Local<v8::Object> json_object,
v8::Local<v8::String> gap) {
Nan::EscapableHandleScope scope;
Nan::MaybeLocal<v8::String> result =
#if NAN_JSON_H_NEED_STRINGIFY
Nan::To<v8::String>(stringify(json_object, gap));
#else
v8::JSON::Stringify(Nan::GetCurrentContext(), json_object, gap);
#endif // NAN_JSON_H_NEED_STRINGIFY
if (result.IsEmpty()) return v8::Local<v8::String>();
return scope.Escape(result.ToLocalChecked());
}

private:
NAN_DISALLOW_ASSIGN_COPY_MOVE(JSON)
#if NAN_JSON_H_NEED_PARSE
Nan::Callback parse_cb_;
#endif // NAN_JSON_H_NEED_PARSE
#if NAN_JSON_H_NEED_STRINGIFY
Nan::Callback stringify_cb_;
#endif // NAN_JSON_H_NEED_STRINGIFY

#if NAN_JSON_H_NEED_PARSE
inline v8::Local<v8::Value> parse(v8::Local<v8::Value> arg) {
assert(!parse_cb_.IsEmpty() && "parse_cb_ is empty");
return parse_cb_.Call(1, &arg);
}
#endif // NAN_JSON_H_NEED_PARSE

#if NAN_JSON_H_NEED_STRINGIFY
inline v8::Local<v8::Value> stringify(v8::Local<v8::Value> arg) {
assert(!stringify_cb_.IsEmpty() && "stringify_cb_ is empty");
return stringify_cb_.Call(1, &arg);
}

inline v8::Local<v8::Value> stringify(v8::Local<v8::Value> arg,
v8::Local<v8::String> gap) {
assert(!stringify_cb_.IsEmpty() && "stringify_cb_ is empty");

v8::Local<v8::Value> argv[] = {
arg,
Nan::Null(),
gap
};
return stringify_cb_.Call(3, argv);
}
#endif // NAN_JSON_H_NEED_STRINGIFY
};

#endif // NAN_JSON_H_
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"Nathan Rajlich <nathan@tootallnate.net> (https://github.com/TooTallNate)",
"Brett Lawson <brett19@gmail.com> (https://github.com/brett19)",
"Ben Noordhuis <info@bnoordhuis.nl> (https://github.com/bnoordhuis)",
"David Siegel <david@artcom.de> (https://github.com/agnat)"
"David Siegel <david@artcom.de> (https://github.com/agnat)",
"Michael Ira Krufky <mkrufky@gmail.com> (https://github.com/mkrufky)"
],
"devDependencies": {
"bindings": "~1.2.1",
Expand Down
8 changes: 8 additions & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,12 @@
"target_name" : "private"
, "sources" : [ "cpp/private.cpp" ]
}
, {
"target_name" : "parse"
, "sources" : [ "cpp/json-parse.cpp" ]
}
, {
"target_name" : "stringify"
, "sources" : [ "cpp/json-stringify.cpp" ]
}
]}
34 changes: 34 additions & 0 deletions test/cpp/json-parse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*********************************************************************
* NAN - Native Abstractions for Node.js
*
* Copyright (c) 2017 NAN contributors
*
* MIT License <https://github.com/nodejs/nan/blob/master/LICENSE.md>
********************************************************************/

#include <nan.h>

NAN_METHOD(Parse) {
Nan::JSON NanJSON;

Nan::MaybeLocal<v8::String> inp = Nan::To<v8::String>(info[0]);

if (!inp.IsEmpty()) {
Nan::MaybeLocal<v8::Value> result = NanJSON.Parse(
inp.ToLocalChecked()
);

if (!result.IsEmpty()) {
info.GetReturnValue().Set(result.ToLocalChecked());
}
}
}

NAN_MODULE_INIT(Init) {
Nan::Set(target
, Nan::New<v8::String>("parse").ToLocalChecked()
, Nan::New<v8::FunctionTemplate>(Parse)->GetFunction()
);
}

NODE_MODULE(parse, Init)

0 comments on commit b533226

Please sign in to comment.