diff options
Diffstat (limited to 'libgo/go/json')
-rw-r--r-- | libgo/go/json/decode.go | 15 | ||||
-rw-r--r-- | libgo/go/json/decode_test.go | 34 | ||||
-rw-r--r-- | libgo/go/json/encode.go | 91 | ||||
-rw-r--r-- | libgo/go/json/encode_test.go | 44 | ||||
-rw-r--r-- | libgo/go/json/scanner_test.go | 2 | ||||
-rw-r--r-- | libgo/go/json/tagkey_test.go | 95 |
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) + } + } + } +} |