Repository: bytedance/sonic
Pull request #249 (2022-06-27 10:19:45)

feat:(ast) Add more strict casting API 


Patch:
diff --git a/ast/node.go b/ast/node.go
index c4a87ad..f08a9c4 100644
--- a/ast/node.go
+++ b/ast/node.go
@@ -165,7 +165,7 @@ func (self *Node) checkRaw() error {
     return nil
 }
 
-// Bool_E returns bool value represented by this node
+// Bool returns bool value represented by this node
 //
 // If node type is not types.V_TRUE or types.V_FALSE, 
 // V_RAW (must be a bool json value), or V_ANY (must be a bool type)
@@ -187,7 +187,7 @@ func (self *Node) Bool() (bool, error) {
     }
 }
 
-// Int64 as above.
+// Int64 casts the node to int64 value, including V_NUMBER, V_TRUE, V_FALSE, V_ANY
 func (self *Node) Int64() (int64, error) {
     if err := self.checkRaw(); err != nil {
         return 0, err
@@ -215,7 +215,33 @@ func (self *Node) Int64() (int64, error) {
     }
 }
 
-// Number as above.
+// StrictInt64 exports underlying int64 value, including V_NUMBER, V_ANY
+func (self *Node) StrictInt64() (int64, error) {
+    if err := self.checkRaw(); err != nil {
+        return 0, err
+    }
+    switch self.t {
+        case _V_NUMBER        : return numberToInt64(self)
+        case _V_ANY           :  
+            any := self.packAny()
+            switch v := any.(type) {
+                case int   : return int64(v), nil
+                case int8  : return int64(v), nil
+                case int16 : return int64(v), nil
+                case int32 : return int64(v), nil
+                case int64 : return int64(v), nil
+                case uint  : return int64(v), nil
+                case uint8 : return int64(v), nil
+                case uint16: return int64(v), nil
+                case uint32: return int64(v), nil
+                case uint64: return int64(v), nil
+                default: return 0, ErrUnsupportType
+            }
+        default               : return 0, ErrUnsupportType
+    }
+}
+
+// Number casts node to float64, including V_NUMBER, V_TRUE, V_FALSE, V_ANY of json.Number
 func (self *Node) Number() (json.Number, error) {
     if err := self.checkRaw(); err != nil {
         return json.Number(""), err
@@ -234,6 +260,23 @@ func (self *Node) Number() (json.Number, error) {
     }
 }
 
+// Number exports underlying float64 value, including V_NUMBER, V_ANY of json.Number
+func (self *Node) StrictNumber() (json.Number, error) {
+    if err := self.checkRaw(); err != nil {
+        return json.Number(""), err
+    }
+    switch self.t {
+        case _V_NUMBER        : return toNumber(self)  , nil
+        case _V_ANY        :        
+            if v, ok := self.packAny().(json.Number); ok {
+                return v, nil
+            } else {
+                return json.Number(""), ErrUnsupportType
+            }
+        default               : return json.Number(""), ErrUnsupportType
+    }
+}
+
 // String returns raw string value if node type is V_STRING.
 // Or return the string representation of other types:
 //  V_NULL => "null",
@@ -261,7 +304,26 @@ func (self *Node) String() (string, error) {
     }
 }
 
-// Float64 as above.
+// StrictString returns string value (unescaped), includeing V_STRING, V_ANY of string.
+// In other cases, it will return empty string.
+func (self *Node) StrictString() (string, error) {
+    if err := self.checkRaw(); err != nil {
+        return "", err
+    }
+    switch self.t {
+        case types.V_NULL    : return "", nil
+        case types.V_STRING  : return addr2str(self.p, self.v), nil
+        case _V_ANY          :        
+            if v, ok := self.packAny().(string); ok {
+                return v, nil
+            } else {
+                return "", ErrUnsupportType
+            }
+        default              : return "", ErrUnsupportType
+    }
+}
+
+// Float64 casts node to float64, includeing V_NUMBER, V_TRUE, V_FALSE, V_ANY
 func (self *Node) Float64() (float64, error) {
     if err := self.checkRaw(); err != nil {
         return 0.0, err
@@ -281,6 +343,24 @@ func (self *Node) Float64() (float64, error) {
     }
 }
 
+// Float64 exports underlying float64 value, includeing V_NUMBER, V_ANY 
+func (self *Node) StrictFloat64() (float64, error) {
+    if err := self.checkRaw(); err != nil {
+        return 0.0, err
+    }
+    switch self.t {
+        case _V_NUMBER       : return numberToFloat64(self)
+        case _V_ANY        :        
+            any := self.packAny()
+            switch v := any.(type) {
+                case float32 : return float64(v), nil
+                case float64 : return float64(v), nil
+                default      : return 0, ErrUnsupportType
+            }
+        default              : return 0.0, ErrUnsupportType
+    }
+}
+
 /** Sequencial Value Methods **/
 
 // Len returns children count of a array|object|string node
@@ -1410,7 +1490,10 @@ func newBytes(v []byte) Node {
     }
 }
 
-// NewString creates a node of type string
+// NewString creates a node of type V_STRING. 
+// v is considered to be a valid UTF-8 string,
+// which means it won't be validated and unescaped.
+// when the node is encoded to json, v will be escaped.
 func NewString(v string) Node {
     return Node{
         t: types.V_STRING,


Test patch:
diff --git a/ast/encode_test.go b/ast/encode_test.go
index 69051fe..c451834 100644
--- a/ast/encode_test.go
+++ b/ast/encode_test.go
@@ -85,7 +85,7 @@ func TestEncodeValue(t *testing.T) {
         {NewBool(false), "false", false},
         {NewNumber("0.0"), "0.0", false},
         {NewString(""), `""`, false},
-        {NewString("\"\""), `"\"\""`, false},
+        {NewString(`\"\"`), `"\\\"\\\""`, false},
         {NewString(_TwitterJson), string(quote), false},
         {NewArray([]Node{}), "[]", false},
         {NewArray([]Node{NewBool(true), NewString("true"), NewString("\t")}), `[true,"true","\t"]`, false},
diff --git a/ast/node_test.go b/ast/node_test.go
index 1e8ad32..7446dad 100644
--- a/ast/node_test.go
+++ b/ast/node_test.go
@@ -280,6 +280,8 @@ func TestTypeCast(t *testing.T) {
         {"Bool", NewAny(false), false, nil},
         {"Bool", NewRaw("true"), true, nil},
         {"Bool", NewRaw("false"), false, nil},
+        {"Int64", NewRaw("true"), int64(1), nil},
+        {"Int64", NewRaw("false"), int64(0), nil},
         {"Int64", NewAny(int(0)), int64(0), nil},
         {"Int64", NewAny(int8(0)), int64(0), nil},
         {"Int64", NewAny(int16(0)), int64(0), nil},
@@ -291,22 +293,57 @@ func TestTypeCast(t *testing.T) {
         {"Int64", NewAny(uint64(0)), int64(0), nil},
         {"Int64", Node{}, int64(0), ErrUnsupportType},
         {"Int64", NewRaw("0"), int64(0), nil},
+        {"StrictInt64", NewRaw("true"), int64(0), ErrUnsupportType},
+        {"StrictInt64", NewRaw("false"), int64(0), ErrUnsupportType},
+        {"StrictInt64", NewAny(int(0)), int64(0), nil},
+        {"StrictInt64", NewAny(int8(0)), int64(0), nil},
+        {"StrictInt64", NewAny(int16(0)), int64(0), nil},
+        {"StrictInt64", NewAny(int32(0)), int64(0), nil},
+        {"StrictInt64", NewAny(int64(0)), int64(0), nil},
+        {"StrictInt64", NewAny(uint(0)), int64(0), nil},
+        {"StrictInt64", NewAny(uint8(0)), int64(0), nil},
+        {"StrictInt64", NewAny(uint32(0)), int64(0), nil},
+        {"StrictInt64", NewAny(uint64(0)), int64(0), nil},
+        {"StrictInt64", Node{}, int64(0), ErrUnsupportType},
+        {"StrictInt64", NewRaw("0"), int64(0), nil},
+        {"Float64", NewRaw("true"), float64(1), nil},
+        {"Float64", NewRaw("false"), float64(0), nil},
         {"Float64", Node{}, float64(0), ErrUnsupportType},
         {"Float64", NewAny(float32(0)), float64(0), nil},
         {"Float64", NewAny(float64(0)), float64(0), nil},
         {"Float64", NewRaw("0.0"), float64(0.0), nil},
+        {"StrictFloat64", NewRaw("true"), float64(0), ErrUnsupportType},
+        {"StrictFloat64", NewRaw("false"), float64(0), ErrUnsupportType},
+        {"StrictFloat64", Node{}, float64(0), ErrUnsupportType},
+        {"StrictFloat64", NewAny(float32(0)), float64(0), nil},
+        {"StrictFloat64", NewAny(float64(0)), float64(0), nil},
+        {"StrictFloat64", NewRaw("0.0"), float64(0.0), nil},
         {"Number", Node{}, json.Number(""), ErrUnsupportType},
         {"Number", NewAny(json.Number("0")), json.Number("0"), nil},
         {"Number", NewRaw("0.0"), json.Number("0.0"), nil},
         {"Number", NewRaw("true"), json.Number("1"), nil},
         {"Number", NewRaw("false"), json.Number("0"), nil},
+        {"StrictNumber", NewRaw("true"), json.Number(""), ErrUnsupportType},
+        {"StrictNumber", NewRaw("false"), json.Number(""), ErrUnsupportType},
+        {"StrictNumber", Node{}, json.Number(""), ErrUnsupportType},
+        {"StrictNumber", NewAny(json.Number("0")), json.Number("0"), nil},
+        {"StrictNumber", NewRaw("0.0"), json.Number("0.0"), nil},
         {"String", Node{}, "", ErrUnsupportType},
-        {"String", NewAny(""), "", nil},
-        {"String", NewRaw(`""`), ``, nil},
+        {"String", NewAny(`\u263a`), `\u263a`, nil},
+        {"String", NewRaw(`"\u263a"`), `☺`, nil},
+        {"String", NewString(`\u263a`), `\u263a`, nil},
         {"String", NewRaw(`0.0`), "0.0", nil},
         {"String", NewRaw(`null`), "null", nil},
         {"String", NewRaw(`true`), "true", nil},
         {"String", NewRaw(`false`), "false", nil},
+        {"StrictString", Node{}, "", ErrUnsupportType},
+        {"StrictString", NewAny(`\u263a`), `\u263a`, nil},
+        {"StrictString", NewRaw(`"\u263a"`), `☺`, nil},
+        {"StrictString", NewString(`\u263a`), `\u263a`, nil},
+        {"StrictString", NewRaw(`0.0`), "", ErrUnsupportType},
+        {"StrictString", NewRaw(`null`), "", nil},
+        {"StrictString", NewRaw(`true`), "", ErrUnsupportType},
+        {"StrictString", NewRaw(`false`), "", ErrUnsupportType},
         {"Len", Node{}, 0, nil},
         {"Len", NewAny(0), 0, ErrUnsupportType},
         {"Len", NewNull(), 0, nil},