diff --git a/syntax/std.go b/syntax/std.go index 84792284..cb95dc87 100644 --- a/syntax/std.go +++ b/syntax/std.go @@ -88,6 +88,7 @@ func stdScope() rel.Scope { stdStr(), stdEval(), stdOs(), + stdJSON(), )) }) return stdScopeVar diff --git a/syntax/std_json.go b/syntax/std_json.go new file mode 100644 index 00000000..638787f0 --- /dev/null +++ b/syntax/std_json.go @@ -0,0 +1,26 @@ +package syntax + +import ( + "encoding/json" + + "github.com/arr-ai/arrai/rel" + "github.com/arr-ai/arrai/translate" +) + +func stdJSON() rel.Attr { + return rel.NewTupleAttr( + "json", + rel.NewNativeFunctionAttr("decode", func(v rel.Value) rel.Value { + s := mustAsString(v) + var data interface{} + var err error + if err = json.Unmarshal([]byte(s), &data); err == nil { + var d rel.Value + if d, err = translate.JSONToArrai(data); err == nil { + return d + } + } + panic(err) + }), + ) +} diff --git a/syntax/std_json_test.go b/syntax/std_json_test.go new file mode 100644 index 00000000..e84e4833 --- /dev/null +++ b/syntax/std_json_test.go @@ -0,0 +1,42 @@ +package syntax + +import "testing" + +func TestJSONDecode(t *testing.T) { + t.Parallel() + AssertCodePanics(t, `//.json.decode(123)`) + AssertCodesEvalToSameValue(t, + `{ + "a": (s: "string"), + "b": 123, + "c": 123.321, + "d": (a: [1, (s: "string again"), (a: []), {}]), + "e": { + "f": { + "g": (s: "321") + }, + "h": (a: []) + }, + "i": (null: {}), + "j": (a: [(b: {()}), (b: {})]), + "k": (s: {}) + }`, + `//.json.decode( + '{ + "a": "string", + "b": 123, + "c": 123.321, + "d": [1, "string again", [], {}], + "e": { + "f": { + "g": "321" + }, + "h": [] + }, + "i": null, + "j": [true, false], + "k": "" + }' + )`, + ) +} diff --git a/syntax/std_str.go b/syntax/std_str.go index cf7e57e6..a86cf6e5 100644 --- a/syntax/std_str.go +++ b/syntax/std_str.go @@ -132,5 +132,5 @@ func mustAsString(v rel.Value) string { if s, ok := rel.AsString(v.(rel.Set)); ok { return s.String() } - panic("can not be a string") + panic("value is not a string") } diff --git a/translate/json.go b/translate/json.go index f12221c1..3716a39c 100644 --- a/translate/json.go +++ b/translate/json.go @@ -16,12 +16,20 @@ func JSONToArrai(data interface{}) (rel.Value, error) { return jsonObjToArrai(v) case []interface{}: return jsonArrToArrai(v) - case string: // rel.NewValue cannot produce strings - return rel.NewString([]rune(v)), nil + case string: + return rel.NewTuple(rel.NewAttr("s", rel.NewString([]rune(v)))), nil + case float64: + return rel.NewNumber(v), nil + case bool: + return rel.NewTuple(rel.NewAttr("b", rel.NewBool(v))), nil case nil: - return rel.None, nil + return rel.NewTuple(rel.NewAttr("null", rel.None)), nil default: - return rel.NewValue(v) + t, err := rel.NewValue(v) + if err != nil { + return nil, err + } + return rel.NewTuple(rel.NewAttr("v", t)), nil } } @@ -52,5 +60,5 @@ func jsonArrToArrai(data []interface{}) (rel.Value, error) { } elts[i] = elt } - return rel.NewArray(elts...), nil + return rel.NewTuple(rel.NewAttr("a", rel.NewArray(elts...))), nil } diff --git a/translate/json_test.go b/translate/json_test.go index 999df69e..227686d5 100644 --- a/translate/json_test.go +++ b/translate/json_test.go @@ -42,46 +42,46 @@ func TestJSONObjectToArrai(t *testing.T) { AssertExpectedJSONTranslation(t, `{}`, `{}`) // different value types - AssertExpectedJSONTranslation(t, `{"key": "val"}`, `{"key":"val"}`) - AssertExpectedJSONTranslation(t, `{"key": 123}`, `{"key":123}`) - AssertExpectedJSONTranslation(t, `{"key": {"foo": "bar"}}`, `{"key":{"foo":"bar"}}`) - AssertExpectedJSONTranslation(t, `{"key": [1, 2, 3]}`, `{"key":[1, 2, 3]}`) - AssertExpectedJSONTranslation(t, `{"key": {}}`, `{"key":null}`) + AssertExpectedJSONTranslation(t, `{"key": 123} `, `{"key":123} `) + AssertExpectedJSONTranslation(t, `{"key": (null: {})} `, `{"key":null} `) + AssertExpectedJSONTranslation(t, `{"key": (s: "val")} `, `{"key":"val"} `) + AssertExpectedJSONTranslation(t, `{"key": (a: [1, 2, 3])}`, `{"key":[1, 2, 3]} `) + AssertExpectedJSONTranslation(t, `{"key": {"foo": (s: "bar")}}`, `{"key":{"foo":"bar"}}`) // Multiple key-val pairs - AssertExpectedJSONTranslation(t, `{"key": "val", "foo": 123}`, `{"key":"val", "foo":123}`) + AssertExpectedJSONTranslation(t, `{"key": (s: "val"), "foo": 123}`, `{"key":"val", "foo":123}`) } func TestJSONArrayToArrai(t *testing.T) { t.Parallel() // Empty - AssertExpectedJSONTranslation(t, `[]`, `[]`) + AssertExpectedJSONTranslation(t, `(a: [])`, `[]`) // Different value types - AssertExpectedJSONTranslation(t, `[1]`, `[1]`) - AssertExpectedJSONTranslation(t, `["hello"]`, `["hello"]`) - AssertExpectedJSONTranslation(t, `[{"foo": "bar"}]`, `[{"foo":"bar"}]`) - AssertExpectedJSONTranslation(t, `[[1, 2, 3]]`, `[[1, 2, 3]]`) - AssertExpectedJSONTranslation(t, `[{}]`, `[null]`) + AssertExpectedJSONTranslation(t, `(a: [1]) `, `[1] `) + AssertExpectedJSONTranslation(t, `(a: [(null: {})]) `, `[null] `) + AssertExpectedJSONTranslation(t, `(a: [(s: "hello")]) `, `["hello"] `) + AssertExpectedJSONTranslation(t, `(a: [(a: [1, 2, 3])]) `, `[[1, 2, 3]] `) + AssertExpectedJSONTranslation(t, `(a: [{"foo": (s: "bar")}])`, `[{"foo":"bar"}]`) // Multiple values with different types - AssertExpectedJSONTranslation(t, `[1, "Hello", {}]`, `[1, "Hello", null]`) + AssertExpectedJSONTranslation(t, `(a: [1, (s: "Hello"), (null: {})])`, `[1, "Hello", null]`) } func TestJSONNullToNone(t *testing.T) { t.Parallel() - AssertExpectedJSONTranslation(t, `{}`, `null`) + AssertExpectedJSONTranslation(t, `(null: {})`, `null`) } func TestJSONStringToArrai(t *testing.T) { t.Parallel() - AssertExpectedJSONTranslation(t, `""`, `""`) - AssertExpectedJSONTranslation(t, `"Hello World"`, `"Hello World"`) + AssertExpectedJSONTranslation(t, `(s: {}) `, `"" `) + AssertExpectedJSONTranslation(t, `(s: "Hello World")`, `"Hello World"`) } func TestJSONNumericToArrai(t *testing.T) { t.Parallel() - AssertExpectedJSONTranslation(t, `123`, `123`) + AssertExpectedJSONTranslation(t, `123 `, `123 `) AssertExpectedJSONTranslation(t, `1.23`, `1.23`) }