Skip to content

Commit 072331f

Browse files
authored
JSON parse and stringify API (#70)
* JSON parse and stringify API * move to functions rather than ctx methods to align closer to C++ API * value implement json.Marshaler interface * test parse error cases * can;t use errors.As in Go 12.x
1 parent 9005132 commit 072331f

File tree

7 files changed

+225
-10
lines changed

7 files changed

+225
-10
lines changed

json.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package v8go
2+
3+
// #include <stdlib.h>
4+
// #include "v8go.h"
5+
import "C"
6+
import (
7+
"errors"
8+
"unsafe"
9+
)
10+
11+
// JSONParse tries to parse the string and returns it as *Value if successful.
12+
// Any JS errors will be returned as `JSError`.
13+
func JSONParse(ctx *Context, str string) (*Value, error) {
14+
if ctx == nil {
15+
return nil, errors.New("v8go: Context is required")
16+
}
17+
cstr := C.CString(str)
18+
defer C.free(unsafe.Pointer(cstr))
19+
20+
rtn := C.JSONParse(ctx.ptr, cstr)
21+
return getValue(ctx, rtn), getError(rtn)
22+
}
23+
24+
// JSONStringify tries to stringify the JSON-serializable object value and returns it as string.
25+
func JSONStringify(ctx *Context, val *Value) (string, error) {
26+
if val == nil {
27+
return "", errors.New("v8go: Value is required")
28+
}
29+
// If a nil context is passed we'll use the context/isolate that created the value.
30+
var ctxPtr C.ContextPtr
31+
if ctx != nil {
32+
ctxPtr = ctx.ptr
33+
}
34+
35+
str := C.JSONStringify(ctxPtr, val.ptr)
36+
defer C.free(unsafe.Pointer(str))
37+
return C.GoString(str), nil
38+
}

json_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package v8go_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"rogchap.com/v8go"
8+
)
9+
10+
func TestJSONParse(t *testing.T) {
11+
t.Parallel()
12+
13+
if _, err := v8go.JSONParse(nil, "{}"); err == nil {
14+
t.Error("expected error but got <nil>")
15+
}
16+
ctx, _ := v8go.NewContext()
17+
_, err := v8go.JSONParse(ctx, "{")
18+
if err == nil {
19+
t.Error("expected error but got <nil>")
20+
return
21+
}
22+
23+
if _, ok := err.(*v8go.JSError); !ok {
24+
t.Errorf("expected error to be of type JSError, got: %T", err)
25+
}
26+
27+
}
28+
29+
func ExampleJSONParse() {
30+
ctx, _ := v8go.NewContext()
31+
val, _ := v8go.JSONParse(ctx, `{"foo": "bar"}`)
32+
fmt.Println(val)
33+
// Output:
34+
// [object Object]
35+
}
36+
37+
func ExampleJSONStringify() {
38+
ctx, _ := v8go.NewContext()
39+
val, _ := v8go.JSONParse(ctx, `{
40+
"a": 1,
41+
"b": "foo"
42+
}`)
43+
jsonStr, _ := v8go.JSONStringify(ctx, val)
44+
fmt.Println(jsonStr)
45+
// Output:
46+
// {"a":1,"b":"foo"}
47+
}

object_template_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func TestObjectTemplate(t *testing.T) {
2828
val, _ := v8go.NewValue(iso, "bar")
2929
objVal, _ := v8go.NewObjectTemplate(iso)
3030
bigbigint, _ := new(big.Int).SetString("36893488147419099136", 10) // larger than a single word size (64bit)
31+
bigbignegint, _ := new(big.Int).SetString("-36893488147419099136", 10)
3132

3233
tests := [...]struct {
3334
name string
@@ -40,7 +41,9 @@ func TestObjectTemplate(t *testing.T) {
4041
{"u64", uint64(1)},
4142
{"float64", float64(1)},
4243
{"bigint", big.NewInt(1)},
44+
{"biguint", new(big.Int).SetUint64(1 << 63)},
4345
{"bigbigint", bigbigint},
46+
{"bigbignegint", bigbignegint},
4447
{"bool", true},
4548
{"val", val},
4649
{"obj", objVal},

v8go.cc

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ TemplatePtr NewFunctionTemplate(IsolatePtr iso_ptr, int callback_ref) {
273273

274274
/********** Context **********/
275275

276+
#define LOCAL_CONTEXT(ctx_ptr) \
277+
m_ctx* ctx = static_cast<m_ctx*>(ctx_ptr); \
278+
Isolate* iso = ctx->iso; \
279+
Locker locker(iso); \
280+
Isolate::Scope isolate_scope(iso); \
281+
HandleScope handle_scope(iso); \
282+
TryCatch try_catch(iso); \
283+
Local<Context> local_ctx = ctx->ptr.Get(iso); \
284+
Context::Scope context_scope(local_ctx);
285+
276286
ContextPtr NewContext(IsolatePtr iso_ptr,
277287
TemplatePtr global_template_ptr,
278288
int ref) {
@@ -316,15 +326,7 @@ void ContextFree(ContextPtr ptr) {
316326
}
317327

318328
RtnValue RunScript(ContextPtr ctx_ptr, const char* source, const char* origin) {
319-
m_ctx* ctx = static_cast<m_ctx*>(ctx_ptr);
320-
Isolate* iso = ctx->iso;
321-
Locker locker(iso);
322-
Isolate::Scope isolate_scope(iso);
323-
HandleScope handle_scope(iso);
324-
TryCatch try_catch(iso);
325-
326-
Local<Context> local_ctx = ctx->ptr.Get(iso);
327-
Context::Scope context_scope(local_ctx);
329+
LOCAL_CONTEXT(ctx_ptr);
328330

329331
Local<String> src =
330332
String::NewFromUtf8(iso, source, NewStringType::kNormal).ToLocalChecked();
@@ -339,7 +341,27 @@ RtnValue RunScript(ContextPtr ctx_ptr, const char* source, const char* origin) {
339341
rtn.error = ExceptionError(try_catch, iso, local_ctx);
340342
return rtn;
341343
}
342-
MaybeLocal<v8::Value> result = script.ToLocalChecked()->Run(local_ctx);
344+
MaybeLocal<Value> result = script.ToLocalChecked()->Run(local_ctx);
345+
if (result.IsEmpty()) {
346+
rtn.error = ExceptionError(try_catch, iso, local_ctx);
347+
return rtn;
348+
}
349+
m_value* val = new m_value;
350+
val->iso = iso;
351+
val->ctx.Reset(iso, local_ctx);
352+
val->ptr.Reset(iso, Persistent<Value>(iso, result.ToLocalChecked()));
353+
354+
rtn.value = static_cast<ValuePtr>(val);
355+
return rtn;
356+
}
357+
358+
RtnValue JSONParse(ContextPtr ctx_ptr, const char* str) {
359+
LOCAL_CONTEXT(ctx_ptr);
360+
RtnValue rtn = {nullptr, nullptr};
361+
362+
MaybeLocal<Value> result = JSON::Parse(
363+
local_ctx,
364+
String::NewFromUtf8(iso, str, NewStringType::kNormal).ToLocalChecked());
343365
if (result.IsEmpty()) {
344366
rtn.error = ExceptionError(try_catch, iso, local_ctx);
345367
return rtn;
@@ -353,6 +375,43 @@ RtnValue RunScript(ContextPtr ctx_ptr, const char* source, const char* origin) {
353375
return rtn;
354376
}
355377

378+
const char* JSONStringify(ContextPtr ctx_ptr, ValuePtr val_ptr) {
379+
Isolate* iso;
380+
Local<Context> local_ctx;
381+
382+
m_value* val = static_cast<m_value*>(val_ptr);
383+
m_ctx* ctx = static_cast<m_ctx*>(ctx_ptr);
384+
385+
if (ctx != nullptr) {
386+
iso = ctx->iso;
387+
} else {
388+
iso = val->iso;
389+
}
390+
391+
Locker locker(iso);
392+
Isolate::Scope isolate_scope(iso);
393+
HandleScope handle_scope(iso);
394+
395+
if (ctx != nullptr) {
396+
local_ctx = ctx->ptr.Get(iso);
397+
} else {
398+
local_ctx = val->ctx.Get(iso);
399+
if (local_ctx.IsEmpty()) {
400+
m_ctx* ctx = static_cast<m_ctx*>(iso->GetData(0));
401+
local_ctx = ctx->ptr.Get(iso);
402+
}
403+
}
404+
405+
Context::Scope context_scope(local_ctx);
406+
407+
MaybeLocal<String> str = JSON::Stringify(local_ctx, val->ptr.Get(iso));
408+
if (str.IsEmpty()) {
409+
return nullptr;
410+
}
411+
String::Utf8Value json(iso, str.ToLocalChecked());
412+
return CopyString(json);
413+
}
414+
356415
/********** Value **********/
357416

358417
#define LOCAL_VALUE(ptr) \

v8go.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ extern void ContextFree(ContextPtr ptr);
5656
extern RtnValue RunScript(ContextPtr ctx_ptr,
5757
const char* source,
5858
const char* origin);
59+
extern RtnValue JSONParse(ContextPtr ctx_ptr, const char* str);
60+
const char* JSONStringify(ContextPtr ctx_ptr, ValuePtr val_ptr);
5961

6062
extern void TemplateFree(TemplatePtr ptr);
6163
extern void TemplateSetValue(TemplatePtr ptr,

value.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,3 +502,12 @@ func (v *Value) finalizer() {
502502
v.ptr = nil
503503
runtime.SetFinalizer(v, nil)
504504
}
505+
506+
// MarshalJSON implements the json.Marshaler interface.
507+
func (v *Value) MarshalJSON() ([]byte, error) {
508+
jsonStr, err := JSONStringify(nil, v)
509+
if err != nil {
510+
return nil, err
511+
}
512+
return []byte(jsonStr), nil
513+
}

value_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package v8go_test
22

33
import (
4+
"bytes"
45
"fmt"
56
"math"
67
"math/big"
@@ -462,3 +463,59 @@ func TestValueIsXXX(t *testing.T) {
462463
})
463464
}
464465
}
466+
467+
func TestValueMarshalJSON(t *testing.T) {
468+
t.Parallel()
469+
iso, _ := v8go.NewIsolate()
470+
471+
tests := [...]struct {
472+
name string
473+
val func() *v8go.Value
474+
expected []byte
475+
}{
476+
{
477+
"primitive",
478+
func() *v8go.Value {
479+
val, _ := v8go.NewValue(iso, int32(0))
480+
return val
481+
},
482+
[]byte("0"),
483+
},
484+
{
485+
"object",
486+
func() *v8go.Value {
487+
ctx, _ := v8go.NewContext(iso)
488+
val, _ := ctx.RunScript("let foo = {a:1, b:2}; foo", "test.js")
489+
return val
490+
},
491+
[]byte(`{"a":1,"b":2}`),
492+
},
493+
{
494+
"objectFunc",
495+
func() *v8go.Value {
496+
ctx, _ := v8go.NewContext(iso)
497+
val, _ := ctx.RunScript("let foo = {a:1, b:()=>{}}; foo", "test.js")
498+
return val
499+
},
500+
[]byte(`{"a":1}`),
501+
},
502+
{
503+
"nil",
504+
func() *v8go.Value { return nil },
505+
[]byte(""),
506+
},
507+
}
508+
509+
for _, tt := range tests {
510+
tt := tt
511+
t.Run(tt.name, func(t *testing.T) {
512+
t.Parallel()
513+
val := tt.val()
514+
json, _ := val.MarshalJSON()
515+
if !bytes.Equal(json, tt.expected) {
516+
t.Errorf("unexpected JSON value: %s", string(json))
517+
}
518+
519+
})
520+
}
521+
}

0 commit comments

Comments
 (0)