summaryrefslogtreecommitdiff
path: root/src/pkg/json
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2011-01-17 12:40:45 +0100
committerOndřej Surý <ondrej@sury.org>2011-01-17 12:40:45 +0100
commit3e45412327a2654a77944249962b3652e6142299 (patch)
treebc3bf69452afa055423cbe0c5cfa8ca357df6ccf /src/pkg/json
parentc533680039762cacbc37db8dc7eed074c3e497be (diff)
downloadgolang-3e45412327a2654a77944249962b3652e6142299.tar.gz
Imported Upstream version 2011.01.12upstream/2011.01.12
Diffstat (limited to 'src/pkg/json')
-rw-r--r--src/pkg/json/Makefile2
-rw-r--r--src/pkg/json/decode.go50
-rw-r--r--src/pkg/json/decode_test.go107
-rw-r--r--src/pkg/json/encode.go99
-rw-r--r--src/pkg/json/scanner.go19
-rw-r--r--src/pkg/json/scanner_test.go22
-rw-r--r--src/pkg/json/stream.go3
-rw-r--r--src/pkg/json/stream_test.go4
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