diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-04-20 15:44:41 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-04-20 15:44:41 +0200 |
commit | 50104cc32a498f7517a51c8dc93106c51c7a54b4 (patch) | |
tree | 47af80be259cc7c45d0eaec7d42e61fa38c8e4fb /src/pkg/json | |
parent | c072558b90f1bbedc2022b0f30c8b1ac4712538e (diff) | |
download | golang-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.go | 72 | ||||
-rw-r--r-- | src/pkg/json/decode_test.go | 35 | ||||
-rw-r--r-- | src/pkg/json/encode.go | 46 |
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 |