From 9ad2a96babca1e646856b4335875b975f9bb7fa1 Mon Sep 17 00:00:00 2001 From: Ondřej Surý Date: Mon, 19 Sep 2011 10:12:52 +0200 Subject: Imported Upstream version 60.1 --- src/pkg/gob/doc.go | 3 +++ src/pkg/json/Makefile | 1 + src/pkg/json/decode.go | 22 +++++++++++++--- src/pkg/json/decode_test.go | 11 +++++++- src/pkg/json/encode.go | 62 ++++++++++++++++++++++++++++++++------------- src/pkg/json/encode_test.go | 38 +++++++++++++++++++++++++++ src/pkg/json/tags.go | 44 ++++++++++++++++++++++++++++++++ src/pkg/json/tags_test.go | 28 ++++++++++++++++++++ src/pkg/reflect/type.go | 3 +++ 9 files changed, 189 insertions(+), 23 deletions(-) create mode 100644 src/pkg/json/tags.go create mode 100644 src/pkg/json/tags_test.go (limited to 'src/pkg') diff --git a/src/pkg/gob/doc.go b/src/pkg/gob/doc.go index 35d882afb..a9284ced7 100644 --- a/src/pkg/gob/doc.go +++ b/src/pkg/gob/doc.go @@ -221,6 +221,9 @@ In summary, a gob stream looks like where * signifies zero or more repetitions and the type id of a value must be predefined or be defined before the value in the stream. + +See "Gobs of data" for a design discussion of the gob wire format: +http://blog.golang.org/2011/03/gobs-of-data.html */ package gob diff --git a/src/pkg/json/Makefile b/src/pkg/json/Makefile index 4e5a8a139..28ed62bc4 100644 --- a/src/pkg/json/Makefile +++ b/src/pkg/json/Makefile @@ -11,5 +11,6 @@ GOFILES=\ indent.go\ scanner.go\ stream.go\ + tags.go\ include ../../Make.pkg diff --git a/src/pkg/json/decode.go b/src/pkg/json/decode.go index 4f6562bd5..b7129f984 100644 --- a/src/pkg/json/decode.go +++ b/src/pkg/json/decode.go @@ -140,6 +140,7 @@ type decodeState struct { scan scanner nextscan scanner // for calls to nextValue savedError os.Error + tempstr string // scratch space to avoid some allocations } // errPhase is used for errors that should not happen unless @@ -470,6 +471,8 @@ func (d *decodeState) object(v reflect.Value) { // Figure out field corresponding to key. var subv reflect.Value + destring := false // whether the value is wrapped in a string to be decoded first + if mv.IsValid() { elemType := mv.Type().Elem() if !mapElem.IsValid() { @@ -486,7 +489,8 @@ func (d *decodeState) object(v reflect.Value) { if isValidTag(key) { for i := 0; i < sv.NumField(); i++ { f = st.Field(i) - if f.Tag.Get("json") == key { + tagName, _ := parseTag(f.Tag.Get("json")) + if tagName == key { ok = true break } @@ -508,6 +512,8 @@ func (d *decodeState) object(v reflect.Value) { } else { subv = sv.FieldByIndex(f.Index) } + _, opts := parseTag(f.Tag.Get("json")) + destring = opts.Contains("string") } } @@ -520,8 +526,12 @@ func (d *decodeState) object(v reflect.Value) { } // Read value. - d.value(subv) - + if destring { + d.value(reflect.ValueOf(&d.tempstr)) + d.literalStore([]byte(d.tempstr), subv) + } else { + d.value(subv) + } // Write value back to map; // if using struct, subv points into struct already. if mv.IsValid() { @@ -550,8 +560,12 @@ func (d *decodeState) literal(v reflect.Value) { // Scan read one byte too far; back up. d.off-- d.scan.undo(op) - item := d.data[start:d.off] + d.literalStore(d.data[start:d.off], v) +} + +// literalStore decodes a literal stored in item into v. +func (d *decodeState) literalStore(item []byte, v reflect.Value) { // Check for unmarshaler. wantptr := item[0] == 'n' // null unmarshaler, pv := d.indirect(v, wantptr) diff --git a/src/pkg/json/decode_test.go b/src/pkg/json/decode_test.go index a855d6048..5f6c3f5b8 100644 --- a/src/pkg/json/decode_test.go +++ b/src/pkg/json/decode_test.go @@ -262,7 +262,10 @@ type All struct { Float32 float32 Float64 float64 - Foo string `json:"bar"` + Foo string `json:"bar"` + Foo2 string `json:"bar2,dummyopt"` + + IntStr int64 `json:",string"` PBool *bool PInt *int @@ -331,6 +334,8 @@ var allValue = All{ Float32: 14.1, Float64: 15.1, Foo: "foo", + Foo2: "foo2", + IntStr: 42, String: "16", Map: map[string]Small{ "17": {Tag: "tag17"}, @@ -391,6 +396,8 @@ var allValueIndent = `{ "Float32": 14.1, "Float64": 15.1, "bar": "foo", + "bar2": "foo2", + "IntStr": "42", "PBool": null, "PInt": null, "PInt8": null, @@ -481,6 +488,8 @@ var pallValueIndent = `{ "Float32": 0, "Float64": 0, "bar": "", + "bar2": "", + "IntStr": "0", "PBool": true, "PInt": 2, "PInt8": 3, diff --git a/src/pkg/json/encode.go b/src/pkg/json/encode.go index 3e593fec1..16be5e2af 100644 --- a/src/pkg/json/encode.go +++ b/src/pkg/json/encode.go @@ -4,6 +4,9 @@ // Package json implements encoding and decoding of JSON objects as defined in // RFC 4627. +// +// See "JSON and Go" for an introduction to this package: +// http://blog.golang.org/2011/01/json-and-go.html package json import ( @@ -14,7 +17,6 @@ import ( "runtime" "sort" "strconv" - "strings" "unicode" "utf8" ) @@ -59,6 +61,12 @@ import ( // // Note the leading comma. // Field int `json:",omitempty"` // +// The "string" option signals that a field is stored as JSON inside a +// JSON-encoded string. This extra level of encoding is sometimes +// used when communicating with JavaScript programs: +// +// Int64String int64 `json:",string"` +// // The key name will be used if it's a non-empty string consisting of // only Unicode letters, digits, dollar signs, hyphens, and underscores. // @@ -221,6 +229,12 @@ func isEmptyValue(v reflect.Value) bool { } func (e *encodeState) reflectValue(v reflect.Value) { + e.reflectValueQuoted(v, false) +} + +// reflectValueQuoted writes the value in v to the output. +// If quoted is true, the serialization is wrapped in a JSON string. +func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) { if !v.IsValid() { e.WriteString("null") return @@ -238,26 +252,39 @@ func (e *encodeState) reflectValue(v reflect.Value) { return } + writeString := (*encodeState).WriteString + if quoted { + writeString = (*encodeState).string + } + switch v.Kind() { case reflect.Bool: x := v.Bool() if x { - e.WriteString("true") + writeString(e, "true") } else { - e.WriteString("false") + writeString(e, "false") } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - e.WriteString(strconv.Itoa64(v.Int())) + writeString(e, strconv.Itoa64(v.Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - e.WriteString(strconv.Uitoa64(v.Uint())) + writeString(e, strconv.Uitoa64(v.Uint())) case reflect.Float32, reflect.Float64: - e.WriteString(strconv.FtoaN(v.Float(), 'g', -1, v.Type().Bits())) + writeString(e, strconv.FtoaN(v.Float(), 'g', -1, v.Type().Bits())) case reflect.String: - e.string(v.String()) + if quoted { + sb, err := Marshal(v.String()) + if err != nil { + e.error(err) + } + e.string(string(sb)) + } else { + e.string(v.String()) + } case reflect.Struct: e.WriteByte('{') @@ -269,17 +296,14 @@ func (e *encodeState) reflectValue(v reflect.Value) { if f.PkgPath != "" { continue } - tag, omitEmpty := f.Name, false + tag, omitEmpty, quoted := f.Name, false, 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" + name, opts := parseTag(tv) + if isValidTag(name) { + tag = name } + omitEmpty = opts.Contains("omitempty") + quoted = opts.Contains("string") } fieldValue := v.Field(i) if omitEmpty && isEmptyValue(fieldValue) { @@ -292,7 +316,7 @@ func (e *encodeState) reflectValue(v reflect.Value) { } e.string(tag) e.WriteByte(':') - e.reflectValue(fieldValue) + e.reflectValueQuoted(fieldValue, quoted) } e.WriteByte('}') @@ -380,7 +404,8 @@ func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) } func (sv stringValues) get(i int) string { return sv[i].String() } -func (e *encodeState) string(s string) { +func (e *encodeState) string(s string) (int, os.Error) { + len0 := e.Len() e.WriteByte('"') start := 0 for i := 0; i < len(s); { @@ -425,4 +450,5 @@ func (e *encodeState) string(s string) { e.WriteString(s[start:]) } e.WriteByte('"') + return e.Len() - len0, nil } diff --git a/src/pkg/json/encode_test.go b/src/pkg/json/encode_test.go index 0e4b63770..012e9f143 100644 --- a/src/pkg/json/encode_test.go +++ b/src/pkg/json/encode_test.go @@ -5,6 +5,8 @@ package json import ( + "bytes" + "reflect" "testing" ) @@ -42,3 +44,39 @@ func TestOmitEmpty(t *testing.T) { t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) } } + +type StringTag struct { + BoolStr bool `json:",string"` + IntStr int64 `json:",string"` + StrStr string `json:",string"` +} + +var stringTagExpected = `{ + "BoolStr": "true", + "IntStr": "42", + "StrStr": "\"xzbit\"" +}` + +func TestStringTag(t *testing.T) { + var s StringTag + s.BoolStr = true + s.IntStr = 42 + s.StrStr = "xzbit" + got, err := MarshalIndent(&s, "", " ") + if err != nil { + t.Fatal(err) + } + if got := string(got); got != stringTagExpected { + t.Fatalf(" got: %s\nwant: %s\n", got, stringTagExpected) + } + + // Verify that it round-trips. + var s2 StringTag + err = NewDecoder(bytes.NewBuffer(got)).Decode(&s2) + if err != nil { + t.Fatalf("Decode: %v", err) + } + if !reflect.DeepEqual(s, s2) { + t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", s, string(got), s2) + } +} diff --git a/src/pkg/json/tags.go b/src/pkg/json/tags.go new file mode 100644 index 000000000..58cda2027 --- /dev/null +++ b/src/pkg/json/tags.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 ( + "strings" +) + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// Contains returns whether checks that a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/src/pkg/json/tags_test.go b/src/pkg/json/tags_test.go new file mode 100644 index 000000000..91fb18831 --- /dev/null +++ b/src/pkg/json/tags_test.go @@ -0,0 +1,28 @@ +// 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" +) + +func TestTagParsing(t *testing.T) { + name, opts := parseTag("field,foobar,foo") + if name != "field" { + t.Fatalf("name = %q, want field", name) + } + for _, tt := range []struct { + opt string + want bool + }{ + {"foobar", true}, + {"foo", true}, + {"bar", false}, + } { + if opts.Contains(tt.opt) != tt.want { + t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) + } + } +} diff --git a/src/pkg/reflect/type.go b/src/pkg/reflect/type.go index 4c377e1fe..ef0fb87ea 100644 --- a/src/pkg/reflect/type.go +++ b/src/pkg/reflect/type.go @@ -10,6 +10,9 @@ // A call to ValueOf returns a Value representing the run-time data. // Zero takes a Type and returns a Value representing a zero value // for that type. +// +// See "The Laws of Reflection" for an introduction to reflection in Go: +// http://blog.golang.org/2011/09/laws-of-reflection.html package reflect import ( -- cgit v1.2.3