aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/json
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/json')
-rw-r--r--libgo/go/json/decode.go15
-rw-r--r--libgo/go/json/decode_test.go34
-rw-r--r--libgo/go/json/encode.go91
-rw-r--r--libgo/go/json/encode_test.go44
-rw-r--r--libgo/go/json/scanner_test.go2
-rw-r--r--libgo/go/json/tagkey_test.go95
6 files changed, 248 insertions, 33 deletions
diff --git a/libgo/go/json/decode.go b/libgo/go/json/decode.go
index e78b60c..4f6562b 100644
--- a/libgo/go/json/decode.go
+++ b/libgo/go/json/decode.go
@@ -8,7 +8,6 @@
package json
import (
- "container/vector"
"encoding/base64"
"os"
"reflect"
@@ -71,7 +70,6 @@ type Unmarshaler interface {
UnmarshalJSON([]byte) os.Error
}
-
// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
@@ -253,6 +251,12 @@ func (d *decodeState) value(v reflect.Value) {
// if it encounters an Unmarshaler, indirect stops and returns that.
// if wantptr is true, indirect stops at the last pointer.
func (d *decodeState) indirect(v reflect.Value, wantptr bool) (Unmarshaler, reflect.Value) {
+ // If v is a named type and is addressable,
+ // start with its address, so that if the type has pointer methods,
+ // we find them.
+ if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
+ v = v.Addr()
+ }
for {
var isUnmarshaler bool
if v.Type().NumMethod() > 0 {
@@ -482,7 +486,7 @@ func (d *decodeState) object(v reflect.Value) {
if isValidTag(key) {
for i := 0; i < sv.NumField(); i++ {
f = st.Field(i)
- if f.Tag == key {
+ if f.Tag.Get("json") == key {
ok = true
break
}
@@ -670,7 +674,7 @@ func (d *decodeState) valueInterface() interface{} {
// arrayInterface is like array but returns []interface{}.
func (d *decodeState) arrayInterface() []interface{} {
- var v vector.Vector
+ var v []interface{}
for {
// Look ahead for ] - can only happen on first iteration.
op := d.scanWhile(scanSkipSpace)
@@ -682,7 +686,7 @@ func (d *decodeState) arrayInterface() []interface{} {
d.off--
d.scan.undo(op)
- v.Push(d.valueInterface())
+ v = append(v, d.valueInterface())
// Next token must be , or ].
op = d.scanWhile(scanSkipSpace)
@@ -742,7 +746,6 @@ func (d *decodeState) objectInterface() map[string]interface{} {
return m
}
-
// literalInterface is like literal but returns an interface value.
func (d *decodeState) literalInterface() interface{} {
// All bytes inside literal return scanContinue op code.
diff --git a/libgo/go/json/decode_test.go b/libgo/go/json/decode_test.go
index bf8bf10..a855d60 100644
--- a/libgo/go/json/decode_test.go
+++ b/libgo/go/json/decode_test.go
@@ -34,18 +34,19 @@ func (u *unmarshaler) UnmarshalJSON(b []byte) os.Error {
return nil
}
+type ustruct struct {
+ M unmarshaler
+}
+
var (
um0, um1 unmarshaler // target2 of unmarshaling
ump = &um1
umtrue = unmarshaler{true}
+ umslice = []unmarshaler{unmarshaler{true}}
+ umslicep = new([]unmarshaler)
+ umstruct = ustruct{unmarshaler{true}}
)
-type badTag struct {
- X string
- Y string "y"
- Z string "@#*%(#@"
-}
-
type unmarshalTest struct {
in string
ptr interface{}
@@ -67,9 +68,6 @@ 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", 17}},
@@ -86,6 +84,9 @@ var unmarshalTests = []unmarshalTest{
// 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},
+ {`[{"T":false}]`, &umslice, umslice, nil},
+ {`[{"T":false}]`, &umslicep, &umslice, nil},
+ {`{"M":{"T":false}}`, &umstruct, umstruct, nil},
}
func TestMarshal(t *testing.T) {
@@ -149,7 +150,6 @@ func TestUnmarshal(t *testing.T) {
println(string(data))
data, _ = Marshal(tt.out)
println(string(data))
- return
continue
}
}
@@ -217,6 +217,18 @@ func TestUnmarshalPtrPtr(t *testing.T) {
}
}
+func TestEscape(t *testing.T) {
+ const input = `"foobar"<html>`
+ const expected = `"\"foobar\"\u003chtml\u003e"`
+ b, err := Marshal(input)
+ if err != nil {
+ t.Fatalf("Marshal error: %v", err)
+ }
+ if s := string(b); s != expected {
+ t.Errorf("Encoding of [%s] was [%s], want [%s]", input, s, expected)
+ }
+}
+
func TestHTMLEscape(t *testing.T) {
b, err := MarshalForHTML("foobarbaz<>&quux")
if err != nil {
@@ -250,7 +262,7 @@ type All struct {
Float32 float32
Float64 float64
- Foo string "bar"
+ Foo string `json:"bar"`
PBool *bool
PInt *int
diff --git a/libgo/go/json/encode.go b/libgo/go/json/encode.go
index ec0a14a..3e593fe 100644
--- a/libgo/go/json/encode.go
+++ b/libgo/go/json/encode.go
@@ -14,6 +14,7 @@ import (
"runtime"
"sort"
"strconv"
+ "strings"
"unicode"
"utf8"
)
@@ -36,11 +37,30 @@ import (
// 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 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.
+// Struct values encode as JSON objects. Each exported struct field
+// becomes a member of the object unless the field is empty and its tag
+// specifies the "omitempty" option. The empty values are false, 0, any
+// nil pointer or interface value, and any array, slice, map, or string of
+// length zero. The object's default key string is the struct field name
+// but can be specified in the struct field's tag value. The "json" key in
+// struct field's tag value is the key name, followed by an optional comma
+// and options. Examples:
+//
+// // Specifies that Field appears in JSON as key "myName"
+// Field int `json:"myName"`
+//
+// // Specifies that Field appears in JSON as key "myName" and
+// // the field is omitted from the object if its value is empty,
+// // as defined above.
+// Field int `json:"myName,omitempty"`
+//
+// // Field appears in JSON as key "Field" (the default), but
+// // the field is skipped if empty.
+// // Note the leading comma.
+// Field int `json:",omitempty"`
+//
+// The key name will be used if it's a non-empty string consisting of
+// only Unicode letters, digits, dollar signs, hyphens, and underscores.
//
// Map values encode as JSON objects.
// The map's key type must be string; the object keys are used directly
@@ -182,6 +202,24 @@ func (e *encodeState) error(err os.Error) {
var byteSliceType = reflect.TypeOf([]byte(nil))
+func isEmptyValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+ }
+ return false
+}
+
func (e *encodeState) reflectValue(v reflect.Value) {
if !v.IsValid() {
e.WriteString("null")
@@ -231,18 +269,30 @@ func (e *encodeState) reflectValue(v reflect.Value) {
if f.PkgPath != "" {
continue
}
+ tag, omitEmpty := f.Name, false
+ if tv := f.Tag.Get("json"); tv != "" {
+ ss := strings.SplitN(tv, ",", 2)
+ if isValidTag(ss[0]) {
+ tag = ss[0]
+ }
+ if len(ss) > 1 {
+ // Currently the only option is omitempty,
+ // so parsing is trivial.
+ omitEmpty = ss[1] == "omitempty"
+ }
+ }
+ fieldValue := v.Field(i)
+ if omitEmpty && isEmptyValue(fieldValue) {
+ continue
+ }
if first {
first = false
} else {
e.WriteByte(',')
}
- if isValidTag(f.Tag) {
- e.string(f.Tag)
- } else {
- e.string(f.Name)
- }
+ e.string(tag)
e.WriteByte(':')
- e.reflectValue(v.Field(i))
+ e.reflectValue(fieldValue)
}
e.WriteByte('}')
@@ -314,7 +364,7 @@ func isValidTag(s string) bool {
return false
}
for _, c := range s {
- if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
+ if c != '$' && c != '-' && c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
@@ -335,17 +385,28 @@ func (e *encodeState) string(s string) {
start := 0
for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf {
- if 0x20 <= b && b != '\\' && b != '"' {
+ if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' {
i++
continue
}
if start < i {
e.WriteString(s[start:i])
}
- if b == '\\' || b == '"' {
+ switch b {
+ case '\\', '"':
e.WriteByte('\\')
e.WriteByte(b)
- } else {
+ case '\n':
+ e.WriteByte('\\')
+ e.WriteByte('n')
+ case '\r':
+ e.WriteByte('\\')
+ e.WriteByte('r')
+ default:
+ // This encodes bytes < 0x20 except for \n and \r,
+ // as well as < and >. The latter are escaped because they
+ // can lead to security holes when user-controlled strings
+ // are rendered into JSON and served to some browsers.
e.WriteString(`\u00`)
e.WriteByte(hex[b>>4])
e.WriteByte(hex[b&0xF])
diff --git a/libgo/go/json/encode_test.go b/libgo/go/json/encode_test.go
new file mode 100644
index 0000000..0e4b637
--- /dev/null
+++ b/libgo/go/json/encode_test.go
@@ -0,0 +1,44 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package json
+
+import (
+ "testing"
+)
+
+type Optionals struct {
+ Sr string `json:"sr"`
+ So string `json:"so,omitempty"`
+
+ Ir int `json:"omitempty"` // actually named omitempty, not an option
+ Io int `json:"io,omitempty"`
+
+ Slr []string `json:"slr,random"`
+ Slo []string `json:"slo,omitempty"`
+
+ Mr map[string]interface{} `json:"mr"`
+ Mo map[string]interface{} `json:",omitempty"`
+}
+
+var optionalsExpected = `{
+ "sr": "",
+ "omitempty": 0,
+ "slr": [],
+ "mr": {}
+}`
+
+func TestOmitEmpty(t *testing.T) {
+ var o Optionals
+ o.Mr = map[string]interface{}{}
+ o.Mo = map[string]interface{}{}
+
+ got, err := MarshalIndent(&o, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := string(got); got != optionalsExpected {
+ t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected)
+ }
+}
diff --git a/libgo/go/json/scanner_test.go b/libgo/go/json/scanner_test.go
index df87c71..023e7c8 100644
--- a/libgo/go/json/scanner_test.go
+++ b/libgo/go/json/scanner_test.go
@@ -255,7 +255,7 @@ func genArray(n int) []interface{} {
if n > 0 && f == 0 {
f = 1
}
- x := make([]interface{}, int(f))
+ x := make([]interface{}, f)
for i := range x {
x[i] = genValue(((i+1)*n)/f - (i*n)/f)
}
diff --git a/libgo/go/json/tagkey_test.go b/libgo/go/json/tagkey_test.go
new file mode 100644
index 0000000..31fe2be
--- /dev/null
+++ b/libgo/go/json/tagkey_test.go
@@ -0,0 +1,95 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package json
+
+import (
+ "testing"
+)
+
+type basicLatin2xTag struct {
+ V string `json:"$-"`
+}
+
+type basicLatin3xTag struct {
+ V string `json:"0123456789"`
+}
+
+type basicLatin4xTag struct {
+ V string `json:"ABCDEFGHIJKLMO"`
+}
+
+type basicLatin5xTag struct {
+ V string `json:"PQRSTUVWXYZ_"`
+}
+
+type basicLatin6xTag struct {
+ V string `json:"abcdefghijklmno"`
+}
+
+type basicLatin7xTag struct {
+ V string `json:"pqrstuvwxyz"`
+}
+
+type miscPlaneTag struct {
+ V string `json:"色は匂へど"`
+}
+
+type emptyTag struct {
+ W string
+}
+
+type misnamedTag struct {
+ X string `jsom:"Misnamed"`
+}
+
+type badFormatTag struct {
+ Y string `:"BadFormat"`
+}
+
+type badCodeTag struct {
+ Z string `json:" !\"#%&'()*+,./"`
+}
+
+var structTagObjectKeyTests = []struct {
+ raw interface{}
+ value string
+ key string
+}{
+ {basicLatin2xTag{"2x"}, "2x", "$-"},
+ {basicLatin3xTag{"3x"}, "3x", "0123456789"},
+ {basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"},
+ {basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"},
+ {basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"},
+ {basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"},
+ {miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"},
+ {emptyTag{"Pour Moi"}, "Pour Moi", "W"},
+ {misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"},
+ {badFormatTag{"Orfevre"}, "Orfevre", "Y"},
+ {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"},
+}
+
+func TestStructTagObjectKey(t *testing.T) {
+ for _, tt := range structTagObjectKeyTests {
+ b, err := Marshal(tt.raw)
+ if err != nil {
+ t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err)
+ }
+ var f interface{}
+ err = Unmarshal(b, &f)
+ if err != nil {
+ t.Fatalf("Unmarshal(%#q) failed: %v", b, err)
+ }
+ for i, v := range f.(map[string]interface{}) {
+ switch i {
+ case tt.key:
+ if s, ok := v.(string); !ok || s != tt.value {
+ t.Fatalf("Unexpected value: %#q, want %v", s, tt.value)
+ }
+ default:
+ t.Fatalf("Unexpected key: %#q", i)
+ }
+ }
+ }
+}