aboutsummaryrefslogtreecommitdiff
path: root/src/pkg/encoding/json/encode.go
diff options
context:
space:
mode:
authorAndrew Gerrand <adg@golang.org>2013-08-29 14:39:55 +1000
committerAndrew Gerrand <adg@golang.org>2013-08-29 14:39:55 +1000
commit466001d05d366cbc97edfb65dc6f5cb883df0498 (patch)
tree351bdaf01408cec52d9189a803c4ee5992eb4c6b /src/pkg/encoding/json/encode.go
parent916937274938adf506040b1118e1e20f990cf2b2 (diff)
downloadgo-466001d05d366cbc97edfb65dc6f5cb883df0498.tar.xz
encoding/json: add "overflow" struct tag option
Fixes #6213. R=golang-dev, dsymonds, bradfitz CC=golang-dev https://golang.org/cl/13180043
Diffstat (limited to 'src/pkg/encoding/json/encode.go')
-rw-r--r--src/pkg/encoding/json/encode.go65
1 files changed, 59 insertions, 6 deletions
diff --git a/src/pkg/encoding/json/encode.go b/src/pkg/encoding/json/encode.go
index f951250e98..590010f3b2 100644
--- a/src/pkg/encoding/json/encode.go
+++ b/src/pkg/encoding/json/encode.go
@@ -109,6 +109,10 @@ import (
// an anonymous struct field in both current and earlier versions, give the field
// a JSON tag of "-".
//
+// The "overflow" option may be used with a struct field of a map type to
+// indicate that the map contents should be marshalled as if the keys are part
+// of the struct object itself.
+//
// Map values encode as JSON objects.
// The map's key type must be string; the object keys are used directly
// as map keys.
@@ -239,6 +243,32 @@ var hex = "0123456789abcdef"
type encodeState struct {
bytes.Buffer // accumulated output
scratch [64]byte
+ overflow int
+}
+
+func (e *encodeState) startOverflow() {
+ e.overflow = e.Len()
+}
+
+func (e *encodeState) endOverflow() {
+ if e.overflow == 0 {
+ panic("endOverflow called before startOverflow")
+ }
+ start, end := e.overflow, e.Len()
+ b := e.Bytes()
+ if b[start] == '{' && b[end-1] == '}' {
+ // Remove surrounding { and }.
+ copy(b[start:], b[start+1:])
+ e.Truncate(end - 2)
+ } else if bytes.Equal(b[start:end], []byte("null")) {
+ // Drop "null".
+ e.Truncate(start)
+ }
+ // Remove trailing comma if overflow value was null or {}.
+ if start > 0 && e.Len() == start && b[start-1] == ',' {
+ e.Truncate(start - 1)
+ }
+ e.overflow = 0
}
// TODO(bradfitz): use a sync.Cache here
@@ -582,7 +612,7 @@ func (se *structEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
e.WriteByte('{')
first := true
for i, f := range se.fields {
- fv := fieldByIndex(v, f.index)
+ fv := fieldByIndex(v, f.index, false)
if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) {
continue
}
@@ -591,6 +621,16 @@ func (se *structEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
} else {
e.WriteByte(',')
}
+ if f.overflow {
+ if tenc := se.fieldEncs[i]; tenc != nil {
+ e.startOverflow()
+ tenc(e, fv, f.quoted)
+ e.endOverflow()
+ } else {
+ panic("no encoder for " + fv.String())
+ }
+ continue
+ }
e.string(f.name)
e.WriteByte(':')
if tenc := se.fieldEncs[i]; tenc != nil {
@@ -610,7 +650,7 @@ func newStructEncoder(t reflect.Type, vx reflect.Value) encoderFunc {
fieldEncs: make([]encoderFunc, len(fields)),
}
for i, f := range fields {
- vxf := fieldByIndex(vx, f.index)
+ vxf := fieldByIndex(vx, f.index, false)
if vxf.IsValid() {
se.fieldEncs[i] = typeEncoder(vxf.Type(), vxf)
}
@@ -750,11 +790,16 @@ func isValidTag(s string) bool {
return true
}
-func fieldByIndex(v reflect.Value, index []int) reflect.Value {
+// fieldByIndex fetches (and allocates, if create is true) the field in v
+// indentified by index.
+func fieldByIndex(v reflect.Value, index []int, create bool) reflect.Value {
for _, i := range index {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
- return reflect.Value{}
+ if !create {
+ return reflect.Value{}
+ }
+ v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
@@ -926,6 +971,7 @@ type field struct {
typ reflect.Type
omitEmpty bool
quoted bool
+ overflow bool
}
// byName sorts field by name, breaking ties with depth,
@@ -1027,8 +1073,15 @@ func typeFields(t reflect.Type) []field {
if name == "" {
name = sf.Name
}
- fields = append(fields, field{name, tagged, index, ft,
- opts.Contains("omitempty"), opts.Contains("string")})
+ fields = append(fields, field{
+ name: name,
+ tag: tagged,
+ index: index,
+ typ: ft,
+ omitEmpty: opts.Contains("omitempty"),
+ quoted: opts.Contains("string"),
+ overflow: opts.Contains("overflow")},
+ )
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.