diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-01-17 12:40:45 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-01-17 12:40:45 +0100 |
commit | 3e45412327a2654a77944249962b3652e6142299 (patch) | |
tree | bc3bf69452afa055423cbe0c5cfa8ca357df6ccf /src/pkg/json | |
parent | c533680039762cacbc37db8dc7eed074c3e497be (diff) | |
download | golang-3e45412327a2654a77944249962b3652e6142299.tar.gz |
Imported Upstream version 2011.01.12upstream/2011.01.12
Diffstat (limited to 'src/pkg/json')
-rw-r--r-- | src/pkg/json/Makefile | 2 | ||||
-rw-r--r-- | src/pkg/json/decode.go | 50 | ||||
-rw-r--r-- | src/pkg/json/decode_test.go | 107 | ||||
-rw-r--r-- | src/pkg/json/encode.go | 99 | ||||
-rw-r--r-- | src/pkg/json/scanner.go | 19 | ||||
-rw-r--r-- | src/pkg/json/scanner_test.go | 22 | ||||
-rw-r--r-- | src/pkg/json/stream.go | 3 | ||||
-rw-r--r-- | src/pkg/json/stream_test.go | 4 |
8 files changed, 227 insertions, 79 deletions
diff --git a/src/pkg/json/Makefile b/src/pkg/json/Makefile index fa34bfd18..4e5a8a139 100644 --- a/src/pkg/json/Makefile +++ b/src/pkg/json/Makefile @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -include ../../Make.$(GOARCH) +include ../../Make.inc TARG=json GOFILES=\ diff --git a/src/pkg/json/decode.go b/src/pkg/json/decode.go index 3f6965009..c704cacbd 100644 --- a/src/pkg/json/decode.go +++ b/src/pkg/json/decode.go @@ -82,6 +82,18 @@ func (e *UnmarshalTypeError) String() string { return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() } +// An UnmarshalFieldError describes a JSON object key that +// led to an unexported (and therefore unwritable) struct field. +type UnmarshalFieldError struct { + Key string + Type *reflect.StructType + Field reflect.StructField +} + +func (e *UnmarshalFieldError) String() string { + return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() +} + // An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. // (The argument to Unmarshal must be a non-nil pointer.) type InvalidUnmarshalError struct { @@ -116,7 +128,9 @@ func (d *decodeState) unmarshal(v interface{}) (err os.Error) { } d.scan.reset() - d.value(pv.Elem()) + // We decode rv not pv.Elem because the Unmarshaler interface + // test must be applied at the top level of the value. + d.value(rv) return d.savedError } @@ -330,7 +344,7 @@ func (d *decodeState) array(v reflect.Value) { newcap = 4 } newv := reflect.MakeSlice(sv.Type().(*reflect.SliceType), sv.Len(), newcap) - reflect.ArrayCopy(newv, sv) + reflect.Copy(newv, sv) sv.Set(newv) } if i >= av.Len() && sv != nil { @@ -450,20 +464,32 @@ func (d *decodeState) object(v reflect.Value) { if mv != nil { subv = reflect.MakeZero(mv.Type().(*reflect.MapType).Elem()) } 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 := sv.Type().(*reflect.StructType).Field(i) + f = st.Field(i) if f.Tag == key { - subv = sv.Field(i) + ok = true break } } - if subv == nil { + if !ok { // Second, exact match. - subv = sv.FieldByName(key) - if subv == nil { - // Third, case-insensitive match. - subv = sv.FieldByNameFunc(func(s string) bool { return matchName(key, s) }) + f, ok = st.FieldByName(key) + } + if !ok { + // Third, case-insensitive match. + f, ok = st.FieldByNameFunc(func(s string) bool { return matchName(key, s) }) + } + + // Extract value; name must be exported. + if ok { + if f.PkgPath != "" { + d.saveError(&UnmarshalFieldError{key, st, f}) + } else { + subv = sv.FieldByIndex(f.Index) } } } @@ -805,13 +831,13 @@ func unquote(s []byte) (t string, ok bool) { if dec := utf16.DecodeRune(rune, rune1); dec != unicode.ReplacementChar { // A valid pair; consume. r += 6 - w += utf8.EncodeRune(dec, b[w:]) + w += utf8.EncodeRune(b[w:], dec) break } // Invalid surrogate; fall back to replacement rune. rune = unicode.ReplacementChar } - w += utf8.EncodeRune(rune, b[w:]) + w += utf8.EncodeRune(b[w:], rune) } // Quote, control characters are invalid. @@ -828,7 +854,7 @@ func unquote(s []byte) (t string, ok bool) { default: rune, size := utf8.DecodeRune(s[r:]) r += size - w += utf8.EncodeRune(rune, b[w:]) + w += utf8.EncodeRune(b[w:], rune) } } return string(b[0:w]), true diff --git a/src/pkg/json/decode_test.go b/src/pkg/json/decode_test.go index e10b2c56e..68cdea051 100644 --- a/src/pkg/json/decode_test.go +++ b/src/pkg/json/decode_test.go @@ -17,6 +17,30 @@ type T struct { Y int } +type tx struct { + x int +} + +var txType = reflect.Typeof((*tx)(nil)).(*reflect.PtrType).Elem().(*reflect.StructType) + +// A type that can unmarshal itself. + +type unmarshaler struct { + T bool +} + +func (u *unmarshaler) UnmarshalJSON(b []byte) os.Error { + *u = unmarshaler{true} // All we need to see that UnmarshalJson is called. + return nil +} + +var ( + um0, um1 unmarshaler // target2 of unmarshaling + ump = &um1 + umtrue = unmarshaler{true} +) + + type unmarshalTest struct { in string ptr interface{} @@ -26,26 +50,34 @@ type unmarshalTest struct { var unmarshalTests = []unmarshalTest{ // basic types - unmarshalTest{`true`, new(bool), true, nil}, - unmarshalTest{`1`, new(int), 1, nil}, - unmarshalTest{`1.2`, new(float), 1.2, nil}, - unmarshalTest{`-5`, new(int16), int16(-5), nil}, - unmarshalTest{`"a\u1234"`, new(string), "a\u1234", nil}, - unmarshalTest{`"http:\/\/"`, new(string), "http://", nil}, - unmarshalTest{`"g-clef: \uD834\uDD1E"`, new(string), "g-clef: \U0001D11E", nil}, - unmarshalTest{`"invalid: \uD834x\uDD1E"`, new(string), "invalid: \uFFFDx\uFFFD", nil}, - unmarshalTest{"null", new(interface{}), nil, nil}, - unmarshalTest{`{"X": [1,2,3], "Y": 4}`, new(T), T{Y: 4}, &UnmarshalTypeError{"array", reflect.Typeof("")}}, + {`true`, new(bool), true, nil}, + {`1`, new(int), 1, nil}, + {`1.2`, new(float), 1.2, nil}, + {`-5`, new(int16), int16(-5), nil}, + {`"a\u1234"`, new(string), "a\u1234", nil}, + {`"http:\/\/"`, new(string), "http://", nil}, + {`"g-clef: \uD834\uDD1E"`, new(string), "g-clef: \U0001D11E", nil}, + {`"invalid: \uD834x\uDD1E"`, new(string), "invalid: \uFFFDx\uFFFD", nil}, + {"null", new(interface{}), nil, nil}, + {`{"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)}}, + + // syntax errors + {`{"X": "foo", "Y"}`, nil, nil, SyntaxError("invalid character '}' after object key")}, // composite tests - unmarshalTest{allValueIndent, new(All), allValue, nil}, - unmarshalTest{allValueCompact, new(All), allValue, nil}, - unmarshalTest{allValueIndent, new(*All), &allValue, nil}, - unmarshalTest{allValueCompact, new(*All), &allValue, nil}, - unmarshalTest{pallValueIndent, new(All), pallValue, nil}, - unmarshalTest{pallValueCompact, new(All), pallValue, nil}, - unmarshalTest{pallValueIndent, new(*All), &pallValue, nil}, - unmarshalTest{pallValueCompact, new(*All), &pallValue, nil}, + {allValueIndent, new(All), allValue, nil}, + {allValueCompact, new(All), allValue, nil}, + {allValueIndent, new(*All), &allValue, nil}, + {allValueCompact, new(*All), &allValue, nil}, + {pallValueIndent, new(All), pallValue, nil}, + {pallValueCompact, new(All), pallValue, nil}, + {pallValueIndent, new(*All), &pallValue, nil}, + {pallValueCompact, new(*All), &pallValue, nil}, + + // unmarshal interface test + {`{"T":false}`, &um0, umtrue, nil}, // use "false" so test will fail if custom unmarshaler is not called + {`{"T":false}`, &ump, &umtrue, nil}, } func TestMarshal(t *testing.T) { @@ -70,12 +102,31 @@ func TestMarshal(t *testing.T) { } } +func TestMarshalBadUTF8(t *testing.T) { + s := "hello\xffworld" + b, err := Marshal(s) + if err == nil { + t.Fatal("Marshal bad UTF8: no error") + } + if len(b) != 0 { + t.Fatal("Marshal returned data") + } + if _, ok := err.(*InvalidUTF8Error); !ok { + t.Fatalf("Marshal did not return InvalidUTF8Error: %T %v", err, err) + } +} + func TestUnmarshal(t *testing.T) { var scan scanner for i, tt := range unmarshalTests { in := []byte(tt.in) if err := checkValid(in, &scan); err != nil { - t.Errorf("#%d: checkValid: %v", i, err) + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("#%d: checkValid: %v", i, err) + continue + } + } + if tt.ptr == nil { continue } // v = new(right-type) @@ -139,6 +190,16 @@ func TestUnmarshalPtrPtr(t *testing.T) { } } +func TestHTMLEscape(t *testing.T) { + b, err := MarshalForHTML("foobarbaz<>&quux") + if err != nil { + t.Fatalf("MarshalForHTML error: %v", err) + } + if !bytes.Equal(b, []byte(`"foobarbaz\u003c\u003e\u0026quux"`)) { + t.Fatalf("Unexpected encoding of \"<>&\": %s", b) + } +} + func noSpace(c int) int { if isSpace(c) { return -1 @@ -209,6 +270,8 @@ type All struct { Interface interface{} PInterface *interface{} + + unexported int } type Small struct { @@ -234,15 +297,15 @@ var allValue = All{ Foo: "foo", String: "16", Map: map[string]Small{ - "17": Small{Tag: "tag17"}, - "18": Small{Tag: "tag18"}, + "17": {Tag: "tag17"}, + "18": {Tag: "tag18"}, }, MapP: map[string]*Small{ "19": &Small{Tag: "tag19"}, "20": nil, }, EmptyMap: map[string]Small{}, - Slice: []Small{Small{Tag: "tag20"}, Small{Tag: "tag21"}}, + Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, SliceP: []*Small{&Small{Tag: "tag22"}, nil, &Small{Tag: "tag23"}}, EmptySlice: []Small{}, StringSlice: []string{"str24", "str25", "str26"}, diff --git a/src/pkg/json/encode.go b/src/pkg/json/encode.go index 5d7ce35cb..759b49dbe 100644 --- a/src/pkg/json/encode.go +++ b/src/pkg/json/encode.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// The json package implements encoding and decoding of JSON objects as +// defined in RFC 4627. package json import ( @@ -11,6 +13,7 @@ import ( "runtime" "sort" "strconv" + "utf8" ) // Marshal returns the JSON encoding of v. @@ -34,6 +37,7 @@ import ( // a member of the object. By default the object's key name is the // struct field name converted to lower case. If the struct field // has a tag, 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 @@ -76,6 +80,43 @@ func MarshalIndent(v interface{}, prefix, indent string) ([]byte, os.Error) { return buf.Bytes(), nil } +// MarshalForHTML is like Marshal but applies HTMLEscape to the output. +func MarshalForHTML(v interface{}) ([]byte, os.Error) { + b, err := Marshal(v) + if err != nil { + return nil, err + } + var buf bytes.Buffer + HTMLEscape(&buf, b) + return buf.Bytes(), nil +} + +// HTMLEscape appends to dst the JSON-encoded src with <, >, and & +// characters inside string literals changed to \u003c, \u003e, \u0026 +// so that the JSON will be safe to embed inside HTML <script> tags. +// For historical reasons, web browsers don't honor standard HTML +// escaping within <script> tags, so an alternative JSON encoding must +// be used. +func HTMLEscape(dst *bytes.Buffer, src []byte) { + // < > & can only appear in string literals, + // so just scan the string one byte at a time. + start := 0 + for i, c := range src { + if c == '<' || c == '>' || c == '&' { + if start < i { + dst.Write(src[start:i]) + } + dst.WriteString(`\u00`) + dst.WriteByte(hex[c>>4]) + dst.WriteByte(hex[c&0xF]) + start = i + 1 + } + } + if start < len(src) { + dst.Write(src[start:]) + } +} + // Marshaler is the interface implemented by objects that // can marshal themselves into valid JSON. type Marshaler interface { @@ -90,6 +131,14 @@ func (e *UnsupportedTypeError) String() string { return "json: unsupported type: " + e.Type.String() } +type InvalidUTF8Error struct { + S string +} + +func (e *InvalidUTF8Error) String() string { + return "json: invalid UTF-8 in string: " + strconv.Quote(e.S) +} + type MarshalerError struct { Type reflect.Type Error os.Error @@ -171,11 +220,17 @@ func (e *encodeState) reflectValue(v reflect.Value) { e.WriteByte('{') t := v.Type().(*reflect.StructType) n := v.NumField() + first := true for i := 0; i < n; i++ { - if i > 0 { + f := t.Field(i) + if f.PkgPath != "" { + continue + } + if first { + first = false + } else { e.WriteByte(',') } - f := t.Field(i) if f.Tag != "" { e.string(f.Tag) } else { @@ -242,18 +297,36 @@ func (sv stringValues) get(i int) string { return sv[i].(*reflect.StringValue) func (e *encodeState) string(s string) { e.WriteByte('"') - for _, c := range s { - switch { - case c < 0x20: - e.WriteString(`\u00`) - e.WriteByte(hex[c>>4]) - e.WriteByte(hex[c&0xF]) - case c == '\\' || c == '"': - e.WriteByte('\\') - fallthrough - default: - e.WriteRune(c) + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' { + i++ + continue + } + if start < i { + e.WriteString(s[start:i]) + } + if b == '\\' || b == '"' { + e.WriteByte('\\') + e.WriteByte(b) + } else { + e.WriteString(`\u00`) + e.WriteByte(hex[b>>4]) + e.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRuneInString(s[i:]) + if c == utf8.RuneError && size == 1 { + e.error(&InvalidUTF8Error{s}) } + i += size + } + if start < len(s) { + e.WriteString(s[start:]) } e.WriteByte('"') } diff --git a/src/pkg/json/scanner.go b/src/pkg/json/scanner.go index 27c5ffb7a..112c8f9c3 100644 --- a/src/pkg/json/scanner.go +++ b/src/pkg/json/scanner.go @@ -155,18 +155,7 @@ func (s *scanner) eof() int { // pushParseState pushes a new parse state p onto the parse stack. func (s *scanner) pushParseState(p int) { - n := len(s.parseState) - if n >= cap(s.parseState) { - if n == 0 { - s.parseState = make([]int, 0, 16) - } else { - ps := make([]int, n, 2*n) - copy(ps, s.parseState) - s.parseState = ps - } - } - s.parseState = s.parseState[0 : n+1] - s.parseState[n] = p + s.parseState = append(s.parseState, p) } // popParseState pops a parse state (already obtained) off the stack @@ -251,6 +240,8 @@ func stateBeginStringOrEmpty(s *scanner, c int) int { return scanSkipSpace } if c == '}' { + n := len(s.parseState) + s.parseState[n-1] = parseObjectValue return stateEndValue(s, c) } return stateBeginString(s, c) @@ -289,10 +280,6 @@ func stateEndValue(s *scanner, c int) int { s.step = stateBeginValue return scanObjectKey } - if c == '}' { - s.popParseState() - return scanEndObject - } return s.error(c, "after object key") case parseObjectValue: if c == ',' { diff --git a/src/pkg/json/scanner_test.go b/src/pkg/json/scanner_test.go index cdc032263..2dc8ff87f 100644 --- a/src/pkg/json/scanner_test.go +++ b/src/pkg/json/scanner_test.go @@ -19,14 +19,14 @@ type example struct { } var examples = []example{ - example{`1`, `1`}, - example{`{}`, `{}`}, - example{`[]`, `[]`}, - example{`{"":2}`, "{\n\t\"\": 2\n}"}, - example{`[3]`, "[\n\t3\n]"}, - example{`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, - example{`{"x":1}`, "{\n\t\"x\": 1\n}"}, - example{ex1, ex1i}, + {`1`, `1`}, + {`{}`, `{}`}, + {`[]`, `[]`}, + {`{"":2}`, "{\n\t\"\": 2\n}"}, + {`[3]`, "[\n\t3\n]"}, + {`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, + {`{"x":1}`, "{\n\t\"x\": 1\n}"}, + {ex1, ex1i}, } var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]` @@ -138,7 +138,7 @@ func TestNextValueBig(t *testing.T) { var scan scanner item, rest, err := nextValue(jsonBig, &scan) if err != nil { - t.Fatalf("nextValue: ", err) + t.Fatalf("nextValue: %s", err) } if len(item) != len(jsonBig) || &item[0] != &jsonBig[0] { t.Errorf("invalid item: %d %d", len(item), len(jsonBig)) @@ -147,9 +147,9 @@ func TestNextValueBig(t *testing.T) { t.Errorf("invalid rest: %d", len(rest)) } - item, rest, err = nextValue(bytes.Add(jsonBig, []byte("HELLO WORLD")), &scan) + item, rest, err = nextValue(append(jsonBig, []byte("HELLO WORLD")...), &scan) if err != nil { - t.Fatalf("nextValue extra: ", err) + t.Fatalf("nextValue extra: %s", err) } if len(item) != len(jsonBig) { t.Errorf("invalid item: %d %d", len(item), len(jsonBig)) diff --git a/src/pkg/json/stream.go b/src/pkg/json/stream.go index d4fb34660..cb9b16559 100644 --- a/src/pkg/json/stream.go +++ b/src/pkg/json/stream.go @@ -5,7 +5,6 @@ package json import ( - "bytes" "io" "os" ) @@ -177,7 +176,7 @@ func (m *RawMessage) UnmarshalJSON(data []byte) os.Error { if m == nil { return os.NewError("json.RawMessage: UnmarshalJSON on nil pointer") } - *m = bytes.Add((*m)[0:0], data) + *m = append((*m)[0:0], data...) return nil } diff --git a/src/pkg/json/stream_test.go b/src/pkg/json/stream_test.go index ab90b754e..c83cfe3a9 100644 --- a/src/pkg/json/stream_test.go +++ b/src/pkg/json/stream_test.go @@ -71,10 +71,10 @@ func TestDecoder(t *testing.T) { } } if !reflect.DeepEqual(out, streamTest[0:i]) { - t.Errorf("decoding %d items: mismatch") + t.Errorf("decoding %d items: mismatch", i) for j := range out { if !reflect.DeepEqual(out[j], streamTest[j]) { - t.Errorf("#%d: have %v want %v", out[j], streamTest[j]) + t.Errorf("#%d: have %v want %v", j, out[j], streamTest[j]) } } break |