Merge pull request #29 from mdbazuin/feature/encoding-json

Added builtin functions to_json and from_json
This commit is contained in:
Daniel Kang 2019-01-23 09:27:09 -08:00 committed by GitHub
commit 6a5e4f7d00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 173 additions and 59 deletions

48
objects/builtin_json.go Normal file
View file

@ -0,0 +1,48 @@
package objects
import (
"encoding/json"
)
func builtinToJSON(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
res, err := json.Marshal(objectToInterface(args[0]))
if err != nil {
return nil, err
}
return &Bytes{Value: res}, nil
}
func builtinFromJSON(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
var target interface{}
switch o := args[0].(type) {
case *Bytes:
err := json.Unmarshal(o.Value, &target)
if err != nil {
return nil, err
}
case *String:
err := json.Unmarshal([]byte(o.Value), &target)
if err != nil {
return nil, err
}
default:
return nil, ErrInvalidTypeConversion
}
res, err := FromInterface(target)
if err != nil {
return nil, err
}
return res, nil
}

View file

@ -80,4 +80,12 @@ var Builtins = []NamedBuiltinFunc{
Name: "is_undefined",
Func: builtinIsUndefined,
},
{
Name: "to_json",
Func: builtinToJSON,
},
{
Name: "from_json",
Func: builtinFromJSON,
},
}

View file

@ -1,6 +1,7 @@
package objects
import (
"fmt"
"strconv"
)
@ -138,3 +139,90 @@ func ToByteSlice(o Object) (v []byte, ok bool) {
//ok = false
return
}
// objectToInterface attempts to convert an object o to an interface{} value
func objectToInterface(o Object) (res interface{}) {
switch o := o.(type) {
case *Int:
res = o.Value
case *String:
res = o.Value
case *Float:
res = o.Value
case *Bool:
res = o.Value
case *Char:
res = o.Value
case *Bytes:
res = o.Value
case *Array:
res = make([]interface{}, len(o.Value))
for i, val := range o.Value {
res.([]interface{})[i] = objectToInterface(val)
}
case *Map:
res = make(map[string]interface{})
for key, v := range o.Value {
res.(map[string]interface{})[key] = objectToInterface(v)
}
case Object:
return o
}
return
}
// FromInterface will attempt to convert an interface{} v to a Tengo Object
func FromInterface(v interface{}) (Object, error) {
switch v := v.(type) {
case nil:
return &Undefined{}, nil
case string:
return &String{Value: v}, nil
case int64:
return &Int{Value: v}, nil
case int:
return &Int{Value: int64(v)}, nil
case bool:
return &Bool{Value: v}, nil
case rune:
return &Char{Value: v}, nil
case byte:
return &Char{Value: rune(v)}, nil
case float64:
return &Float{Value: v}, nil
case []byte:
return &Bytes{Value: v}, nil
case error:
return &Error{Value: &String{Value: v.Error()}}, nil
case map[string]Object:
return &Map{Value: v}, nil
case map[string]interface{}:
kv := make(map[string]Object)
for vk, vv := range v {
vo, err := FromInterface(vv)
if err != nil {
return nil, err
}
kv[vk] = vo
}
return &Map{Value: kv}, nil
case []Object:
return &Array{Value: v}, nil
case []interface{}:
arr := make([]Object, len(v), len(v))
for i, e := range v {
vo, err := FromInterface(e)
if err != nil {
return nil, err
}
arr[i] = vo
}
return &Array{Value: arr}, nil
case Object:
return v, nil
}
return nil, fmt.Errorf("unsupported value type: %T", v)
}

View file

@ -113,4 +113,31 @@ func TestBuiltinFunction(t *testing.T) {
expect(t, `out = is_undefined(undefined)`, true)
expect(t, `out = is_undefined(error(1))`, false)
// to_json
expect(t, `out = to_json(5)`, []byte("5"))
expect(t, `out = to_json({foo: 5})`, []byte("{\"foo\":5}"))
expect(t, `out = to_json({foo: "bar"})`, []byte("{\"foo\":\"bar\"}"))
expect(t, `out = to_json({foo: 1.8})`, []byte("{\"foo\":1.8}"))
expect(t, `out = to_json({foo: true})`, []byte("{\"foo\":true}"))
expect(t, `out = to_json({foo: '8'})`, []byte("{\"foo\":56}"))
expect(t, `out = to_json({foo: bytes("foo")})`, []byte("{\"foo\":\"Zm9v\"}")) // json encoding returns []byte as base64 encoded string
expect(t, `out = to_json({foo: ["bar", 1, 1.8, '8', true]})`, []byte("{\"foo\":[\"bar\",1,1.8,56,true]}"))
expect(t, `out = to_json({foo: [["bar", 1], ["bar", 1]]})`, []byte("{\"foo\":[[\"bar\",1],[\"bar\",1]]}"))
expect(t, `out = to_json({foo: {string: "bar", int: 1, float: 1.8, char: '8', bool: true}})`, []byte("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}"))
expect(t, `out = to_json({foo: {map1: {string: "bar"}, map2: {int: "1"}}})`, []byte("{\"foo\":{\"map1\":{\"string\":\"bar\"},\"map2\":{\"int\":\"1\"}}}"))
expect(t, `out = to_json([["bar", 1], ["bar", 1]])`, []byte("[[\"bar\",1],[\"bar\",1]]"))
// from_json
expect(t, `out = from_json("{\"foo\":5}").foo`, 5.0)
expect(t, `out = from_json("{\"foo\":\"bar\"}").foo`, "bar")
expect(t, `out = from_json("{\"foo\":1.8}").foo`, 1.8)
expect(t, `out = from_json("{\"foo\":true}").foo`, true)
expect(t, `out = from_json("{\"foo\":[\"bar\",1,1.8,56,true]}").foo`, ARR{"bar", 1.0, 1.8, 56.0, true})
expect(t, `out = from_json("{\"foo\":[[\"bar\",1],[\"bar\",1]]}").foo[0]`, ARR{"bar", 1.0})
expect(t, `out = from_json("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}").foo.bool`, true)
expect(t, `out = from_json("{\"foo\":{\"map1\":{\"string\":\"bar\"},\"map2\":{\"int\":\"1\"}}}").foo.map1.string`, "bar")
expect(t, `out = from_json("5")`, 5.0)
expect(t, `out = from_json("[\"bar\",1,1.8,56,true]")`, ARR{"bar", 1.0, 1.8, 56.0, true})
}

View file

@ -1,8 +1,6 @@
package script
import (
"fmt"
"github.com/d5/tengo/objects"
)
@ -30,58 +28,3 @@ func objectToInterface(o objects.Object) interface{} {
return o
}
func interfaceToObject(v interface{}) (objects.Object, error) {
switch v := v.(type) {
case nil:
return undefined, nil
case string:
return &objects.String{Value: v}, nil
case int64:
return &objects.Int{Value: v}, nil
case int:
return &objects.Int{Value: int64(v)}, nil
case bool:
return &objects.Bool{Value: v}, nil
case rune:
return &objects.Char{Value: v}, nil
case byte:
return &objects.Char{Value: rune(v)}, nil
case float64:
return &objects.Float{Value: v}, nil
case []byte:
return &objects.Bytes{Value: v}, nil
case error:
return &objects.Error{Value: &objects.String{Value: v.Error()}}, nil
case map[string]objects.Object:
return &objects.Map{Value: v}, nil
case map[string]interface{}:
kv := make(map[string]objects.Object)
for vk, vv := range v {
vo, err := interfaceToObject(vv)
if err != nil {
return nil, err
}
kv[vk] = vo
}
return &objects.Map{Value: kv}, nil
case []objects.Object:
return &objects.Array{Value: v}, nil
case []interface{}:
arr := make([]objects.Object, len(v), len(v))
for _, e := range v {
vo, err := interfaceToObject(e)
if err != nil {
return nil, err
}
arr = append(arr, vo)
}
return &objects.Array{Value: arr}, nil
case objects.Object:
return v, nil
}
return nil, fmt.Errorf("unsupported value type: %T", v)
}

View file

@ -27,7 +27,7 @@ func New(input []byte) *Script {
// Add adds a new variable or updates an existing variable to the script.
func (s *Script) Add(name string, value interface{}) error {
obj, err := interfaceToObject(value)
obj, err := objects.FromInterface(value)
if err != nil {
return err
}

View file

@ -14,7 +14,7 @@ type Variable struct {
// NewVariable creates a Variable.
func NewVariable(name string, value interface{}) (*Variable, error) {
obj, err := interfaceToObject(value)
obj, err := objects.FromInterface(value)
if err != nil {
return nil, err
}