Skip to content

Commit

Permalink
json module faster implementation (#173)
Browse files Browse the repository at this point in the history
* json module faster implementation

* add some decoding error test
  • Loading branch information
d5 authored Apr 6, 2019
1 parent 17a50b7 commit 2cde0ea
Show file tree
Hide file tree
Showing 7 changed files with 1,324 additions and 51 deletions.
19 changes: 17 additions & 2 deletions docs/stdlib-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,20 @@ json := import("json")

## Functions

- `parse(v)`: Parses the JSON string and returns an object.
- `stringify(v)`: Returns the JSON string representation of the object.
- `decode(b string/bytes) => object`: Parses the JSON string and returns an object.
- `encode(o object) => bytes`: Returns the JSON string (bytes) of the object. Unlike Go's JSON package, this function does not HTML-escape texts, but, one can use `html_escape` function if needed.
- `indent(b string/bytes) => bytes`: Returns an indented form of input JSON bytes string.
- `html_escape(b string/bytes) => bytes`: Return an HTML-safe form of input JSON bytes string.


## Examples

```golang
json := import("json")

encoded := json.encode({a: 1, b: [2, 3, 4]}) // JSON-encoded bytes string
indentded := json.indent(encoded) // indented form
html_safe := json.html_escape(encoded) // HTML escaped form

decoded := json.decode(encoded) // {a: 1, b: [2, 3, 4]}
```
103 changes: 80 additions & 23 deletions stdlib/json.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,126 @@
package stdlib

import (
"encoding/json"
"bytes"
gojson "encoding/json"

"github.com/d5/tengo"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib/json"
)

var jsonModule = map[string]objects.Object{
"parse": &objects.UserFunction{Name: "parse", Value: jsonParse},
"stringify": &objects.UserFunction{Name: "stringify", Value: jsonStringify},
"decode": &objects.UserFunction{Name: "decode", Value: jsonDecode},
"encode": &objects.UserFunction{Name: "encode", Value: jsonEncode},
"indent": &objects.UserFunction{Name: "encode", Value: jsonIndent},
"html_escape": &objects.UserFunction{Name: "html_escape", Value: jsonHTMLEscape},
}

func jsonParse(args ...objects.Object) (ret objects.Object, err error) {
func jsonDecode(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}

var target interface{}

switch o := args[0].(type) {
case *objects.Bytes:
err := json.Unmarshal(o.Value, &target)
v, err := json.Decode(o.Value)
if err != nil {
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
}
return v, nil
case *objects.String:
err := json.Unmarshal([]byte(o.Value), &target)
v, err := json.Decode([]byte(o.Value))
if err != nil {
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
}
return v, nil
default:
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "bytes/string",
Found: args[0].TypeName(),
}
}
}

func jsonEncode(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}

res, err := objects.FromInterface(target)
b, err := json.Encode(args[0])
if err != nil {
return nil, err
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
}

return res, nil
return &objects.Bytes{Value: b}, nil
}

func jsonStringify(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
func jsonIndent(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 3 {
return nil, objects.ErrWrongNumArguments
}

v := objects.ToInterface(args[0])
if vErr, isErr := v.(error); isErr {
v = vErr.Error()
prefix, ok := objects.ToString(args[1])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "prefix",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
}

res, err := json.Marshal(v)
if err != nil {
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
indent, ok := objects.ToString(args[2])
if !ok {
return nil, objects.ErrInvalidArgumentType{
Name: "indent",
Expected: "string(compatible)",
Found: args[2].TypeName(),
}
}

switch o := args[0].(type) {
case *objects.Bytes:
var dst bytes.Buffer
err := gojson.Indent(&dst, o.Value, prefix, indent)
if err != nil {
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
}
return &objects.Bytes{Value: dst.Bytes()}, nil
case *objects.String:
var dst bytes.Buffer
err := gojson.Indent(&dst, []byte(o.Value), prefix, indent)
if err != nil {
return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil
}
return &objects.Bytes{Value: dst.Bytes()}, nil
default:
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "bytes/string",
Found: args[0].TypeName(),
}
}
}

if len(res) > tengo.MaxBytesLen {
return nil, objects.ErrBytesLimit
func jsonHTMLEscape(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}

return &objects.String{Value: string(res)}, nil
switch o := args[0].(type) {
case *objects.Bytes:
var dst bytes.Buffer
gojson.HTMLEscape(&dst, o.Value)
return &objects.Bytes{Value: dst.Bytes()}, nil
case *objects.String:
var dst bytes.Buffer
gojson.HTMLEscape(&dst, []byte(o.Value))
return &objects.Bytes{Value: dst.Bytes()}, nil
default:
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "bytes/string",
Found: args[0].TypeName(),
}
}
}
Loading

0 comments on commit 2cde0ea

Please sign in to comment.