From d8c83b65789e8074e1bd1b3ee77c0400b1abc602 Mon Sep 17 00:00:00 2001 From: Mike Bazuin Date: Mon, 21 Jan 2019 17:04:27 +0100 Subject: [PATCH 1/4] Moved interfaceToObject from scripts/conversion.go to objects/conversion.go as FromInterface --- objects/conversion.go | 56 ++++++++++++++++++++++++++++++++++++++++++ script/conversion.go | 57 ------------------------------------------- script/script.go | 2 +- script/variable.go | 2 +- 4 files changed, 58 insertions(+), 59 deletions(-) diff --git a/objects/conversion.go b/objects/conversion.go index 99996bb..90dcf52 100644 --- a/objects/conversion.go +++ b/objects/conversion.go @@ -1,6 +1,7 @@ package objects import ( + "fmt" "strconv" ) @@ -138,3 +139,58 @@ func ToByteSlice(o Object) (v []byte, ok bool) { //ok = false 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) +} diff --git a/script/conversion.go b/script/conversion.go index d1617ff..440ef9a 100644 --- a/script/conversion.go +++ b/script/conversion.go @@ -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) -} diff --git a/script/script.go b/script/script.go index 9f36530..5dab3c4 100644 --- a/script/script.go +++ b/script/script.go @@ -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 } diff --git a/script/variable.go b/script/variable.go index bec167a..13aa210 100644 --- a/script/variable.go +++ b/script/variable.go @@ -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 } From 65f84ec2d1a984397ade4e48f2d40764d1dd3374 Mon Sep 17 00:00:00 2001 From: Mike Bazuin Date: Mon, 21 Jan 2019 11:16:40 +0100 Subject: [PATCH 2/4] Added conversion functions: objectToInterface objects/conversion.go: - Added private helper function --- objects/conversion.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/objects/conversion.go b/objects/conversion.go index 90dcf52..6b762a3 100644 --- a/objects/conversion.go +++ b/objects/conversion.go @@ -140,6 +140,38 @@ func ToByteSlice(o Object) (v []byte, ok bool) { 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) { From c4e6e61fb651eb45fed0c1a90e036cedd3205786 Mon Sep 17 00:00:00 2001 From: Mike Bazuin Date: Mon, 21 Jan 2019 11:18:53 +0100 Subject: [PATCH 3/4] Added builtin function to_json objects/builtin_json.go: - Added function builtinToJSON objects/builtins.go: - Added builtin function to_json runtime/vm_builtin_test.go - Added tests for builtin function to_json --- objects/builtin_json.go | 18 ++++++++++++++++++ objects/builtins.go | 4 ++++ runtime/vm_builtin_test.go | 15 +++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 objects/builtin_json.go diff --git a/objects/builtin_json.go b/objects/builtin_json.go new file mode 100644 index 0000000..38e76cd --- /dev/null +++ b/objects/builtin_json.go @@ -0,0 +1,18 @@ +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 +} diff --git a/objects/builtins.go b/objects/builtins.go index fe7085e..6e9f898 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -80,4 +80,8 @@ var Builtins = []NamedBuiltinFunc{ Name: "is_undefined", Func: builtinIsUndefined, }, + { + Name: "to_json", + Func: builtinToJSON, + }, } diff --git a/runtime/vm_builtin_test.go b/runtime/vm_builtin_test.go index 587269e..73d164a 100644 --- a/runtime/vm_builtin_test.go +++ b/runtime/vm_builtin_test.go @@ -113,4 +113,19 @@ 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 e94b3dab0f310887a4b32e67862f4fb4402af553 Mon Sep 17 00:00:00 2001 From: Mike Bazuin Date: Mon, 21 Jan 2019 11:24:31 +0100 Subject: [PATCH 4/4] Added builtin function from_json objects/builtin_json.go: - Added function builtinFromJSON objects/builtins.go: - Added builtin function from_json runtim/vm_builtin_test.go: - Added tests for builtin function from_json --- objects/builtin_json.go | 30 ++++++++++++++++++++++++++++++ objects/builtins.go | 4 ++++ runtime/vm_builtin_test.go | 12 ++++++++++++ 3 files changed, 46 insertions(+) diff --git a/objects/builtin_json.go b/objects/builtin_json.go index 38e76cd..60bc548 100644 --- a/objects/builtin_json.go +++ b/objects/builtin_json.go @@ -16,3 +16,33 @@ func builtinToJSON(args ...Object) (Object, error) { 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 +} diff --git a/objects/builtins.go b/objects/builtins.go index 6e9f898..a55d41d 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -84,4 +84,8 @@ var Builtins = []NamedBuiltinFunc{ Name: "to_json", Func: builtinToJSON, }, + { + Name: "from_json", + Func: builtinFromJSON, + }, } diff --git a/runtime/vm_builtin_test.go b/runtime/vm_builtin_test.go index 73d164a..f6481a3 100644 --- a/runtime/vm_builtin_test.go +++ b/runtime/vm_builtin_test.go @@ -128,4 +128,16 @@ func TestBuiltinFunction(t *testing.T) { 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}) }