diff options
| author | Andrew Gerrand <adg@golang.org> | 2013-08-29 14:39:55 +1000 |
|---|---|---|
| committer | Andrew Gerrand <adg@golang.org> | 2013-08-29 14:39:55 +1000 |
| commit | 466001d05d366cbc97edfb65dc6f5cb883df0498 (patch) | |
| tree | 351bdaf01408cec52d9189a803c4ee5992eb4c6b /src/pkg/encoding/json/encode.go | |
| parent | 916937274938adf506040b1118e1e20f990cf2b2 (diff) | |
| download | go-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.go | 65 |
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. |
