summaryrefslogtreecommitdiff
path: root/src/pkg/json
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2011-04-20 15:44:41 +0200
committerOndřej Surý <ondrej@sury.org>2011-04-20 15:44:41 +0200
commit50104cc32a498f7517a51c8dc93106c51c7a54b4 (patch)
tree47af80be259cc7c45d0eaec7d42e61fa38c8e4fb /src/pkg/json
parentc072558b90f1bbedc2022b0f30c8b1ac4712538e (diff)
downloadgolang-upstream/2011.03.07.1.tar.gz
Imported Upstream version 2011.03.07.1upstream/2011.03.07.1
Diffstat (limited to 'src/pkg/json')
-rw-r--r--src/pkg/json/decode.go72
-rw-r--r--src/pkg/json/decode_test.go35
-rw-r--r--src/pkg/json/encode.go46
3 files changed, 129 insertions, 24 deletions
diff --git a/src/pkg/json/decode.go b/src/pkg/json/decode.go
index ff91dd83c..501230c0c 100644
--- a/src/pkg/json/decode.go
+++ b/src/pkg/json/decode.go
@@ -9,6 +9,7 @@ package json
import (
"container/vector"
+ "encoding/base64"
"os"
"reflect"
"runtime"
@@ -466,13 +467,15 @@ func (d *decodeState) object(v reflect.Value) {
} else {
var f reflect.StructField
var ok bool
- // First try for field with that tag.
st := sv.Type().(*reflect.StructType)
- for i := 0; i < sv.NumField(); i++ {
- f = st.Field(i)
- if f.Tag == key {
- ok = true
- break
+ // First try for field with that tag.
+ if isValidTag(key) {
+ for i := 0; i < sv.NumField(); i++ {
+ f = st.Field(i)
+ if f.Tag == key {
+ ok = true
+ break
+ }
}
}
if !ok {
@@ -568,17 +571,29 @@ func (d *decodeState) literal(v reflect.Value) {
}
case '"': // string
- s, ok := unquote(item)
+ s, ok := unquoteBytes(item)
if !ok {
d.error(errPhase)
}
switch v := v.(type) {
default:
d.saveError(&UnmarshalTypeError{"string", v.Type()})
+ case *reflect.SliceValue:
+ if v.Type() != byteSliceType {
+ d.saveError(&UnmarshalTypeError{"string", v.Type()})
+ break
+ }
+ b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
+ n, err := base64.StdEncoding.Decode(b, s)
+ if err != nil {
+ d.saveError(err)
+ break
+ }
+ v.Set(reflect.NewValue(b[0:n]).(*reflect.SliceValue))
case *reflect.StringValue:
- v.Set(s)
+ v.Set(string(s))
case *reflect.InterfaceValue:
- v.Set(reflect.NewValue(s))
+ v.Set(reflect.NewValue(string(s)))
}
default: // number
@@ -772,12 +787,43 @@ func getu4(s []byte) int {
// unquote converts a quoted JSON string literal s into an actual string t.
// The rules are different than for Go, so cannot use strconv.Unquote.
func unquote(s []byte) (t string, ok bool) {
+ s, ok = unquoteBytes(s)
+ t = string(s)
+ return
+}
+
+func unquoteBytes(s []byte) (t []byte, ok bool) {
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
return
}
+ s = s[1 : len(s)-1]
+
+ // Check for unusual characters. If there are none,
+ // then no unquoting is needed, so return a slice of the
+ // original bytes.
+ r := 0
+ for r < len(s) {
+ c := s[r]
+ if c == '\\' || c == '"' || c < ' ' {
+ break
+ }
+ if c < utf8.RuneSelf {
+ r++
+ continue
+ }
+ rune, size := utf8.DecodeRune(s[r:])
+ if rune == utf8.RuneError && size == 1 {
+ break
+ }
+ r += size
+ }
+ if r == len(s) {
+ return s, true
+ }
+
b := make([]byte, len(s)+2*utf8.UTFMax)
- w := 0
- for r := 1; r < len(s)-1; {
+ w := copy(b, s[0:r])
+ for r < len(s) {
// Out of room? Can only happen if s is full of
// malformed UTF-8 and we're replacing each
// byte with RuneError.
@@ -789,7 +835,7 @@ func unquote(s []byte) (t string, ok bool) {
switch c := s[r]; {
case c == '\\':
r++
- if r >= len(s)-1 {
+ if r >= len(s) {
return
}
switch s[r] {
@@ -857,5 +903,5 @@ func unquote(s []byte) (t string, ok bool) {
w += utf8.EncodeRune(b[w:], rune)
}
}
- return string(b[0:w]), true
+ return b[0:w], true
}
diff --git a/src/pkg/json/decode_test.go b/src/pkg/json/decode_test.go
index 9cb27af41..ad6026363 100644
--- a/src/pkg/json/decode_test.go
+++ b/src/pkg/json/decode_test.go
@@ -40,6 +40,11 @@ var (
umtrue = unmarshaler{true}
)
+type badTag struct {
+ X string
+ Y string "y"
+ Z string "@#*%(#@"
+}
type unmarshalTest struct {
in string
@@ -62,6 +67,9 @@ var unmarshalTests = []unmarshalTest{
{`{"X": [1,2,3], "Y": 4}`, new(T), T{Y: 4}, &UnmarshalTypeError{"array", reflect.Typeof("")}},
{`{"x": 1}`, new(tx), tx{}, &UnmarshalFieldError{"x", txType, txType.Field(0)}},
+ // skip invalid tags
+ {`{"X":"a", "y":"b", "Z":"c"}`, new(badTag), badTag{"a", "b", "c"}, nil},
+
// syntax errors
{`{"X": "foo", "Y"}`, nil, nil, SyntaxError("invalid character '}' after object key")},
@@ -164,6 +172,25 @@ func TestUnmarshalMarshal(t *testing.T) {
}
}
+func TestLargeByteSlice(t *testing.T) {
+ s0 := make([]byte, 2000)
+ for i := range s0 {
+ s0[i] = byte(i)
+ }
+ b, err := Marshal(s0)
+ if err != nil {
+ t.Fatalf("Marshal: %v", err)
+ }
+ var s1 []byte
+ if err := Unmarshal(b, &s1); err != nil {
+ t.Fatalf("Unmarshal: %v", err)
+ }
+ if bytes.Compare(s0, s1) != 0 {
+ t.Errorf("Marshal large byte slice")
+ diff(t, s0, s1)
+ }
+}
+
type Xint struct {
X int
}
@@ -412,11 +439,7 @@ var allValueIndent = `{
"str25",
"str26"
],
- "ByteSlice": [
- 27,
- 28,
- 29
- ],
+ "ByteSlice": "Gxwd",
"Small": {
"Tag": "tag30"
},
@@ -502,7 +525,7 @@ var pallValueIndent = `{
"EmptySlice": [],
"NilSlice": [],
"StringSlice": [],
- "ByteSlice": [],
+ "ByteSlice": "",
"Small": {
"Tag": ""
},
diff --git a/src/pkg/json/encode.go b/src/pkg/json/encode.go
index 0fcc78aa8..26ce47039 100644
--- a/src/pkg/json/encode.go
+++ b/src/pkg/json/encode.go
@@ -7,12 +7,14 @@
package json
import (
- "os"
"bytes"
+ "encoding/base64"
+ "os"
"reflect"
"runtime"
"sort"
"strconv"
+ "unicode"
"utf8"
)
@@ -31,12 +33,14 @@ import (
// String values encode as JSON strings, with each invalid UTF-8 sequence
// replaced by the encoding of the Unicode replacement character U+FFFD.
//
-// Array and slice values encode as JSON arrays.
+// Array and slice values encode as JSON arrays, except that
+// []byte encodes as a base64-encoded string.
//
// Struct values encode as JSON objects. Each struct field becomes
// a member of the object. By default the object's key name is the
-// struct field name. If the struct field has a tag, that tag will
-// be used as the name instead. Only exported fields will be encoded.
+// struct field name. If the struct field has a non-empty tag consisting
+// of only Unicode letters, digits, and underscores, that tag will be used
+// as the name instead. Only exported fields will be encoded.
//
// Map values encode as JSON objects.
// The map's key type must be string; the object keys are used directly
@@ -176,6 +180,8 @@ func (e *encodeState) error(err os.Error) {
panic(err)
}
+var byteSliceType = reflect.Typeof([]byte(nil))
+
func (e *encodeState) reflectValue(v reflect.Value) {
if v == nil {
e.WriteString("null")
@@ -230,7 +236,7 @@ func (e *encodeState) reflectValue(v reflect.Value) {
} else {
e.WriteByte(',')
}
- if f.Tag != "" {
+ if isValidTag(f.Tag) {
e.string(f.Tag)
} else {
e.string(f.Name)
@@ -262,6 +268,24 @@ func (e *encodeState) reflectValue(v reflect.Value) {
e.WriteByte('}')
case reflect.ArrayOrSliceValue:
+ if v.Type() == byteSliceType {
+ e.WriteByte('"')
+ s := v.Interface().([]byte)
+ if len(s) < 1024 {
+ // for small buffers, using Encode directly is much faster.
+ dst := make([]byte, base64.StdEncoding.EncodedLen(len(s)))
+ base64.StdEncoding.Encode(dst, s)
+ e.Write(dst)
+ } else {
+ // for large buffers, avoid unnecessary extra temporary
+ // buffer space.
+ enc := base64.NewEncoder(base64.StdEncoding, e)
+ enc.Write(s)
+ enc.Close()
+ }
+ e.WriteByte('"')
+ break
+ }
e.WriteByte('[')
n := v.Len()
for i := 0; i < n; i++ {
@@ -285,6 +309,18 @@ func (e *encodeState) reflectValue(v reflect.Value) {
return
}
+func isValidTag(s string) bool {
+ if s == "" {
+ return false
+ }
+ for _, c := range s {
+ if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
+ return false
+ }
+ }
+ return true
+}
+
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
type stringValues []reflect.Value