diff options
Diffstat (limited to 'libgo/go/encoding/json/encode.go')
-rw-r--r-- | libgo/go/encoding/json/encode.go | 132 |
1 files changed, 79 insertions, 53 deletions
diff --git a/libgo/go/encoding/json/encode.go b/libgo/go/encoding/json/encode.go index 6bb6de8f079..8f21ddaed94 100644 --- a/libgo/go/encoding/json/encode.go +++ b/libgo/go/encoding/json/encode.go @@ -34,7 +34,7 @@ import ( // and is not a nil pointer, Marshal calls its MarshalJSON method // to produce JSON. If no MarshalJSON method is present but the // value implements encoding.TextMarshaler instead, Marshal calls -// its MarshalText method. +// its MarshalText method and encodes the result as a JSON string. // The nil pointer exception is not strictly necessary // but mimics a similar, necessary exception in the behavior of // UnmarshalJSON. @@ -50,25 +50,33 @@ import ( // The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" // to keep some browsers from misinterpreting JSON output as HTML. // Ampersand "&" is also escaped to "\u0026" for the same reason. -// This escaping can be disabled using an Encoder with DisableHTMLEscaping. +// This escaping can be disabled using an Encoder that had SetEscapeHTML(false) +// called on it. // // Array and slice values encode as JSON arrays, except that // []byte encodes as a base64-encoded string, and a nil slice // encodes as the null JSON value. // -// Struct values encode as JSON objects. Each exported struct field -// becomes a member of the object unless -// - the field's tag is "-", or -// - 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 -// the struct field's tag value is the key name, followed by an optional comma -// and options. Examples: +// Struct values encode as JSON objects. +// Each exported struct field becomes a member of the object, using the +// field name as the object key, unless the field is omitted for one of the +// reasons given below. // -// // Field is ignored by this package. -// Field int `json:"-"` +// The encoding of each struct field can be customized by the format string +// stored under the "json" key in the struct field's tag. +// The format string gives the name of the field, possibly followed by a +// comma-separated list of options. The name may be empty in order to +// specify options without overriding the default field name. +// +// The "omitempty" option specifies that the field should be omitted +// from the encoding if the field has an empty value, defined as +// false, 0, a nil pointer, a nil interface value, and any empty array, +// slice, map, or string. +// +// As a special case, if the field tag is "-", the field is always omitted. +// Note that a field with name "-" can still be generated using the tag "-,". +// +// Examples of struct field tags and their meanings: // // // Field appears in JSON as key "myName". // Field int `json:"myName"` @@ -83,6 +91,12 @@ import ( // // Note the leading comma. // Field int `json:",omitempty"` // +// // Field is ignored by this package. +// Field int `json:"-"` +// +// // Field appears in JSON as key "-". +// Field int `json:"-,"` +// // The "string" option signals that a field is stored as JSON inside a // JSON-encoded string. It applies only to fields of string, floating point, // integer, or boolean types. This extra level of encoding is sometimes used @@ -110,7 +124,9 @@ import ( // // 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, // even if there are multiple untagged fields that would otherwise conflict. +// // 2) If there is exactly one field (tagged or not according to the first rule), that is selected. +// // 3) Otherwise there are multiple fields, and all are ignored; no error occurs. // // Handling of anonymous struct fields is new in Go 1.1. @@ -427,7 +443,11 @@ func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { e.WriteString("null") return } - m := v.Interface().(Marshaler) + m, ok := v.Interface().(Marshaler) + if !ok { + e.WriteString("null") + return + } b, err := m.MarshalJSON() if err == nil { // copy JSON into buffer, checking validity. @@ -525,7 +545,31 @@ func (bits floatEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if math.IsInf(f, 0) || math.IsNaN(f) { e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))}) } - b := strconv.AppendFloat(e.scratch[:0], f, 'g', -1, int(bits)) + + // Convert as if by ES6 number to string conversion. + // This matches most other JSON generators. + // See golang.org/issue/6384 and golang.org/issue/14135. + // Like fmt %g, but the exponent cutoffs are different + // and exponents themselves are not padded to two digits. + b := e.scratch[:0] + abs := math.Abs(f) + fmt := byte('f') + // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. + if abs != 0 { + if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { + fmt = 'e' + } + } + b = strconv.AppendFloat(b, f, fmt, -1, int(bits)) + if fmt == 'e' { + // clean up e-09 to e-9 + n := len(b) + if n >= 4 && b[n-4] == 'e' && b[n-3] == '-' && b[n-2] == '0' { + b[n-2] = b[n-1] + b = b[:n-1] + } + } + if opts.quoted { e.WriteByte('"') } @@ -635,7 +679,7 @@ func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.error(&MarshalerError{v.Type(), err}) } } - sort.Sort(byString(sv)) + sort.Slice(sv, func(i, j int) bool { return sv[i].s < sv[j].s }) for i, kv := range sv { if i > 0 { @@ -834,15 +878,6 @@ func (w *reflectWithString) resolve() error { panic("unexpected map key type") } -// byString is a slice of reflectWithString where the reflect.Value is either -// a string or an encoding.TextMarshaler. -// It implements the methods to sort by string. -type byString []reflectWithString - -func (sv byString) Len() int { return len(sv) } -func (sv byString) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } -func (sv byString) Less(i, j int) bool { return sv[i].s < sv[j].s } - // NOTE: keep in sync with stringBytes below. func (e *encodeState) string(s string, escapeHTML bool) int { len0 := e.Len() @@ -850,8 +885,7 @@ func (e *encodeState) string(s string, escapeHTML bool) int { start := 0 for i := 0; i < len(s); { if b := s[i]; b < utf8.RuneSelf { - if 0x20 <= b && b != '\\' && b != '"' && - (!escapeHTML || b != '<' && b != '>' && b != '&') { + if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { i++ continue } @@ -928,8 +962,7 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int { start := 0 for i := 0; i < len(s); { if b := s[i]; b < utf8.RuneSelf { - if 0x20 <= b && b != '\\' && b != '"' && - (!escapeHTML || b != '<' && b != '>' && b != '&') { + if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { i++ continue } @@ -1018,28 +1051,6 @@ func fillField(f field) field { return f } -// byName sorts field by name, breaking ties with depth, -// then breaking ties with "name came from json tag", then -// breaking ties with index sequence. -type byName []field - -func (x byName) Len() int { return len(x) } - -func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } - -func (x byName) Less(i, j int) bool { - if x[i].name != x[j].name { - return x[i].name < x[j].name - } - if len(x[i].index) != len(x[j].index) { - return len(x[i].index) < len(x[j].index) - } - if x[i].tag != x[j].tag { - return x[i].tag - } - return byIndex(x).Less(i, j) -} - // byIndex sorts field by index sequence. type byIndex []field @@ -1157,7 +1168,22 @@ func typeFields(t reflect.Type) []field { } } - sort.Sort(byName(fields)) + sort.Slice(fields, func(i, j int) bool { + x := fields + // sort field by name, breaking ties with depth, then + // breaking ties with "name came from json tag", then + // breaking ties with index sequence. + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) + }) // Delete all fields that are hidden by the Go rules for embedded fields, // except that fields with JSON tags are promoted. |