', 'comma-separated list of benchmark names', (value) => {
18 | return value.split(',')
19 | })
20 |
21 | prog.version('0.2.0')
22 | prog.parse(process.argv)
23 |
24 | // createChart creates and exports a Google Bar Chart
25 | // as a PNG image, using the decamelized form of name
26 | // with '-' as separator. The parameter data must be a
27 | // two-dimensional array where each row represents a
28 | // bar in the chart.
29 | function createBarChart (dom, name, data, opts) {
30 | var head = dom.window.document.getElementsByTagName('head')[0]
31 | var func = head.insertBefore
32 |
33 | // Prevent call to Google Font API.
34 | head.insertBefore = function (el, ref) {
35 | if (el.href && el.href.indexOf('//fonts.googleapis.com/css?family=Input') > -1) {
36 | return
37 | }
38 | func.call(head, el, ref)
39 | }
40 | // Add an event listener to draw the chart once
41 | // the DOM is fully loaded.
42 | dom.window.addEventListener('DOMContentLoaded', function () {
43 | const g = dom.window.google
44 |
45 | // Load the Google Visualization
46 | // API and the corechart package.
47 | g.charts.load('45.2', {
48 | packages: ['corechart', 'bar']
49 | })
50 | g.charts.setOnLoadCallback(function () {
51 | drawBarChart(dom, name, data, opts)
52 | })
53 | })
54 | }
55 |
56 | // exportChartAsSVG exports the SVG of the chart to
57 | // the current working directory. The file name is a
58 | // decamelized version of name using the hyphen char
59 | // as separator, in lowercase.
60 | function exportChartAsSVG (e, name) {
61 | var filename = util.format('%s.svg',
62 | path.join(prog.directory, deca(name, '-'))
63 | )
64 | var svgEl = e.getElementsByTagName('svg')[0]
65 | var svg = htmlToElement(svgEl.outerHTML)
66 |
67 | svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
68 | svg.setAttribute('version', '1.1')
69 |
70 | const result = optimize(svg.outerHTML, {
71 | plugins: [{
72 | name: "sortAttrs"
73 | }, {
74 | name: "removeAttrs",
75 | params: {
76 | attrs: "(clip-path|aria-label|overflow)"
77 | }
78 | }],
79 | multipass: true
80 | })
81 | try {
82 | return fs.writeFileSync(filename, result.data)
83 | } catch (err) {
84 | console.error('cannot write svg chart %s: %s', filename, err)
85 | }
86 | }
87 |
88 | function htmlToElement (html) {
89 | const d = (new JSDOM('...')).window.document
90 | var t = d.createElement('template')
91 | t.innerHTML = html.trim()
92 | return t.content.firstChild
93 | }
94 |
95 | function drawBarChart (dom, name, data, opts) {
96 | const g = dom.window.google
97 | const d = dom.window.document
98 |
99 | // Convert the data 2D-array to a DataTable,
100 | // and sort it in alphabetical order using
101 | // the content of the first column which
102 | // contains the names of the benchmark runs.
103 | var dt = new g.visualization.arrayToDataTable(data)
104 | dt.sort([{
105 | column: 0
106 | }])
107 | var e = d.getElementById('chart')
108 | var c = new g.visualization.ColumnChart(e)
109 |
110 | // Setup a callback that exports the chart as
111 | // a PNG image when it has finished drawing.
112 | g.visualization.events.addListener(c, 'ready', function () {
113 | exportChartAsSVG(e, name)
114 | })
115 | c.draw(dt, opts)
116 | }
117 |
118 | // Load and parse the JSON-formatted benchmark
119 | // statistics from the input file.
120 | var file = fs.readFileSync(prog.input)
121 | var data = JSON.parse(file)
122 |
123 | // Iterate over all benchmarks and create the
124 | // chart only if it was requested through the
125 | // command-line parameters.
126 | Object.keys(data).forEach(function (key) {
127 | if (!prog.names.includes(key)) {
128 | return
129 | }
130 | const pageFile = path.join(__dirname, 'jsdom.html')
131 |
132 | JSDOM.fromFile(pageFile, {
133 | resources: 'usable',
134 | runScripts: 'dangerously',
135 | pretendToBeVisual: true
136 | }).then(dom => {
137 | createBarChart(dom, key, data[key], {
138 | width: 700,
139 | height: 400,
140 | chartArea: {
141 | left: 100,
142 | top: 50,
143 | width: '70%',
144 | height: '75%'
145 | },
146 | vAxis: {
147 | format: '',
148 | gridlines: {
149 | count: 5
150 | },
151 | minorGridlines: {
152 | count: 2
153 | }
154 | },
155 | hAxis: {
156 | textStyle: {
157 | bold: true,
158 | fontName: 'Input'
159 | }
160 | }
161 | })
162 | })
163 | })
164 |
--------------------------------------------------------------------------------
/types.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "encoding"
5 | "encoding/json"
6 | "reflect"
7 | "sync"
8 | "time"
9 | "unsafe"
10 | )
11 |
12 | var (
13 | timeTimeType = reflect.TypeOf(time.Time{})
14 | timeDurationType = reflect.TypeOf(time.Duration(0))
15 | syncMapType = reflect.TypeOf((*sync.Map)(nil)).Elem()
16 | jsonNumberType = reflect.TypeOf(json.Number(""))
17 | jsonRawMessageType = reflect.TypeOf(json.RawMessage(nil))
18 | jsonMarshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
19 | textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
20 | appendMarshalerType = reflect.TypeOf((*AppendMarshaler)(nil)).Elem()
21 | appendMarshalerCtxType = reflect.TypeOf((*AppendMarshalerCtx)(nil)).Elem()
22 | )
23 |
24 | var emptyFnCache sync.Map // map[reflect.Type]emptyFunc
25 |
26 | // emptyFunc is a function that returns whether a
27 | // value pointed by an unsafe.Pointer represents the
28 | // zero value of its type.
29 | type emptyFunc func(unsafe.Pointer) bool
30 |
31 | // marshalerEncodeFunc is a function that appends
32 | // the result of a marshaler method call to dst.
33 | type marshalerEncodeFunc func(interface{}, []byte, encOpts, reflect.Type) ([]byte, error)
34 |
35 | func isBasicType(t reflect.Type) bool {
36 | return isBoolean(t) || isString(t) || isFloatingPoint(t) || isInteger(t)
37 | }
38 |
39 | func isBoolean(t reflect.Type) bool { return t.Kind() == reflect.Bool }
40 | func isString(t reflect.Type) bool { return t.Kind() == reflect.String }
41 |
42 | func isFloatingPoint(t reflect.Type) bool {
43 | kind := t.Kind()
44 | if kind == reflect.Float32 || kind == reflect.Float64 {
45 | return true
46 | }
47 | return false
48 | }
49 |
50 | func isInteger(t reflect.Type) bool {
51 | switch t.Kind() {
52 | case reflect.Int,
53 | reflect.Int8,
54 | reflect.Int16,
55 | reflect.Int32,
56 | reflect.Int64,
57 | reflect.Uint,
58 | reflect.Uint8,
59 | reflect.Uint16,
60 | reflect.Uint32,
61 | reflect.Uint64,
62 | reflect.Uintptr:
63 | return true
64 | default:
65 | return false
66 | }
67 | }
68 |
69 | func isInlined(t reflect.Type) bool {
70 | switch t.Kind() {
71 | case reflect.Ptr, reflect.Map:
72 | return true
73 | case reflect.Struct:
74 | return t.NumField() == 1 && isInlined(t.Field(0).Type)
75 | default:
76 | return false
77 | }
78 | }
79 |
80 | func isNilable(t reflect.Type) bool {
81 | switch t.Kind() {
82 | case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map:
83 | return true
84 | }
85 | return false
86 | }
87 |
88 | // cachedEmptyFuncOf is similar to emptyFuncOf, but
89 | // returns a cached function, to avoid duplicates.
90 | func cachedEmptyFuncOf(t reflect.Type) emptyFunc {
91 | if fn, ok := emptyFnCache.Load(t); ok {
92 | return fn.(emptyFunc)
93 | }
94 | fn, _ := emptyFnCache.LoadOrStore(t, emptyFuncOf(t))
95 | return fn.(emptyFunc)
96 | }
97 |
98 | // emptyFuncOf returns a function that can be used to
99 | // determine if a value pointed by an unsafe,Pointer
100 | // represents the zero-value of type t.
101 | func emptyFuncOf(t reflect.Type) emptyFunc {
102 | switch t.Kind() {
103 | case reflect.Bool:
104 | return func(p unsafe.Pointer) bool {
105 | return !*(*bool)(p)
106 | }
107 | case reflect.String:
108 | return func(p unsafe.Pointer) bool {
109 | return (*stringHeader)(p).Len == 0
110 | }
111 | case reflect.Int:
112 | return func(p unsafe.Pointer) bool {
113 | return *(*int)(p) == 0
114 | }
115 | case reflect.Int8:
116 | return func(p unsafe.Pointer) bool {
117 | return *(*int8)(p) == 0
118 | }
119 | case reflect.Int16:
120 | return func(p unsafe.Pointer) bool {
121 | return *(*int16)(p) == 0
122 | }
123 | case reflect.Int32:
124 | return func(p unsafe.Pointer) bool {
125 | return *(*int32)(p) == 0
126 | }
127 | case reflect.Int64:
128 | return func(p unsafe.Pointer) bool {
129 | return *(*int64)(p) == 0
130 | }
131 | case reflect.Uint:
132 | return func(p unsafe.Pointer) bool {
133 | return *(*uint)(p) == 0
134 | }
135 | case reflect.Uint8:
136 | return func(p unsafe.Pointer) bool {
137 | return *(*uint8)(p) == 0
138 | }
139 | case reflect.Uint16:
140 | return func(p unsafe.Pointer) bool {
141 | return *(*uint16)(p) == 0
142 | }
143 | case reflect.Uint32:
144 | return func(p unsafe.Pointer) bool {
145 | return *(*uint32)(p) == 0
146 | }
147 | case reflect.Uint64:
148 | return func(p unsafe.Pointer) bool {
149 | return *(*uint64)(p) == 0
150 | }
151 | case reflect.Uintptr:
152 | return func(p unsafe.Pointer) bool {
153 | return *(*uintptr)(p) == 0
154 | }
155 | case reflect.Float32:
156 | return func(p unsafe.Pointer) bool {
157 | return *(*float32)(p) == 0
158 | }
159 | case reflect.Float64:
160 | return func(p unsafe.Pointer) bool {
161 | return *(*float64)(p) == 0
162 | }
163 | case reflect.Map:
164 | return func(p unsafe.Pointer) bool {
165 | return maplen(*(*unsafe.Pointer)(p)) == 0
166 | }
167 | case reflect.Ptr:
168 | return func(p unsafe.Pointer) bool {
169 | return *(*unsafe.Pointer)(p) == nil
170 | }
171 | case reflect.Interface:
172 | return func(p unsafe.Pointer) bool {
173 | return *(*unsafe.Pointer)(p) == nil
174 | }
175 | case reflect.Slice:
176 | return func(p unsafe.Pointer) bool {
177 | return (*sliceHeader)(p).Len == 0
178 | }
179 | case reflect.Array:
180 | if t.Len() == 0 {
181 | return func(unsafe.Pointer) bool { return true }
182 | }
183 | }
184 | return func(unsafe.Pointer) bool { return false }
185 | }
186 |
--------------------------------------------------------------------------------
/json.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "reflect"
7 | "runtime"
8 | )
9 |
10 | // AppendMarshaler is a variant of the json.Marshaler
11 | // interface, implemented by types that can append a
12 | // valid and compact JSON representation of themselves
13 | // to a buffer. If a type implements both interfaces,
14 | // this one will be used in priority by the package.
15 | type AppendMarshaler interface {
16 | AppendJSON([]byte) ([]byte, error)
17 | }
18 |
19 | // AppendMarshalerCtx is similar to AppendMarshaler,
20 | // but the method implemented also takes a context.
21 | // The use case for this interface is to dynamically
22 | // control the marshaling of the type implementing it
23 | // through the values encapsulated by the context,
24 | // that may be provided at runtime using WithContext.
25 | type AppendMarshalerCtx interface {
26 | AppendJSONContext(context.Context, []byte) ([]byte, error)
27 | }
28 |
29 | const (
30 | marshalerJSON = "MarshalJSON"
31 | marshalerText = "MarshalText"
32 | marshalerAppendJSONCtx = "AppendJSONContext"
33 | marshalerAppendJSON = "AppendJSON"
34 | )
35 |
36 | // MarshalerError represents an error from calling
37 | // the methods MarshalJSON or MarshalText.
38 | type MarshalerError struct {
39 | Type reflect.Type
40 | Err error
41 | funcName string
42 | }
43 |
44 | // Error implements the builtin error interface.
45 | func (e *MarshalerError) Error() string {
46 | return fmt.Sprintf("json: error calling %s for type %s: %s",
47 | e.funcName, e.Type, e.Err.Error())
48 | }
49 |
50 | // Unwrap returns the error wrapped by e.
51 | // This doesn't implement a public interface, but
52 | // allow to use the errors.Unwrap function released
53 | // in Go1.13 with a MarshalerError.
54 | func (e *MarshalerError) Unwrap() error {
55 | return e.Err
56 | }
57 |
58 | // UnsupportedTypeError is the error returned
59 | // by Marshal when attempting to encode an
60 | // unsupported value type.
61 | type UnsupportedTypeError struct {
62 | Type reflect.Type
63 | }
64 |
65 | // Error implements the bultin error interface.
66 | func (e *UnsupportedTypeError) Error() string {
67 | return fmt.Sprintf("json: unsupported type: %s", e.Type)
68 | }
69 |
70 | // UnsupportedValueError is the error returned
71 | // by Marshal when attempting to encode an
72 | // unsupported value.
73 | type UnsupportedValueError struct {
74 | Value reflect.Value
75 | Str string
76 | }
77 |
78 | // Error implements the builtin error interface.
79 | func (e *UnsupportedValueError) Error() string {
80 | return fmt.Sprintf("json: unsupported value: %s", e.Str)
81 | }
82 |
83 | // A SyntaxError is a description of a JSON syntax error.
84 | // Unlike its equivalent in the encoding/json package, the
85 | // Error method implemented does not return a meaningful
86 | // message, and the Offset field is always zero.
87 | // It is present merely for consistency.
88 | type SyntaxError struct {
89 | msg string
90 | Offset int64
91 | }
92 |
93 | // Error implements the builtin error interface.
94 | func (e *SyntaxError) Error() string { return e.msg }
95 |
96 | // InvalidOptionError is the error returned by
97 | // MarshalOpts when one of the given options is
98 | // invalid.
99 | type InvalidOptionError struct {
100 | Err error
101 | }
102 |
103 | // Error implements the builtin error interface.
104 | func (e *InvalidOptionError) Error() string {
105 | return fmt.Sprintf("json: invalid option: %s", e.Err.Error())
106 | }
107 |
108 | // Marshal returns the JSON encoding of v.
109 | // The full documentation can be found at
110 | // https://golang.org/pkg/encoding/json/#Marshal.
111 | func Marshal(v interface{}) ([]byte, error) {
112 | if v == nil {
113 | return []byte("null"), nil
114 | }
115 | return marshalJSON(v, defaultEncOpts())
116 | }
117 |
118 | // Append is similar to Marshal but appends the JSON
119 | // representation of v to dst instead of returning a
120 | // new allocated slice.
121 | func Append(dst []byte, v interface{}) ([]byte, error) {
122 | if v == nil {
123 | return append(dst, "null"...), nil
124 | }
125 | return appendJSON(dst, v, defaultEncOpts())
126 | }
127 |
128 | // MarshalOpts is similar to Marshal, but also accepts
129 | // a list of options to configure the encoding behavior.
130 | func MarshalOpts(v interface{}, opts ...Option) ([]byte, error) {
131 | if v == nil {
132 | return []byte("null"), nil
133 | }
134 | eo := defaultEncOpts()
135 |
136 | if len(opts) != 0 {
137 | (&eo).apply(opts...)
138 | if err := eo.validate(); err != nil {
139 | return nil, &InvalidOptionError{err}
140 | }
141 | }
142 | return marshalJSON(v, eo)
143 | }
144 |
145 | // AppendOpts is similar to Append, but also accepts
146 | // a list of options to configure the encoding behavior.
147 | func AppendOpts(dst []byte, v interface{}, opts ...Option) ([]byte, error) {
148 | if v == nil {
149 | return append(dst, "null"...), nil
150 | }
151 | eo := defaultEncOpts()
152 |
153 | if len(opts) != 0 {
154 | (&eo).apply(opts...)
155 | if err := eo.validate(); err != nil {
156 | return nil, &InvalidOptionError{err}
157 | }
158 | }
159 | return appendJSON(dst, v, eo)
160 | }
161 |
162 | func marshalJSON(v interface{}, opts encOpts) ([]byte, error) {
163 | ins := cachedInstr(reflect.TypeOf(v))
164 | buf := cachedBuffer()
165 |
166 | var err error
167 | buf.B, err = ins(unpackEface(v).word, buf.B, opts)
168 |
169 | // Ensure that v is reachable until
170 | // the instruction has returned.
171 | runtime.KeepAlive(v)
172 |
173 | var b []byte
174 | if err == nil {
175 | // Make a copy of the buffer's content
176 | // before its returned to the pool.
177 | b = make([]byte, len(buf.B))
178 | copy(b, buf.B)
179 | }
180 | bufferPool.Put(buf)
181 |
182 | return b, err
183 | }
184 |
185 | func appendJSON(dst []byte, v interface{}, opts encOpts) ([]byte, error) {
186 | ins := cachedInstr(reflect.TypeOf(v))
187 | var err error
188 | dst, err = ins(unpackEface(v).word, dst, opts)
189 | runtime.KeepAlive(v)
190 |
191 | return dst, err
192 | }
193 |
--------------------------------------------------------------------------------
/time.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import "time"
4 |
5 | const epoch = 62135683200 // 1970-01-01T00:00:00
6 |
7 | // DurationFmt represents the format used
8 | // to encode a time.Duration value.
9 | type DurationFmt int
10 |
11 | // DurationFmt constants.
12 | const (
13 | DurationString DurationFmt = iota
14 | DurationMinutes
15 | DurationSeconds
16 | DurationMilliseconds
17 | DurationMicroseconds
18 | DurationNanoseconds // default
19 | )
20 |
21 | // String implements the fmt.Stringer
22 | // interface for DurationFmt.
23 | func (f DurationFmt) String() string {
24 | if !f.valid() {
25 | return "unknown"
26 | }
27 | return durationFmtStr[f]
28 | }
29 |
30 | func (f DurationFmt) valid() bool {
31 | return f >= DurationString && f <= DurationNanoseconds
32 | }
33 |
34 | var (
35 | zeroDuration = []byte("0s")
36 | durationFmtStr = []string{"str", "min", "s", "ms", "μs", "nanosecond"}
37 | dayOffset = [13]uint16{0, 306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275}
38 | )
39 |
40 | // appendDuration appends the textual representation
41 | // of d to the tail of dst and returns the extended buffer.
42 | // Adapted from https://golang.org/src/time/time.go.
43 | func appendDuration(dst []byte, d time.Duration) []byte {
44 | var buf [32]byte
45 |
46 | l := len(buf)
47 | u := uint64(d)
48 | n := d < 0
49 | if n {
50 | u = -u
51 | }
52 | if u < uint64(time.Second) {
53 | // Special case: if duration is smaller than
54 | // a second, use smaller units, like 1.2ms
55 | var prec int
56 | l--
57 | buf[l] = 's'
58 | l--
59 | switch {
60 | case u == 0:
61 | return append(dst, zeroDuration...)
62 | case u < uint64(time.Microsecond):
63 | prec = 0
64 | buf[l] = 'n'
65 | case u < uint64(time.Millisecond):
66 | prec = 3
67 | // U+00B5 'µ' micro sign is 0xC2 0xB5.
68 | // Need room for two bytes.
69 | l--
70 | copy(buf[l:], "µ")
71 | default: // Format as milliseconds.
72 | prec = 6
73 | buf[l] = 'm'
74 | }
75 | l, u = fmtFrac(buf[:l], u, prec)
76 | l = fmtInt(buf[:l], u)
77 | } else {
78 | l--
79 | buf[l] = 's'
80 |
81 | l, u = fmtFrac(buf[:l], u, 9)
82 |
83 | // Format as seconds.
84 | l = fmtInt(buf[:l], u%60)
85 | u /= 60
86 |
87 | // Format as minutes.
88 | if u > 0 {
89 | l--
90 | buf[l] = 'm'
91 | l = fmtInt(buf[:l], u%60)
92 | u /= 60
93 |
94 | // Format as hours. Stop there, because
95 | // days can be different lengths.
96 | if u > 0 {
97 | l--
98 | buf[l] = 'h'
99 | l = fmtInt(buf[:l], u)
100 | }
101 | }
102 | }
103 | if n {
104 | l--
105 | buf[l] = '-'
106 | }
107 | return append(dst, buf[l:]...)
108 | }
109 |
110 | // fmtInt formats v into the tail of buf.
111 | // It returns the index where the output begins.
112 | // Taken from https://golang.org/src/time/time.go.
113 | func fmtInt(buf []byte, v uint64) int {
114 | w := len(buf)
115 | if v == 0 {
116 | w--
117 | buf[w] = '0'
118 | } else {
119 | for v > 0 {
120 | w--
121 | buf[w] = byte(v%10) + '0'
122 | v /= 10
123 | }
124 | }
125 | return w
126 | }
127 |
128 | // fmtFrac formats the fraction of v/10**prec (e.g., ".12345")
129 | // into the tail of buf, omitting trailing zeros. It omits the
130 | // decimal point too when the fraction is 0. It returns the
131 | // index where the output bytes begin and the value v/10**prec.
132 | // Taken from https://golang.org/src/time/time.go.
133 | func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
134 | // Omit trailing zeros up to and including decimal point.
135 | w := len(buf)
136 | print := false
137 | for i := 0; i < prec; i++ {
138 | digit := v % 10
139 | print = print || digit != 0
140 | if print {
141 | w--
142 | buf[w] = byte(digit) + '0'
143 | }
144 | v /= 10
145 | }
146 | if print {
147 | w--
148 | buf[w] = '.'
149 | }
150 | return w, v
151 | }
152 |
153 | func rdnToYmd(rdn uint32) (uint16, uint16, uint16) {
154 | // Rata Die algorithm by Peter Baum.
155 | var (
156 | Z = rdn + 306
157 | H = 100*Z - 25
158 | A = H / 3652425
159 | B = A - (A >> 2)
160 | y = (100*B + H) / 36525
161 | d = B + Z - (1461 * y >> 2)
162 | m = (535*d + 48950) >> 14
163 | )
164 | if m > 12 {
165 | y++
166 | m -= 12
167 | }
168 | return uint16(y), uint16(m), uint16(d) - dayOffset[m]
169 | }
170 |
171 | // appendRFC3339Time appends the RFC3339 textual representation
172 | // of t to the tail of dst and returns the extended buffer.
173 | // Adapted from https://github.com/chansen/c-timestamp.
174 | func appendRFC3339Time(t time.Time, dst []byte, nano bool) []byte {
175 | var buf [37]byte
176 |
177 | // Base layout chars with opening quote.
178 | buf[0], buf[5], buf[8], buf[11], buf[14], buf[17] = '"', '-', '-', 'T', ':', ':'
179 |
180 | // Year.
181 | _, offset := t.Zone()
182 | sec := t.Unix() + int64(offset) + epoch
183 | y, m, d := rdnToYmd(uint32(sec / 86400))
184 | for i := 4; i >= 1; i-- {
185 | buf[i] = byte(y%10) + '0'
186 | y /= 10
187 | }
188 | buf[7], m = byte(m%10)+'0', m/10 // month
189 | buf[6] = byte(m%10) + '0'
190 |
191 | buf[10], d = byte(d%10)+'0', d/10 // day
192 | buf[9] = byte(d%10) + '0'
193 |
194 | // Hours/minutes/seconds.
195 | s := sec % 86400
196 | buf[19], s = byte(s%10)+'0', s/10
197 | buf[18], s = byte(s%06)+'0', s/6
198 | buf[16], s = byte(s%10)+'0', s/10
199 | buf[15], s = byte(s%06)+'0', s/6
200 | buf[13], s = byte(s%10)+'0', s/10
201 | buf[12], _ = byte(s%10)+'0', 0
202 |
203 | n := 20
204 |
205 | // Fractional second precision.
206 | nsec := t.Nanosecond()
207 | if nano && nsec != 0 {
208 | buf[n] = '.'
209 | u := nsec
210 | for i := 9; i >= 1; i-- {
211 | buf[n+i] = byte(u%10) + '0'
212 | u /= 10
213 | }
214 | // Remove trailing zeros.
215 | var rpad int
216 | for i := 9; i >= 1; i-- {
217 | if buf[n+i] == '0' {
218 | rpad++
219 | } else {
220 | break
221 | }
222 | }
223 | n += 10 - rpad
224 | }
225 | // Zone.
226 | if offset == 0 {
227 | buf[n] = 'Z'
228 | n++
229 | } else {
230 | var z int
231 | zone := offset / 60 // convert to minutes
232 | if zone < 0 {
233 | buf[n] = '-'
234 | z = -zone
235 | } else {
236 | buf[n] = '+'
237 | z = zone
238 | }
239 | buf[n+3] = ':'
240 | buf[n+5], z = byte(z%10)+'0', z/10
241 | buf[n+4], z = byte(z%06)+'0', z/6
242 | buf[n+2], z = byte(z%10)+'0', z/10
243 | buf[n+1], _ = byte(z%10)+'0', 0
244 | n += 6
245 | }
246 | // Finally, add the closing quote.
247 | // It's position depends on the presence
248 | // of the fractional seconds and/or the
249 | // timezone offset.
250 | buf[n] = '"'
251 |
252 | return append(dst, buf[:n+1]...)
253 | }
254 |
--------------------------------------------------------------------------------
/bench_test.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "context"
7 | "encoding/json"
8 | "io/ioutil"
9 | "os"
10 | "strconv"
11 | "sync"
12 | "testing"
13 | "time"
14 |
15 | jsoniter "github.com/json-iterator/go"
16 | segmentj "github.com/segmentio/encoding/json"
17 | )
18 |
19 | var jsoniterCfg = jsoniter.ConfigCompatibleWithStandardLibrary
20 |
21 | type marshalFunc func(interface{}) ([]byte, error)
22 |
23 | type codeResponse struct {
24 | Tree *codeNode `json:"tree"`
25 | Username string `json:"username"`
26 | }
27 |
28 | type codeNode struct {
29 | Name string `json:"name"`
30 | Kids []*codeNode `json:"kids"`
31 | CLWeight float64 `json:"cl_weight"`
32 | Touches int `json:"touches"`
33 | MinT int64 `json:"min_t"`
34 | MaxT int64 `json:"max_t"`
35 | MeanT int64 `json:"mean_t"`
36 | }
37 |
38 | type simplePayload struct {
39 | St int `json:"st"`
40 | Sid int `json:"sid"`
41 | Tt string `json:"tt"`
42 | Gr int `json:"gr"`
43 | UUID string `json:"uuid"`
44 | IP string `json:"ip"`
45 | Ua string `json:"ua"`
46 | Tz int `json:"tz"`
47 | V bool `json:"v"`
48 | }
49 |
50 | func BenchmarkSimple(b *testing.B) {
51 | sp := &simplePayload{
52 | St: 1,
53 | Sid: 2,
54 | Tt: "TestString",
55 | Gr: 4,
56 | UUID: "8f9a65eb-4807-4d57-b6e0-bda5d62f1429",
57 | IP: "127.0.0.1",
58 | Ua: "Mozilla",
59 | Tz: 8,
60 | V: true,
61 | }
62 | benchMarshal(b, sp)
63 | }
64 |
65 | func BenchmarkComplex(b *testing.B) {
66 | benchMarshal(b, xx)
67 | }
68 |
69 | func BenchmarkCodeMarshal(b *testing.B) {
70 | // Taken from the encoding/json package.
71 | x := codeInit(b)
72 | benchMarshal(b, x)
73 | }
74 |
75 | func BenchmarkMap(b *testing.B) {
76 | m := map[string]int{
77 | "Cassianus": 1,
78 | "Ludovicus": 42,
79 | "Flavius": 8990,
80 | "Baldwin": 345,
81 | "Agapios": -43,
82 | "Liberia": 0,
83 | }
84 | benchMarshal(b, m)
85 | benchMarshalOpts(b, "jettison-nosort", m, UnsortedMap())
86 | }
87 |
88 | func BenchmarkSyncMap(b *testing.B) {
89 | if testing.Short() {
90 | b.SkipNow()
91 | }
92 | var sm sync.Map
93 |
94 | sm.Store("a", "foobar")
95 | sm.Store("b", 42)
96 | sm.Store("c", false)
97 | sm.Store("d", float64(3.14159))
98 |
99 | benchMarshalOpts(b, "sorted", m)
100 | benchMarshalOpts(b, "unsorted", m, UnsortedMap())
101 | }
102 |
103 | func BenchmarkDuration(b *testing.B) {
104 | if testing.Short() {
105 | b.SkipNow()
106 | }
107 | d := 1337 * time.Second
108 | benchMarshal(b, d)
109 | }
110 |
111 | func BenchmarkDurationFormat(b *testing.B) {
112 | if testing.Short() {
113 | b.SkipNow()
114 | }
115 | d := 32*time.Hour + 56*time.Minute + 25*time.Second
116 | for _, f := range []DurationFmt{
117 | DurationString,
118 | DurationMinutes,
119 | DurationSeconds,
120 | DurationMicroseconds,
121 | DurationMilliseconds,
122 | DurationNanoseconds,
123 | } {
124 | benchMarshalOpts(b, f.String(), d, DurationFormat(f))
125 | }
126 | }
127 |
128 | func BenchmarkTime(b *testing.B) {
129 | if testing.Short() {
130 | b.SkipNow()
131 | }
132 | t := time.Now()
133 | benchMarshal(b, t)
134 | }
135 |
136 | func BenchmarkStringEscaping(b *testing.B) {
137 | if testing.Short() {
138 | b.SkipNow()
139 | }
140 | s := "<ŁØŘ€M ƗƤŞỮM ĐØŁØŘ ŞƗŦ ΔM€Ŧ>"
141 |
142 | benchMarshalOpts(b, "Full", s)
143 | benchMarshalOpts(b, "NoUTF8Coercion", s, NoUTF8Coercion())
144 | benchMarshalOpts(b, "NoHTMLEscaping", s, NoHTMLEscaping())
145 | benchMarshalOpts(b, "NoUTF8Coercion/NoHTMLEscaping", s, NoUTF8Coercion(), NoHTMLEscaping())
146 | benchMarshalOpts(b, "NoStringEscaping", s, NoStringEscaping())
147 | }
148 |
149 | type (
150 | jsonbm struct{}
151 | textbm struct{}
152 | jetibm struct{}
153 | jetictxbm struct{}
154 | )
155 |
156 | var (
157 | loreumipsum = "Lorem ipsum dolor sit amet"
158 | loreumipsumQ = strconv.Quote(loreumipsum)
159 | )
160 |
161 | func (jsonbm) MarshalJSON() ([]byte, error) { return []byte(loreumipsumQ), nil }
162 | func (textbm) MarshalText() ([]byte, error) { return []byte(loreumipsum), nil }
163 | func (jetibm) AppendJSON(dst []byte) ([]byte, error) { return append(dst, loreumipsum...), nil }
164 |
165 | func (jetictxbm) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
166 | return append(dst, loreumipsum...), nil
167 | }
168 |
169 | func BenchmarkMarshaler(b *testing.B) {
170 | if testing.Short() {
171 | b.SkipNow()
172 | }
173 | for _, bb := range []struct {
174 | name string
175 | impl interface{}
176 | opts []Option
177 | }{
178 | {"json", jsonbm{}, nil},
179 | {"text", textbm{}, nil},
180 | {"append", jetibm{}, nil},
181 | {"appendctx", jetictxbm{}, []Option{WithContext(context.Background())}},
182 | } {
183 | benchMarshalOpts(b, bb.name, bb.impl, bb.opts...)
184 | }
185 | }
186 |
187 | func codeInit(b *testing.B) *codeResponse {
188 | f, err := os.Open("testdata/code.json.gz")
189 | if err != nil {
190 | b.Fatal(err)
191 | }
192 | defer f.Close()
193 | gz, err := gzip.NewReader(f)
194 | if err != nil {
195 | b.Fatal(err)
196 | }
197 | data, err := ioutil.ReadAll(gz)
198 | if err != nil {
199 | b.Fatal(err)
200 | }
201 | codeJSON := data
202 |
203 | var resp codeResponse
204 | if err := json.Unmarshal(codeJSON, &resp); err != nil {
205 | b.Fatalf("unmarshal code.json: %s", err)
206 | }
207 | if data, err = Marshal(&resp); err != nil {
208 | b.Fatalf("marshal code.json: %s", err)
209 | }
210 | if !bytes.Equal(data, codeJSON) {
211 | b.Logf("different lengths: %d - %d", len(data), len(codeJSON))
212 |
213 | for i := 0; i < len(data) && i < len(codeJSON); i++ {
214 | if data[i] != codeJSON[i] {
215 | b.Logf("re-marshal: changed at byte %d", i)
216 | b.Logf("old: %s", string(codeJSON[i-10:i+10]))
217 | b.Logf("new: %s", string(data[i-10:i+10]))
218 | break
219 | }
220 | }
221 | b.Fatal("re-marshal code.json: different result")
222 | }
223 | return &resp
224 | }
225 |
226 | func benchMarshalOpts(b *testing.B, name string, x interface{}, opts ...Option) {
227 | b.Run(name, func(b *testing.B) {
228 | b.ReportAllocs()
229 | for i := 0; i < b.N; i++ {
230 | bts, err := MarshalOpts(x, opts...)
231 | if err != nil {
232 | b.Fatal(err)
233 | }
234 | b.SetBytes(int64(len(bts)))
235 | }
236 | })
237 | }
238 |
239 | func benchMarshal(b *testing.B, x interface{}) {
240 | for _, bb := range []struct {
241 | name string
242 | fn marshalFunc
243 | }{
244 | {"standard", json.Marshal},
245 | {"jsoniter", jsoniterCfg.Marshal},
246 | {"segmentj", segmentj.Marshal},
247 | {"jettison", Marshal},
248 | } {
249 | bb := bb
250 | b.Run(bb.name, func(b *testing.B) {
251 | b.ReportAllocs()
252 | for i := 0; i < b.N; i++ {
253 | bts, err := bb.fn(x)
254 | if err != nil {
255 | b.Error(err)
256 | }
257 | b.SetBytes(int64(len(bts)))
258 | }
259 | })
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/options.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 | )
8 |
9 | // defaultTimeLayout is the default layout used
10 | // to format time.Time values. This is compliant
11 | // with the ECMA specification and the JavaScript
12 | // Date's toJSON method implementation.
13 | const defaultTimeLayout = time.RFC3339Nano
14 |
15 | // defaultDurationFmt is the default format used
16 | // to encode time.Duration values.
17 | const defaultDurationFmt = DurationNanoseconds
18 |
19 | // An Option overrides the default encoding
20 | // behavior of the MarshalOpts function.
21 | type Option func(*encOpts)
22 |
23 | type bitmask uint64
24 |
25 | func (b *bitmask) set(f bitmask) { *b |= f }
26 | func (b bitmask) has(f bitmask) bool { return b&f != 0 }
27 |
28 | const (
29 | unixTime bitmask = 1 << iota
30 | unsortedMap
31 | rawByteSlice
32 | byteArrayAsString
33 | nilMapEmpty
34 | nilSliceEmpty
35 | noStringEscaping
36 | noHTMLEscaping
37 | noUTF8Coercion
38 | noCompact
39 | noNumberValidation
40 | )
41 |
42 | type encOpts struct {
43 | ctx context.Context
44 | timeLayout string
45 | durationFmt DurationFmt
46 | flags bitmask
47 | allowList stringSet
48 | denyList stringSet
49 | }
50 |
51 | func defaultEncOpts() encOpts {
52 | return encOpts{
53 | ctx: context.TODO(),
54 | timeLayout: defaultTimeLayout,
55 | durationFmt: defaultDurationFmt,
56 | }
57 | }
58 |
59 | func (eo *encOpts) apply(opts ...Option) {
60 | for _, opt := range opts {
61 | if opt != nil {
62 | opt(eo)
63 | }
64 | }
65 | }
66 |
67 | func (eo encOpts) validate() error {
68 | switch {
69 | case eo.ctx == nil:
70 | return fmt.Errorf("nil context")
71 | case eo.timeLayout == "":
72 | return fmt.Errorf("empty time layout")
73 | case !eo.durationFmt.valid():
74 | return fmt.Errorf("unknown duration format")
75 | default:
76 | return nil
77 | }
78 | }
79 |
80 | // isDeniedField returns whether a struct field
81 | // identified by its name must be skipped during
82 | // the encoding of a struct.
83 | func (eo encOpts) isDeniedField(name string) bool {
84 | // The deny-list has precedence and must
85 | // be checked first if it has entries.
86 | if eo.denyList != nil {
87 | if _, ok := eo.denyList[name]; ok {
88 | return true
89 | }
90 | }
91 | if eo.allowList != nil {
92 | if _, ok := eo.allowList[name]; !ok {
93 | return true
94 | }
95 | }
96 | return false
97 | }
98 |
99 | type stringSet map[string]struct{}
100 |
101 | func fieldListToSet(list []string) stringSet {
102 | m := make(stringSet)
103 | for _, f := range list {
104 | m[f] = struct{}{}
105 | }
106 | return m
107 | }
108 |
109 | // UnixTime configures an encoder to encode
110 | // time.Time values as Unix timestamps. This
111 | // option, when used, has precedence over any
112 | // time layout confiured.
113 | func UnixTime() Option {
114 | return func(o *encOpts) { o.flags.set(unixTime) }
115 | }
116 |
117 | // UnsortedMap configures an encoder to skip
118 | // the sort of map keys.
119 | func UnsortedMap() Option {
120 | return func(o *encOpts) { o.flags.set(unsortedMap) }
121 | }
122 |
123 | // RawByteSlice configures an encoder to
124 | // encode byte slices as raw JSON strings,
125 | // rather than bas64-encoded strings.
126 | func RawByteSlice() Option {
127 | return func(o *encOpts) { o.flags.set(rawByteSlice) }
128 | }
129 |
130 | // ByteArrayAsString configures an encoder
131 | // to encode byte arrays as raw JSON strings.
132 | func ByteArrayAsString() Option {
133 | return func(o *encOpts) { o.flags.set(byteArrayAsString) }
134 | }
135 |
136 | // NilMapEmpty configures an encoder to
137 | // encode nil Go maps as empty JSON objects,
138 | // rather than null.
139 | func NilMapEmpty() Option {
140 | return func(o *encOpts) { o.flags.set(nilMapEmpty) }
141 | }
142 |
143 | // NilSliceEmpty configures an encoder to
144 | // encode nil Go slices as empty JSON arrays,
145 | // rather than null.
146 | func NilSliceEmpty() Option {
147 | return func(o *encOpts) { o.flags.set(nilSliceEmpty) }
148 | }
149 |
150 | // NoStringEscaping configures an encoder to
151 | // disable string escaping.
152 | func NoStringEscaping() Option {
153 | return func(o *encOpts) { o.flags.set(noStringEscaping) }
154 | }
155 |
156 | // NoHTMLEscaping configures an encoder to
157 | // disable the escaping of problematic HTML
158 | // characters in JSON strings.
159 | func NoHTMLEscaping() Option {
160 | return func(o *encOpts) { o.flags.set(noHTMLEscaping) }
161 | }
162 |
163 | // NoUTF8Coercion configures an encoder to
164 | // disable UTF8 coercion that replace invalid
165 | // bytes with the Unicode replacement rune.
166 | func NoUTF8Coercion() Option {
167 | return func(o *encOpts) { o.flags.set(noUTF8Coercion) }
168 | }
169 |
170 | // NoNumberValidation configures an encoder to
171 | // disable the validation of json.Number values.
172 | func NoNumberValidation() Option {
173 | return func(o *encOpts) { o.flags.set(noNumberValidation) }
174 | }
175 |
176 | // NoCompact configures an encoder to disable
177 | // the compaction of the JSON output produced
178 | // by a call to MarshalJSON, or the content of
179 | // a json.RawMessage.
180 | // see https://golang.org/pkg/encoding/json/#Compact
181 | func NoCompact() Option {
182 | return func(o *encOpts) { o.flags.set(noCompact) }
183 | }
184 |
185 | // TimeLayout sets the time layout used to encode
186 | // time.Time values. The layout must be compatible
187 | // with the Golang time package specification.
188 | func TimeLayout(layout string) Option {
189 | return func(o *encOpts) {
190 | o.timeLayout = layout
191 | }
192 | }
193 |
194 | // DurationFormat sets the format used to encode
195 | // time.Duration values.
196 | func DurationFormat(format DurationFmt) Option {
197 | return func(o *encOpts) {
198 | o.durationFmt = format
199 | }
200 | }
201 |
202 | // WithContext sets the context to use during
203 | // encoding. The context will be passed in to
204 | // the AppendJSONContext method of types that
205 | // implement the AppendMarshalerCtx interface.
206 | func WithContext(ctx context.Context) Option {
207 | return func(o *encOpts) {
208 | o.ctx = ctx
209 | }
210 | }
211 |
212 | // AllowList sets the list of fields which are to be
213 | // considered when encoding a struct.
214 | // The fields are identified by the name that is
215 | // used in the final JSON payload.
216 | // See DenyFields documentation for more information
217 | // regarding joint use with this option.
218 | func AllowList(fields []string) Option {
219 | m := fieldListToSet(fields)
220 | return func(o *encOpts) {
221 | o.allowList = m
222 | }
223 | }
224 |
225 | // DenyList is similar to AllowList, but conversely
226 | // sets the list of fields to omit during encoding.
227 | // When used in conjunction with AllowList, denied
228 | // fields have precedence over the allowed fields.
229 | func DenyList(fields []string) Option {
230 | m := fieldListToSet(fields)
231 | return func(o *encOpts) {
232 | o.denyList = m
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/struct.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "reflect"
7 | "sort"
8 | "strings"
9 | "sync"
10 | "unicode"
11 | )
12 |
13 | const validChars = "!#$%&()*+-./:<=>?@[]^_{|}~ "
14 |
15 | var fieldsCache sync.Map // map[reflect.Type][]field
16 |
17 | type seq struct {
18 | offset uintptr
19 | indir bool
20 | }
21 |
22 | type field struct {
23 | typ reflect.Type
24 | name string
25 | keyNonEsc []byte
26 | keyEscHTML []byte
27 | index []int
28 | tag bool
29 | quoted bool
30 | omitEmpty bool
31 | omitNil bool
32 | omitNullMarshaler bool
33 | instr instruction
34 | empty emptyFunc
35 |
36 | // embedSeq represents the sequence of offsets
37 | // and indirections to follow to reach the field
38 | // through one or more anonymous fields.
39 | embedSeq []seq
40 | }
41 |
42 | type typeCount map[reflect.Type]int
43 |
44 | // byIndex sorts a list of fields by index sequence.
45 | type byIndex []field
46 |
47 | func (x byIndex) Len() int { return len(x) }
48 | func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
49 |
50 | func (x byIndex) Less(i, j int) bool {
51 | for k, xik := range x[i].index {
52 | if k >= len(x[j].index) {
53 | return false
54 | }
55 | if xik != x[j].index[k] {
56 | return xik < x[j].index[k]
57 | }
58 | }
59 | return len(x[i].index) < len(x[j].index)
60 | }
61 |
62 | // cachedFields is similar to structFields, but uses a
63 | // cache to avoid repeated work.
64 | func cachedFields(t reflect.Type) []field {
65 | if f, ok := fieldsCache.Load(t); ok {
66 | return f.([]field)
67 | }
68 | f, _ := fieldsCache.LoadOrStore(t, structFields(t))
69 | return f.([]field)
70 | }
71 |
72 | // structFields returns a list of fields that should be
73 | // encoded for the given struct type. The algorithm is
74 | // breadth-first search over the set of structs to include,
75 | // the top one and then any reachable anonymous structs.
76 | func structFields(t reflect.Type) []field {
77 | var (
78 | flds []field
79 | ccnt typeCount
80 | curr = []field{}
81 | next = []field{{typ: t}}
82 | ncnt = make(typeCount)
83 | seen = make(map[reflect.Type]bool)
84 | )
85 | for len(next) > 0 {
86 | curr, next = next, curr[:0]
87 | ccnt, ncnt = ncnt, make(map[reflect.Type]int)
88 |
89 | for _, f := range curr {
90 | if seen[f.typ] {
91 | continue
92 | }
93 | seen[f.typ] = true
94 | // Scan the type for fields to encode.
95 | flds, next = scanFields(f, flds, next, ccnt, ncnt)
96 | }
97 | }
98 | sortFields(flds)
99 |
100 | flds = filterByVisibility(flds)
101 |
102 | // Sort fields by their index sequence.
103 | sort.Sort(byIndex(flds))
104 |
105 | return flds
106 | }
107 |
108 | // sortFields sorts the fields by name, breaking ties
109 | // with depth, then whether the field name come from
110 | // the JSON tag, and finally with the index sequence.
111 | func sortFields(fields []field) {
112 | sort.Slice(fields, func(i int, j int) bool {
113 | x := fields
114 |
115 | if x[i].name != x[j].name {
116 | return x[i].name < x[j].name
117 | }
118 | if len(x[i].index) != len(x[j].index) {
119 | return len(x[i].index) < len(x[j].index)
120 | }
121 | if x[i].tag != x[j].tag {
122 | return x[i].tag
123 | }
124 | return byIndex(x).Less(i, j)
125 | })
126 | }
127 |
128 | // shouldEncodeField returns whether a struct
129 | // field should be encoded.
130 | func shouldEncodeField(sf reflect.StructField) bool {
131 | isUnexported := sf.PkgPath != ""
132 | if sf.Anonymous {
133 | t := sf.Type
134 | if t.Kind() == reflect.Ptr {
135 | t = t.Elem()
136 | }
137 | // Ignore embedded fields of unexported non-struct
138 | // types, but in the contrary, don't ignore embedded
139 | // fields of unexported struct types since they may
140 | // have exported fields.
141 | if isUnexported && t.Kind() != reflect.Struct {
142 | return false
143 | }
144 | } else if isUnexported {
145 | // Ignore unexported non-embedded fields.
146 | return false
147 | }
148 | return true
149 | }
150 |
151 | // isValidFieldName returns whether s is a valid
152 | // name and can be used as a JSON key to encode
153 | // a struct field.
154 | func isValidFieldName(s string) bool {
155 | if len(s) == 0 {
156 | return false
157 | }
158 | for _, c := range s {
159 | switch {
160 | case strings.ContainsRune(validChars, c):
161 | // Backslash and quote chars are reserved, but
162 | // otherwise any punctuation chars are allowed
163 | // in a tag name.
164 | case !unicode.IsLetter(c) && !unicode.IsDigit(c):
165 | return false
166 | }
167 | }
168 | return true
169 | }
170 |
171 | // filterByVisibility deletes all fields that are hidden
172 | // by the Go rules for embedded fields, except that fields
173 | // with JSON tags are promoted. The fields are sorted in
174 | // primary order of name, secondary order of field index
175 | // length.
176 | func filterByVisibility(fields []field) []field {
177 | ret := fields[:0]
178 |
179 | for adv, i := 0, 0; i < len(fields); i += adv {
180 | // One iteration per name.
181 | // Find the sequence of fields with the name
182 | // of this first field.
183 | fi := fields[i]
184 | for adv = 1; i+adv < len(fields); adv++ {
185 | fj := fields[i+adv]
186 | if fj.name != fi.name {
187 | break
188 | }
189 | }
190 | if adv == 1 {
191 | // Only one field with this name.
192 | ret = append(ret, fi)
193 | continue
194 | }
195 | // More than one field with the same name are
196 | // present, delete hidden fields by choosing
197 | // the dominant field that survives.
198 | if dominant, ok := dominantField(fields[i : i+adv]); ok {
199 | ret = append(ret, dominant)
200 | }
201 | }
202 | return ret
203 | }
204 |
205 | func typeByIndex(t reflect.Type, index []int) reflect.Type {
206 | for _, i := range index {
207 | if t.Kind() == reflect.Ptr {
208 | t = t.Elem()
209 | }
210 | t = t.Field(i).Type
211 | }
212 | return t
213 | }
214 |
215 | // dominantField looks through the fields, all of which
216 | // are known to have the same name, to find the single
217 | // field that dominates the others using Go's embedding
218 | // rules, modified by the presence of JSON tags. If there
219 | // are multiple top-level fields, it returns false. This
220 | // condition is an error in Go, and all fields are skipped.
221 | func dominantField(fields []field) (field, bool) {
222 | if len(fields) > 1 &&
223 | len(fields[0].index) == len(fields[1].index) &&
224 | fields[0].tag == fields[1].tag {
225 | return field{}, false
226 | }
227 | return fields[0], true
228 | }
229 |
230 | func scanFields(f field, fields, next []field, cnt, ncnt typeCount) ([]field, []field) {
231 | var escBuf bytes.Buffer
232 |
233 | for i := 0; i < f.typ.NumField(); i++ {
234 | sf := f.typ.Field(i)
235 |
236 | if !shouldEncodeField(sf) {
237 | continue
238 | }
239 | tag := sf.Tag.Get("json")
240 | if tag == "-" {
241 | continue
242 | }
243 | // Parse name and options from the content
244 | // of the JSON tag.
245 | name, opts := parseTag(tag)
246 | if !isValidFieldName(name) {
247 | name = ""
248 | }
249 | index := make([]int, len(f.index)+1)
250 | copy(index, f.index)
251 | index[len(f.index)] = i
252 |
253 | typ := sf.Type
254 | isPtr := typ.Kind() == reflect.Ptr
255 | if typ.Name() == "" && isPtr {
256 | typ = typ.Elem()
257 | }
258 | // If the field is a named embedded struct or a
259 | // simple field, record it and its index sequence.
260 | if name != "" || !sf.Anonymous || typ.Kind() != reflect.Struct {
261 | tagged := name != ""
262 | // If a name is not present in the tag,
263 | // use the struct field's name instead.
264 | if name == "" {
265 | name = sf.Name
266 | }
267 | // Build HTML escaped field key.
268 | escBuf.Reset()
269 | _, _ = escBuf.WriteString(`"`)
270 | json.HTMLEscape(&escBuf, []byte(name))
271 | _, _ = escBuf.WriteString(`":`)
272 |
273 | nf := field{
274 | typ: typ,
275 | name: name,
276 | tag: tagged,
277 | index: index,
278 | omitEmpty: opts.Contains("omitempty"),
279 | omitNil: opts.Contains("omitnil"),
280 | quoted: opts.Contains("string") && isBasicType(typ),
281 | keyNonEsc: []byte(`"` + name + `":`),
282 | keyEscHTML: append([]byte(nil), escBuf.Bytes()...), // copy
283 | embedSeq: append(f.embedSeq[:0:0], f.embedSeq...), // clone
284 | }
285 | // Add final offset to sequences.
286 | nf.embedSeq = append(nf.embedSeq, seq{sf.Offset, false})
287 | fields = append(fields, nf)
288 |
289 | if cnt[f.typ] > 1 {
290 | // If there were multiple instances, add a
291 | // second, so that the annihilation code will
292 | // see a duplicate. It only cares about the
293 | // distinction between 1 or 2, so don't bother
294 | // generating any more copies.
295 | fields = append(fields, fields[len(fields)-1])
296 | }
297 | continue
298 | }
299 | // Record unnamed embedded struct
300 | // to be scanned in the next round.
301 | ncnt[typ]++
302 | if ncnt[typ] == 1 {
303 | next = append(next, field{
304 | typ: typ,
305 | name: typ.Name(),
306 | index: index,
307 | embedSeq: append(f.embedSeq, seq{sf.Offset, isPtr}),
308 | })
309 | }
310 | }
311 | return fields, next
312 | }
313 |
--------------------------------------------------------------------------------
/example_test.go:
--------------------------------------------------------------------------------
1 | package jettison_test
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "os"
9 | "strconv"
10 | "time"
11 |
12 | "github.com/wI2L/jettison"
13 | )
14 |
15 | func ExampleMarshal() {
16 | type X struct {
17 | A string `json:"a"`
18 | B int64 `json:"b"`
19 | C []string `json:"colors"`
20 | }
21 | x := X{
22 | A: "Loreum",
23 | B: -42,
24 | C: []string{"blue", "white", "red"},
25 | }
26 | b, err := jettison.Marshal(x)
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 | os.Stdout.Write(b)
31 | // Output:
32 | // {"a":"Loreum","b":-42,"colors":["blue","white","red"]}
33 | }
34 |
35 | func ExampleAppend() {
36 | type X struct {
37 | A bool `json:"a"`
38 | B uint32 `json:"b"`
39 | C map[string]string `json:"users"`
40 | }
41 | x := X{
42 | A: true,
43 | B: 42,
44 | C: map[string]string{
45 | "bob": "admin",
46 | "jerry": "user",
47 | },
48 | }
49 | buf, err := jettison.Append([]byte(nil), x)
50 | if err != nil {
51 | log.Fatal(err)
52 | }
53 | os.Stdout.Write(buf)
54 | // Output:
55 | // {"a":true,"b":42,"users":{"bob":"admin","jerry":"user"}}
56 | }
57 |
58 | func ExampleAppendOpts() {
59 | for _, v := range []interface{}{
60 | nil, 2 * time.Second,
61 | } {
62 | buf, err := jettison.AppendOpts([]byte(nil), v,
63 | jettison.DurationFormat(jettison.DurationString),
64 | )
65 | if err != nil {
66 | log.Fatal(err)
67 | }
68 | fmt.Printf("%s\n", string(buf))
69 | }
70 | // Output:
71 | // null
72 | // "2s"
73 | }
74 |
75 | type Animal int
76 |
77 | const (
78 | Unknown Animal = iota
79 | Gopher
80 | Zebra
81 | )
82 |
83 | // AppendJSON implements the jettison.AppendMarshaler interface.
84 | func (a Animal) AppendJSON(dst []byte) ([]byte, error) {
85 | var s string
86 | switch a {
87 | default:
88 | s = "unknown"
89 | case Gopher:
90 | s = "gopher"
91 | case Zebra:
92 | s = "zebra"
93 | }
94 | dst = append(dst, strconv.Quote(s)...)
95 | return dst, nil
96 | }
97 |
98 | func Example_customMarshaler() {
99 | zoo := []Animal{
100 | Unknown,
101 | Zebra,
102 | Gopher,
103 | }
104 | b, err := jettison.Marshal(zoo)
105 | if err != nil {
106 | log.Fatal(err)
107 | }
108 | os.Stdout.Write(b)
109 | // Output:
110 | // ["unknown","zebra","gopher"]
111 | }
112 |
113 | func ExampleRawByteSlice() {
114 | bs := []byte("Loreum Ipsum")
115 |
116 | for _, opt := range []jettison.Option{
117 | nil, jettison.RawByteSlice(),
118 | } {
119 | b, err := jettison.MarshalOpts(bs, opt)
120 | if err != nil {
121 | log.Fatal(err)
122 | }
123 | fmt.Printf("%s\n", string(b))
124 | }
125 | // Output:
126 | // "TG9yZXVtIElwc3Vt"
127 | // "Loreum Ipsum"
128 | }
129 |
130 | func ExampleByteArrayAsString() {
131 | b1 := [6]byte{'L', 'o', 'r', 'e', 'u', 'm'}
132 | b2 := [6]*byte{&b1[0], &b1[1], &b1[2], &b1[3], &b1[4], &b1[5]}
133 |
134 | for _, opt := range []jettison.Option{
135 | nil, jettison.ByteArrayAsString(),
136 | } {
137 | for _, v := range []interface{}{b1, b2} {
138 | b, err := jettison.MarshalOpts(v, opt)
139 | if err != nil {
140 | log.Fatal(err)
141 | }
142 | fmt.Printf("%s\n", string(b))
143 | }
144 | }
145 | // Output:
146 | // [76,111,114,101,117,109]
147 | // [76,111,114,101,117,109]
148 | // "Loreum"
149 | // [76,111,114,101,117,109]
150 | }
151 |
152 | func ExampleNilMapEmpty() {
153 | type X struct {
154 | M1 map[string]int
155 | M2 map[int]string
156 | }
157 | x := X{
158 | M1: map[string]int{},
159 | M2: nil,
160 | }
161 | for _, opt := range []jettison.Option{
162 | nil, jettison.NilMapEmpty(),
163 | } {
164 | b, err := jettison.MarshalOpts(x, opt)
165 | if err != nil {
166 | log.Fatal(err)
167 | }
168 | fmt.Printf("%s\n", string(b))
169 | }
170 | // Output:
171 | // {"M1":{},"M2":null}
172 | // {"M1":{},"M2":{}}
173 | }
174 |
175 | func ExampleNilSliceEmpty() {
176 | type X struct {
177 | S1 []int
178 | S2 []string
179 | }
180 | x := X{
181 | S1: []int{},
182 | S2: nil,
183 | }
184 | for _, opt := range []jettison.Option{
185 | nil, jettison.NilSliceEmpty(),
186 | } {
187 | b, err := jettison.MarshalOpts(x, opt)
188 | if err != nil {
189 | log.Fatal(err)
190 | }
191 | fmt.Printf("%s\n", string(b))
192 | }
193 | // Output:
194 | // {"S1":[],"S2":null}
195 | // {"S1":[],"S2":[]}
196 | }
197 |
198 | func ExampleUnixTime() {
199 | t := time.Date(2024, time.December, 24, 12, 24, 42, 0, time.UTC)
200 |
201 | b, err := jettison.MarshalOpts(t, jettison.UnixTime())
202 | if err != nil {
203 | log.Fatal(err)
204 | }
205 | os.Stdout.Write(b)
206 | // Output:
207 | // 1735043082
208 | }
209 |
210 | func ExampleTimeLayout() {
211 | t := time.Date(2042, time.July, 25, 16, 42, 24, 67850, time.UTC)
212 |
213 | locs := []*time.Location{
214 | time.UTC, time.FixedZone("WTF", 666), time.FixedZone("LOL", -4242),
215 | }
216 | for _, layout := range []string{
217 | time.RFC3339,
218 | time.RFC822,
219 | time.RFC1123Z,
220 | time.RFC3339Nano, // default
221 | } {
222 | for _, loc := range locs {
223 | b, err := jettison.MarshalOpts(t.In(loc), jettison.TimeLayout(layout))
224 | if err != nil {
225 | log.Fatal(err)
226 | }
227 | fmt.Printf("%s\n", string(b))
228 | }
229 | }
230 | // Output:
231 | // "2042-07-25T16:42:24Z"
232 | // "2042-07-25T16:53:30+00:11"
233 | // "2042-07-25T15:31:42-01:10"
234 | // "25 Jul 42 16:42 UTC"
235 | // "25 Jul 42 16:53 WTF"
236 | // "25 Jul 42 15:31 LOL"
237 | // "Fri, 25 Jul 2042 16:42:24 +0000"
238 | // "Fri, 25 Jul 2042 16:53:30 +0011"
239 | // "Fri, 25 Jul 2042 15:31:42 -0110"
240 | // "2042-07-25T16:42:24.00006785Z"
241 | // "2042-07-25T16:53:30.00006785+00:11"
242 | // "2042-07-25T15:31:42.00006785-01:10"
243 | }
244 |
245 | func ExampleDurationFormat() {
246 | d := 1*time.Hour + 3*time.Minute + 2*time.Second + 66*time.Millisecond
247 |
248 | for _, format := range []jettison.DurationFmt{
249 | jettison.DurationString,
250 | jettison.DurationMinutes,
251 | jettison.DurationSeconds,
252 | jettison.DurationMilliseconds,
253 | jettison.DurationMicroseconds,
254 | jettison.DurationNanoseconds,
255 | } {
256 | b, err := jettison.MarshalOpts(d, jettison.DurationFormat(format))
257 | if err != nil {
258 | log.Fatal(err)
259 | }
260 | fmt.Printf("%s\n", string(b))
261 | }
262 | // Output:
263 | // "1h3m2.066s"
264 | // 63.03443333333333
265 | // 3782.066
266 | // 3782066
267 | // 3782066000
268 | // 3782066000000
269 | }
270 |
271 | func ExampleUnsortedMap() {
272 | m := map[int]string{
273 | 3: "three",
274 | 1: "one",
275 | 2: "two",
276 | }
277 | b, err := jettison.MarshalOpts(m, jettison.UnsortedMap())
278 | if err != nil {
279 | log.Fatal(err)
280 | }
281 | var sorted map[int]string
282 | if err := json.Unmarshal(b, &sorted); err != nil {
283 | log.Fatal(err)
284 | }
285 | b, err = jettison.Marshal(sorted)
286 | if err != nil {
287 | log.Fatal(err)
288 | }
289 | os.Stdout.Write(b)
290 | // Output:
291 | // {"1":"one","2":"two","3":"three"}
292 | }
293 |
294 | func ExampleNoCompact() {
295 | rm := json.RawMessage(`{ "a":"b" }`)
296 | for _, opt := range []jettison.Option{
297 | nil, jettison.NoCompact(),
298 | } {
299 | b, err := jettison.MarshalOpts(rm, opt)
300 | if err != nil {
301 | log.Fatal(err)
302 | }
303 | fmt.Printf("%s\n", string(b))
304 | }
305 | // Output:
306 | // {"a":"b"}
307 | // { "a":"b" }
308 | }
309 |
310 | func ExampleAllowList() {
311 | type Z struct {
312 | Omega int `json:"ω"`
313 | }
314 | type Y struct {
315 | Pi string `json:"π"`
316 | }
317 | type X struct {
318 | Z Z `json:"Z"`
319 | Alpha string `json:"α"`
320 | Beta string `json:"β"`
321 | Gamma string
322 | Y
323 | }
324 | x := X{
325 | Z: Z{Omega: 42},
326 | Alpha: "1",
327 | Beta: "2",
328 | Gamma: "3",
329 | Y: Y{Pi: "4"},
330 | }
331 | for _, opt := range []jettison.Option{
332 | nil, jettison.AllowList([]string{"Z", "β", "Gamma", "π"}),
333 | } {
334 | b, err := jettison.MarshalOpts(x, opt)
335 | if err != nil {
336 | log.Fatal(err)
337 | }
338 | fmt.Printf("%s\n", string(b))
339 | }
340 | // Output:
341 | // {"Z":{"ω":42},"α":"1","β":"2","Gamma":"3","π":"4"}
342 | // {"Z":{},"β":"2","Gamma":"3","π":"4"}
343 | }
344 |
345 | func ExampleDenyList() {
346 | type X struct {
347 | A int `json:"aaAh"`
348 | B bool `json:"buzz"`
349 | C string
350 | D uint
351 | }
352 | x := X{
353 | A: -42,
354 | B: true,
355 | C: "Loreum",
356 | D: 42,
357 | }
358 | for _, opt := range []jettison.Option{
359 | nil, jettison.DenyList([]string{"buzz", "D"}),
360 | } {
361 | b, err := jettison.MarshalOpts(x, opt)
362 | if err != nil {
363 | log.Fatal(err)
364 | }
365 | fmt.Printf("%s\n", string(b))
366 | }
367 | // Output:
368 | // {"aaAh":-42,"buzz":true,"C":"Loreum","D":42}
369 | // {"aaAh":-42,"C":"Loreum"}
370 | }
371 |
372 | type (
373 | secret string
374 | ctxKey string
375 | )
376 |
377 | const obfuscateKey = ctxKey("_obfuscate_")
378 |
379 | // AppendJSONContext implements the jettison.AppendMarshalerCtx interface.
380 | func (s secret) AppendJSONContext(ctx context.Context, dst []byte) ([]byte, error) {
381 | out := string(s)
382 | if v := ctx.Value(obfuscateKey); v != nil {
383 | if hide, ok := v.(bool); ok && hide {
384 | out = "**__SECRET__**"
385 | }
386 | }
387 | dst = append(dst, strconv.Quote(out)...)
388 | return dst, nil
389 | }
390 |
391 | func ExampleWithContext() {
392 | sec := secret("v3ryS3nSitiv3P4ssWord")
393 |
394 | b, err := jettison.Marshal(sec)
395 | if err != nil {
396 | log.Fatal(err)
397 | }
398 | fmt.Printf("%s\n", string(b))
399 |
400 | ctx := context.WithValue(context.Background(),
401 | obfuscateKey, true,
402 | )
403 | b, err = jettison.MarshalOpts(sec, jettison.WithContext(ctx))
404 | if err != nil {
405 | log.Fatal(err)
406 | }
407 | fmt.Printf("%s\n", string(b))
408 | // Output:
409 | // "v3ryS3nSitiv3P4ssWord"
410 | // "**__SECRET__**"
411 | }
412 |
--------------------------------------------------------------------------------
/instruction.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "sync"
7 | "sync/atomic"
8 | "unsafe"
9 | )
10 |
11 | var (
12 | instrCachePtr unsafe.Pointer // *instrCache
13 | structInstrCache sync.Map // map[string]instruction
14 | )
15 |
16 | // An instruction appends the JSON representation
17 | // of a value pointed by the unsafe.Pointer p to
18 | // dst and returns the extended buffer.
19 | type instruction func(unsafe.Pointer, []byte, encOpts) ([]byte, error)
20 |
21 | // instrCache is an eventually consistent cache that
22 | // maps Go type definitions to dynamically generated
23 | // instructions. The key is unsafe.Pointer instead of
24 | // reflect.Type to improve lookup performance.
25 | type instrCache map[unsafe.Pointer]instruction
26 |
27 | func typeID(t reflect.Type) unsafe.Pointer {
28 | return unpackEface(t).word
29 | }
30 |
31 | // cachedInstr returns an instruction to encode the
32 | // given type from a cache, or create one on the fly.
33 | func cachedInstr(t reflect.Type) instruction {
34 | id := typeID(t)
35 |
36 | if instr, ok := loadInstr(id); ok {
37 | return instr
38 | }
39 | canAddr := t.Kind() == reflect.Ptr
40 |
41 | // canAddr indicates if the input value is addressable.
42 | // At this point, we only need to know if the value is
43 | // a pointer, the others instructions will handle that
44 | // themselves for their type, or pass-by the value.
45 | instr := newInstruction(t, canAddr, false)
46 | if isInlined(t) {
47 | instr = wrapInlineInstr(instr)
48 | }
49 | storeInstr(id, instr, loadCache())
50 |
51 | return instr
52 | }
53 |
54 | func loadCache() instrCache {
55 | p := atomic.LoadPointer(&instrCachePtr)
56 | return *(*instrCache)(unsafe.Pointer(&p))
57 | }
58 |
59 | func loadInstr(id unsafe.Pointer) (instruction, bool) {
60 | cache := loadCache()
61 | instr, ok := cache[id]
62 | return instr, ok
63 | }
64 |
65 | func storeInstr(key unsafe.Pointer, instr instruction, cache instrCache) {
66 | newCache := make(instrCache, len(cache)+1)
67 |
68 | // Clone the current cache and add the
69 | // new instruction.
70 | for k, v := range cache {
71 | newCache[k] = v
72 | }
73 | newCache[key] = instr
74 |
75 | atomic.StorePointer(
76 | &instrCachePtr,
77 | *(*unsafe.Pointer)(unsafe.Pointer(&newCache)),
78 | )
79 | }
80 |
81 | // newInstruction returns an instruction to encode t.
82 | // canAddr and quoted respectively indicates if the
83 | // value to encode is addressable and must be enclosed
84 | // with double-quote character in the output.
85 | func newInstruction(t reflect.Type, canAddr, quoted bool) instruction {
86 | // Go types must be checked first, because a Duration
87 | // is an int64, json.Number is a string, and both would
88 | // be interpreted as a basic type. Also, the time.Time
89 | // type implements the TextMarshaler interface, but we
90 | // want to use a special instruction instead.
91 | if ins := newGoTypeInstr(t); ins != nil {
92 | return ins
93 | }
94 | if ins := newMarshalerTypeInstr(t, canAddr); ins != nil {
95 | return ins
96 | }
97 | if ins := newBasicTypeInstr(t, quoted); ins != nil {
98 | return ins
99 | }
100 | switch t.Kind() {
101 | case reflect.Interface:
102 | return encodeInterface
103 | case reflect.Struct:
104 | return newStructInstr(t, canAddr)
105 | case reflect.Map:
106 | return newMapInstr(t)
107 | case reflect.Slice:
108 | return newSliceInstr(t)
109 | case reflect.Array:
110 | return newArrayInstr(t, canAddr)
111 | case reflect.Ptr:
112 | return newPtrInstr(t, quoted)
113 | }
114 | return newUnsupportedTypeInstr(t)
115 | }
116 |
117 | func newGoTypeInstr(t reflect.Type) instruction {
118 | switch t {
119 | case syncMapType:
120 | return encodeSyncMap
121 | case timeTimeType:
122 | return encodeTime
123 | case timeDurationType:
124 | return encodeDuration
125 | case jsonNumberType:
126 | return encodeNumber
127 | case jsonRawMessageType:
128 | return encodeRawMessage
129 | default:
130 | return nil
131 | }
132 | }
133 |
134 | // newMarshalerTypeInstr returns an instruction to handle
135 | // a type that implement one of the Marshaler, MarshalerCtx,
136 | // json.Marshal, encoding.TextMarshaler interfaces.
137 | func newMarshalerTypeInstr(t reflect.Type, canAddr bool) instruction {
138 | isPtr := t.Kind() == reflect.Ptr
139 | ptrTo := reflect.PtrTo(t)
140 |
141 | switch {
142 | case t.Implements(appendMarshalerCtxType):
143 | return newAppendMarshalerCtxInstr(t, false)
144 | case !isPtr && canAddr && ptrTo.Implements(appendMarshalerCtxType):
145 | return newAppendMarshalerCtxInstr(t, true)
146 | case t.Implements(appendMarshalerType):
147 | return newAppendMarshalerInstr(t, false)
148 | case !isPtr && canAddr && ptrTo.Implements(appendMarshalerType):
149 | return newAppendMarshalerInstr(t, true)
150 | case t.Implements(jsonMarshalerType):
151 | return newJSONMarshalerInstr(t, false)
152 | case !isPtr && canAddr && ptrTo.Implements(jsonMarshalerType):
153 | return newJSONMarshalerInstr(t, true)
154 | case t.Implements(textMarshalerType):
155 | return newTextMarshalerInstr(t, false)
156 | case !isPtr && canAddr && ptrTo.Implements(textMarshalerType):
157 | return newTextMarshalerInstr(t, true)
158 | default:
159 | return nil
160 | }
161 | }
162 |
163 | func newBasicTypeInstr(t reflect.Type, quoted bool) instruction {
164 | var ins instruction
165 |
166 | switch t.Kind() {
167 | case reflect.Bool:
168 | ins = encodeBool
169 | case reflect.String:
170 | return newStringInstr(quoted)
171 | case reflect.Int:
172 | ins = encodeInt
173 | case reflect.Int8:
174 | ins = encodeInt8
175 | case reflect.Int16:
176 | ins = encodeInt16
177 | case reflect.Int32:
178 | ins = encodeInt32
179 | case reflect.Int64:
180 | ins = encodeInt64
181 | case reflect.Uint:
182 | ins = encodeUint
183 | case reflect.Uint8:
184 | ins = encodeUint8
185 | case reflect.Uint16:
186 | ins = encodeUint16
187 | case reflect.Uint32:
188 | ins = encodeUint32
189 | case reflect.Uint64:
190 | ins = encodeUint64
191 | case reflect.Uintptr:
192 | ins = encodeUintptr
193 | case reflect.Float32:
194 | ins = encodeFloat32
195 | case reflect.Float64:
196 | ins = encodeFloat64
197 | default:
198 | return nil
199 | }
200 | if quoted {
201 | return wrapQuotedInstr(ins)
202 | }
203 | return ins
204 | }
205 |
206 | func newStringInstr(quoted bool) instruction {
207 | if quoted {
208 | return encodeQuotedString
209 | }
210 | return encodeString
211 | }
212 |
213 | func newUnsupportedTypeInstr(t reflect.Type) instruction {
214 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
215 | return dst, &UnsupportedTypeError{t}
216 | }
217 | }
218 |
219 | func newPtrInstr(t reflect.Type, quoted bool) instruction {
220 | e := t.Elem()
221 | i := newInstruction(e, true, quoted)
222 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
223 | return encodePointer(p, dst, opts, i)
224 | }
225 | }
226 |
227 | func newAppendMarshalerCtxInstr(t reflect.Type, hasPtr bool) instruction {
228 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
229 | return encodeMarshaler(p, dst, opts, t, hasPtr, encodeAppendMarshalerCtx)
230 | }
231 | }
232 |
233 | func newAppendMarshalerInstr(t reflect.Type, hasPtr bool) instruction {
234 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
235 | return encodeMarshaler(p, dst, opts, t, hasPtr, encodeAppendMarshaler)
236 | }
237 | }
238 |
239 | func newJSONMarshalerInstr(t reflect.Type, hasPtr bool) instruction {
240 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
241 | return encodeMarshaler(p, dst, opts, t, hasPtr, encodeJSONMarshaler)
242 | }
243 | }
244 |
245 | func newTextMarshalerInstr(t reflect.Type, hasPtr bool) instruction {
246 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
247 | return encodeMarshaler(p, dst, opts, t, hasPtr, encodeTextMarshaler)
248 | }
249 | }
250 |
251 | func newStructInstr(t reflect.Type, canAddr bool) instruction {
252 | id := fmt.Sprintf("%p-%t", typeID(t), canAddr)
253 |
254 | if instr, ok := structInstrCache.Load(id); ok {
255 | return instr.(instruction)
256 | }
257 | // To deal with recursive types, populate the
258 | // instructions cache with an indirect func
259 | // before we build it. This type waits on the
260 | // real instruction (ins) to be ready and then
261 | // calls it. This indirect function is only
262 | // used for recursive types.
263 | var (
264 | wg sync.WaitGroup
265 | ins instruction
266 | )
267 | wg.Add(1)
268 | i, loaded := structInstrCache.LoadOrStore(id,
269 | instruction(func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
270 | wg.Wait() // few ns/op overhead
271 | return ins(p, dst, opts)
272 | }),
273 | )
274 | if loaded {
275 | return i.(instruction)
276 | }
277 | // Generate the real instruction and replace
278 | // the indirect func with it.
279 | ins = newStructFieldsInstr(t, canAddr)
280 | wg.Done()
281 | structInstrCache.Store(id, ins)
282 |
283 | return ins
284 | }
285 |
286 | func newStructFieldsInstr(t reflect.Type, canAddr bool) instruction {
287 | if t.NumField() == 0 {
288 | // Fast path for empty struct.
289 | return func(_ unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
290 | return append(dst, "{}"...), nil
291 | }
292 | }
293 | var (
294 | flds = cachedFields(t)
295 | dupl = append(flds[:0:0], flds...) // clone
296 | )
297 | for i := range dupl {
298 | f := &dupl[i]
299 | ftyp := typeByIndex(t, f.index)
300 | etyp := ftyp
301 |
302 | if etyp.Kind() == reflect.Ptr {
303 | etyp = etyp.Elem()
304 | }
305 | if f.omitNil && (ftyp.Implements(jsonMarshalerType) || reflect.PtrTo(ftyp).Implements(jsonMarshalerType)) {
306 | f.omitNullMarshaler = true
307 | }
308 | if !isNilable(ftyp) {
309 | // Disable the omitnil option, to
310 | // eliminate a check at runtime.
311 | f.omitNil = false
312 | }
313 | // Generate instruction and empty func of the field.
314 | // Only strings, floats, integers, and booleans
315 | // types can be quoted.
316 | f.instr = newInstruction(ftyp, canAddr, f.quoted && isBasicType(etyp))
317 | if f.omitEmpty {
318 | f.empty = cachedEmptyFuncOf(ftyp)
319 | }
320 | }
321 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
322 | return encodeStruct(p, dst, opts, dupl)
323 | }
324 | }
325 |
326 | func newArrayInstr(t reflect.Type, canAddr bool) instruction {
327 | var (
328 | etyp = t.Elem()
329 | size = etyp.Size()
330 | isba = false
331 | )
332 | // Array elements are addressable if the
333 | // array itself is addressable.
334 | ins := newInstruction(etyp, canAddr, false)
335 |
336 | // Byte arrays does not encode as a string
337 | // by default, this behavior is defined by
338 | // the encoder's options during marshaling.
339 | if etyp.Kind() == reflect.Uint8 {
340 | pe := reflect.PtrTo(etyp)
341 | if !pe.Implements(jsonMarshalerType) && !pe.Implements(textMarshalerType) {
342 | isba = true
343 | }
344 | }
345 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
346 | return encodeArray(p, dst, opts, ins, size, t.Len(), isba)
347 | }
348 | }
349 |
350 | func newSliceInstr(t reflect.Type) instruction {
351 | etyp := t.Elem()
352 |
353 | if etyp.Kind() == reflect.Uint8 {
354 | pe := reflect.PtrTo(etyp)
355 | if !pe.Implements(jsonMarshalerType) && !pe.Implements(textMarshalerType) {
356 | return encodeByteSlice
357 | }
358 | }
359 | // Slice elements are always addressable.
360 | // see https://golang.org/pkg/reflect/#Value.CanAddr
361 | // for reference.
362 | var (
363 | ins = newInstruction(etyp, true, false)
364 | size = etyp.Size()
365 | )
366 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
367 | return encodeSlice(p, dst, opts, ins, size)
368 | }
369 | }
370 |
371 | func newMapInstr(t reflect.Type) instruction {
372 | var (
373 | ki instruction
374 | vi instruction
375 | )
376 | kt := t.Key()
377 | et := t.Elem()
378 |
379 | if !isString(kt) && !isInteger(kt) && !kt.Implements(textMarshalerType) {
380 | return newUnsupportedTypeInstr(t)
381 | }
382 | // The standard library has a strict precedence order
383 | // for map key types, defined by the documentation of
384 | // the json.Marshal function. That's why we bypass the
385 | // newTypeInstr function if key type is string.
386 | if isString(kt) {
387 | ki = encodeString
388 | } else {
389 | ki = newInstruction(kt, false, false)
390 | }
391 | // Wrap the key instruction for types that
392 | // do not encode with quotes by default.
393 | if !isString(kt) && !kt.Implements(textMarshalerType) {
394 | ki = wrapQuotedInstr(ki)
395 | }
396 | // See issue golang.org/issue/33675 for reference.
397 | if kt.Implements(textMarshalerType) && kt.Kind() == reflect.Ptr {
398 | ki = wrapTextMarshalerNilCheck(ki)
399 | }
400 | vi = newInstruction(et, false, false)
401 |
402 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
403 | return encodeMap(p, dst, opts, t, ki, vi)
404 | }
405 | }
406 |
407 | func wrapInlineInstr(ins instruction) instruction {
408 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
409 | return ins(noescape(unsafe.Pointer(&p)), dst, opts)
410 | }
411 | }
412 |
413 | func wrapQuotedInstr(ins instruction) instruction {
414 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
415 | dst = append(dst, '"')
416 | var err error
417 | dst, err = ins(p, dst, opts)
418 | if err == nil {
419 | dst = append(dst, '"')
420 | }
421 | return dst, err
422 | }
423 | }
424 |
425 | func wrapTextMarshalerNilCheck(ins instruction) instruction {
426 | return func(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
427 | if *(*unsafe.Pointer)(p) == nil {
428 | return append(dst, `""`...), nil
429 | }
430 | return ins(p, dst, opts)
431 | }
432 | }
433 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Jettison
2 | 
Jettison is a fast and flexible JSON encoder for the Go programming language, inspired by bet365/jingo, with a richer features set, aiming at 100% compatibility with the standard library.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ---
16 |
17 | ## Installation
18 |
19 | Jettison uses [Go modules](https://github.com/golang/go/wiki/Modules). Releases are tagged according to the _SemVer_ format, prefixed with a `v`, starting from *0.2.0*. You can get the latest release using the following command.
20 |
21 | ```console
22 | $ go get github.com/wI2L/jettison@latest
23 | ```
24 |
25 | :warning: From version `v0.7.4`, the package requires [Go 1.17+](https://golang.org/doc/install) to build, due to the usage of the [new build constraints](https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md).
26 |
27 | ## Key features
28 |
29 | - Fast, see [benchmarks](#benchmarks)
30 | - No dynamic memory allocations in hot paths
31 | - Behavior identical to the standard library by default
32 | - No code generation required
33 | - Clear and concise API
34 | - Configurable with opt-in functional options
35 | - Native support for many standard library types, see [improvements](#improvements)
36 | - Custom `AppendMarshaler` interface to avoid allocations
37 | - Extensive testsuite that compares its output against `encoding/json`
38 |
39 | ## Overview
40 |
41 | The goal of Jettision is to take up the idea introduced by the **bet365/jingo** package and build a fully-featured JSON encoder around it, that comply with the behavior of the [encoding/json](https://golang.org/pkg/encoding/json/) package. Unlike the latter, Jettison does not use reflection during marshaling, but only once to create the instruction set for a given type ahead of time. The drawback to this approach requires to instantiate an instruction-set once for each type that needs to be marshaled, but that is overcomed with a package cache.
42 |
43 | The package aims to have a behavior similar to that of the standard library for all types encoding and struct tags, meaning that the documentation of the `json.Marshal` [function](https://golang.org/pkg/encoding/json/#Marshal) is applicable for Jettison, with a few exceptions described in this [section](#differences-with-encodingjson). As such, most of the tests compare their output against it to guarantee that.
44 |
45 | ### Implementation details
46 |
47 | The main concept of Jettison consists of using pre-build instructions-set to reduce the cost of using the `reflect` package at runtime. When marshaling a value, a set of _instructions_ is recursively generated for its type, which defines how to iteratively encode it. An _instruction_ is a function or a closure, that have all the information required to read the data from memory using _unsafe_ operations (pointer type conversion, arithmetic...) during the instruction set execution.
48 |
49 | ### Differences with `encoding/json`
50 |
51 | All notable differences with the standard library behavior are listed below. Please note that these might evolve with future versions of the package.
52 |
53 | #### Improvements
54 |
55 | - The `time.Time` and `time.Duration` types are handled natively. For time values, the encoder doesn't invoke `MarshalJSON` or `MarshalText`, but use the `time.AppendFormat` [function](https://golang.org/pkg/time/#Time.AppendFormat) instead, and write the result to the stream. Similarly, for durations, it isn't necessary to implements the `json.Marshaler` or `encoding.TextMarshaler` interfaces on a custom wrapper type, the encoder uses the result of one of the methods `Minutes`, `Seconds`, `Nanoseconds` or `String`, based on the duration [format](https://godoc.org/github.com/wI2L/jettison#DurationFmt) configured.
56 |
57 | - The `sync.Map` type is handled natively. The marshaling behavior is similar to the one of a standard Go `map`. The option `UnsortedMap` can also be used in cunjunction with this type to disable the default keys sort.
58 |
59 | - The `omitnil` field tag's option can be used to specify that a field with a nil pointer should be omitted from the encoding. This option has precedence over the `omitempty` option. Note that struct fields that implement the `json.Marshaler` interface will be omitted too, if they return the literal JSON `null` value.
60 |
61 | #### Bugs
62 |
63 | ##### Go1.13 and backward
64 |
65 | - Nil map keys values implementing the `encoding.TextMarshaler` interface are encoded as empty strings, while the `encoding/json` package currently panic because of that. See this [issue](https://github.com/golang/go/issues/33675) for more details.[1]
66 |
67 | - Nil struct fields implementing the `encoding.TextMarshaler` interface are encoded as `null`, while the `encoding/json` package currently panic because of that. See this [issue](https://github.com/golang/go/issues/34235) for more details.[1]
68 |
69 | 1: The issues mentioned above have had their associated CL merged, and was released with Go 1.14.
70 |
71 | ## Usage
72 |
73 | ### Basic
74 |
75 | As stated above, the library behave similarly to the `encoding/json` package. You can simply replace the `json.Marshal` function with `jettison.Marshal`, and expect the same output with better performances.
76 |
77 | ```go
78 | type X struct {
79 | A string `json:"a"`
80 | B int64 `json:"b"`
81 | }
82 | b, err := jettison.Marshal(X{
83 | A: "Loreum",
84 | B: 42,
85 | })
86 | if err != nil {
87 | log.Fatal(err)
88 | }
89 | os.Stdout.Write(b)
90 | ```
91 | ###### Result
92 | ```json
93 | {"a":"Loreum","b":42}
94 | ```
95 |
96 | ### Advanced
97 |
98 | If more control over the encoding behavior is required, use the `MarshalOpts` function instead. The second parameter is variadic and accept a list of functional opt-in [options](https://godoc.org/github.com/wI2L/jettison#Option) described below:
99 |
100 | | name | description |
101 | |:------------------------:| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
102 | | **`TimeLayout`** | Defines the layout used to encode `time.Time` values. The layout must be compatible with the [AppendFormat](https://golang.org/pkg/time/#Time.AppendFormat) method. |
103 | | **`DurationFormat`** | Defines the format used to encode `time.Duration` values. See the documentation of the `DurationFmt` type for the complete list of formats available. |
104 | | **`UnixTime`** | Encode `time.Time` values as JSON numbers representing Unix timestamps, the number of seconds elapsed since *January 1, 1970 UTC*. This option has precedence over `TimeLayout`. |
105 | | **`UnsortedMap`** | Disables map keys sort. |
106 | | **`ByteArrayAsString`** | Encodes byte arrays as JSON strings rather than JSON arrays. The output is subject to the same escaping rules used for JSON strings, unless the option `NoStringEscaping` is used. |
107 | | **`RawByteSlice`** | Disables the *base64* default encoding used for byte slices. |
108 | | **`NilMapEmpty`** | Encodes nil Go maps as empty JSON objects rather than `null`. |
109 | | **`NilSliceEmpty`** | Encodes nil Go slices as empty JSON arrays rather than `null`. |
110 | | **`NoStringEscaping`** | Disables string escaping. `NoHTMLEscaping` and `NoUTF8Coercion` are ignored when this option is used. |
111 | | **`NoHTMLEscaping`** | Disables the escaping of special HTML characters such as `&`, `<` and `>` in JSON strings. This is similar to `json.Encoder.SetEscapeHTML(false)`. |
112 | | **`NoUTF8Coercion`** | Disables the replacement of invalid bytes with the Unicode replacement rune in JSON strings. |
113 | | **`AllowList`** | Sets a whitelist that represents which fields are to be encoded when marshaling a Go struct. |
114 | | **`DenyList`** | Sets a blacklist that represents which fields are ignored during the marshaling of a Go struct. |
115 | | **`NoCompact`** | Disables the compaction of JSON output produced by `MarshalJSON` method, and `json.RawMessage` values. |
116 | | **`NoNumberValidation`** | Disables the validation of `json.Number` values. |
117 | | **`WithContext`** | Sets the `context.Context` to be passed to invocations of `AppendJSONContext` methods. |
118 |
119 | Take a look at the [examples](example_test.go) to see these options in action.
120 |
121 | ## Benchmarks
122 |
123 | If you'd like to run the benchmarks yourself, use the following command.
124 |
125 | ```shell
126 | go get github.com/cespare/prettybench
127 | go test -bench=. | prettybench
128 | ```
129 |
130 | ### Results `-short`
131 |
132 | These benchmarks were run 10x (statistics computed with [benchstat](https://godoc.org/golang.org/x/perf/cmd/benchstat)) on a MacBook Pro 15", with the following specs:
133 | ```
134 | OS: macOS Catalina (10.15.7)
135 | CPU: 2.6 GHz Intel Core i7
136 | Mem: 16GB
137 | Go: go version go1.17 darwin/amd64
138 | Tag: v0.7.2
139 | ```
140 |
141 | Stats
142 | name time/op
143 | Simple/standard-8 573ns ± 1%
144 | Simple/jsoniter-8 547ns ± 0%
145 | Simple/segmentj-8 262ns ± 1%
146 | Simple/jettison-8 408ns ± 1%
147 | Complex/standard-8 11.7µs ± 0%
148 | Complex/jsoniter-8 11.6µs ± 1%
149 | Complex/segmentj-8 7.96µs ± 0%
150 | Complex/jettison-8 5.90µs ± 1%
151 | CodeMarshal/standard-8 6.71ms ± 0%
152 | CodeMarshal/jsoniter-8 6.35ms ± 1%
153 | CodeMarshal/segmentj-8 4.38ms ± 1%
154 | CodeMarshal/jettison-8 5.56ms ± 1%
155 | Map/standard-8 1.83µs ± 1%
156 | Map/jsoniter-8 1.65µs ± 0%
157 | Map/segmentj-8 1.61µs ± 0%
158 | Map/jettison-8 772ns ± 1%
159 | Map/jettison-nosort-8 507ns ± 1%
160 |
161 | name speed
162 | Simple/standard-8 236MB/s ± 1%
163 | Simple/jsoniter-8 247MB/s ± 0%
164 | Simple/segmentj-8 516MB/s ± 1%
165 | Simple/jettison-8 331MB/s ± 1%
166 | Complex/standard-8 72.9MB/s ± 0%
167 | Complex/jsoniter-8 70.6MB/s ± 0%
168 | Complex/segmentj-8 108MB/s ± 0%
169 | Complex/jettison-8 144MB/s ± 1%
170 | CodeMarshal/standard-8 289MB/s ± 0%
171 | CodeMarshal/jsoniter-8 306MB/s ± 1%
172 | CodeMarshal/segmentj-8 443MB/s ± 1%
173 | CodeMarshal/jettison-8 349MB/s ± 1%
174 | Map/standard-8 46.6MB/s ± 1%
175 | Map/jsoniter-8 51.5MB/s ± 0%
176 | Map/segmentj-8 52.8MB/s ± 0%
177 | Map/jettison-8 110MB/s ± 1%
178 | Map/jettison-nosort-8 168MB/s ± 1%
179 |
180 | name alloc/op
181 | Simple/standard-8 144B ± 0%
182 | Simple/jsoniter-8 152B ± 0%
183 | Simple/segmentj-8 144B ± 0%
184 | Simple/jettison-8 144B ± 0%
185 | Complex/standard-8 4.05kB ± 0%
186 | Complex/jsoniter-8 3.95kB ± 0%
187 | Complex/segmentj-8 2.56kB ± 0%
188 | Complex/jettison-8 935B ± 0%
189 | CodeMarshal/standard-8 1.97MB ± 0%
190 | CodeMarshal/jsoniter-8 2.00MB ± 0%
191 | CodeMarshal/segmentj-8 1.98MB ± 2%
192 | CodeMarshal/jettison-8 1.98MB ± 2%
193 | Map/standard-8 888B ± 0%
194 | Map/jsoniter-8 884B ± 0%
195 | Map/segmentj-8 576B ± 0%
196 | Map/jettison-8 96.0B ± 0%
197 | Map/jettison-nosort-8 160B ± 0%
198 |
199 | name allocs/op
200 | Simple/standard-8 1.00 ± 0%
201 | Simple/jsoniter-8 2.00 ± 0%
202 | Simple/segmentj-8 1.00 ± 0%
203 | Simple/jettison-8 1.00 ± 0%
204 | Complex/standard-8 79.0 ± 0%
205 | Complex/jsoniter-8 71.0 ± 0%
206 | Complex/segmentj-8 52.0 ± 0%
207 | Complex/jettison-8 8.00 ± 0%
208 | CodeMarshal/standard-8 1.00 ± 0%
209 | CodeMarshal/jsoniter-8 2.00 ± 0%
210 | CodeMarshal/segmentj-8 1.00 ± 0%
211 | CodeMarshal/jettison-8 1.00 ± 0%
212 | Map/standard-8 19.0 ± 0%
213 | Map/jsoniter-8 14.0 ± 0%
214 | Map/segmentj-8 18.0 ± 0%
215 | Map/jettison-8 1.00 ± 0%
216 | Map/jettison-nosort-8 2.00 ± 0%
217 |
218 |
219 | #### Simple [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L50)]
220 |
221 | Basic payload with fields of type `string`, `int` and `bool`.
222 |
223 | 
224 |
225 | #### Complex [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L65)]
226 |
227 | Large payload with a variety of composite Go types, such as `struct`, `map`, `interface`, multi-dimensions `array` and `slice`, with pointer and non-pointer value types.
228 |
229 | Please note that this test is somewhat positively influenced by the performances of map marshaling.
230 |
231 | 
232 |
233 | #### CodeMarshal [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L69)]
234 |
235 | Borrowed from the `encoding/json` tests. See [testdata/code.json.gz](testdata/code.json.gz).
236 |
237 | 
238 |
239 | #### Map [[source](https://github.com/wI2L/jettison/blob/master/bench_test.go#L75)]
240 |
241 | Simple `map[string]int` with 6 keys.
242 |
243 | 
244 |
245 | ## Credits
246 |
247 | This library and its design has been inspired by the work of others at **@bet365** and **@segmentio**.
248 | See the following projects for reference:
249 | - [bet365/jingo](https://github.com/bet365/jingo)
250 | - [segmentio/encoding](https://github.com/segmentio/encoding)
251 |
252 | ## License
253 |
254 | Jettison is licensed under the **MIT** license. See the [LICENSE](LICENSE) file.
255 |
256 | This package also uses some portions of code from the Go **encoding/json** package. The associated license can be found in [LICENSE.golang](LICENSE.golang).
257 |
--------------------------------------------------------------------------------
/encode.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "bytes"
5 | "encoding"
6 | "encoding/base64"
7 | "encoding/json"
8 | "errors"
9 | "fmt"
10 | "math"
11 | "reflect"
12 | "runtime"
13 | "sort"
14 | "strconv"
15 | "sync"
16 | "time"
17 | "unicode/utf8"
18 | "unsafe"
19 | )
20 |
21 | const hex = "0123456789abcdef"
22 |
23 | //nolint:unparam
24 | func encodeBool(p unsafe.Pointer, dst []byte, _ encOpts) ([]byte, error) {
25 | if *(*bool)(p) {
26 | return append(dst, "true"...), nil
27 | }
28 | return append(dst, "false"...), nil
29 | }
30 |
31 | // encodeString appends the escaped bytes of the string
32 | // pointed by p to dst. If quoted is true, escaped double
33 | // quote characters are added at the beginning and the
34 | // end of the JSON string.
35 | // nolint:unparam
36 | func encodeString(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
37 | dst = append(dst, '"')
38 | dst = appendEscapedBytes(dst, sp2b(p), opts)
39 | dst = append(dst, '"')
40 |
41 | return dst, nil
42 | }
43 |
44 | //nolint:unparam
45 | func encodeQuotedString(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
46 | dst = append(dst, `"\"`...)
47 | dst = appendEscapedBytes(dst, sp2b(p), opts)
48 | dst = append(dst, `\""`...)
49 |
50 | return dst, nil
51 | }
52 |
53 | // encodeFloat32 appends the textual representation of
54 | // the 32-bits floating point number pointed by p to dst.
55 | func encodeFloat32(p unsafe.Pointer, dst []byte, _ encOpts) ([]byte, error) {
56 | return appendFloat(dst, float64(*(*float32)(p)), 32)
57 | }
58 |
59 | // encodeFloat64 appends the textual representation of
60 | // the 64-bits floating point number pointed by p to dst.
61 | func encodeFloat64(p unsafe.Pointer, dst []byte, _ encOpts) ([]byte, error) {
62 | return appendFloat(dst, *(*float64)(p), 64)
63 | }
64 |
65 | func encodeInterface(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
66 | v := *(*interface{})(p)
67 | if v == nil {
68 | return append(dst, "null"...), nil
69 | }
70 | typ := reflect.TypeOf(v)
71 | ins := cachedInstr(typ)
72 |
73 | return ins(unpackEface(v).word, dst, opts)
74 | }
75 |
76 | func encodeNumber(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
77 | // Cast pointer to string directly to avoid
78 | // a useless conversion.
79 | num := *(*string)(p)
80 |
81 | // In Go1.5 the empty string encodes to "0".
82 | // While this is not a valid number literal,
83 | // we keep compatibility, so check validity
84 | // after this.
85 | if num == "" {
86 | num = "0" // Number's zero-val
87 | }
88 | if !opts.flags.has(noNumberValidation) && !isValidNumber(num) {
89 | return dst, fmt.Errorf("json: invalid number literal %q", num)
90 | }
91 | return append(dst, num...), nil
92 | }
93 |
94 | func encodeRawMessage(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
95 | v := *(*json.RawMessage)(p)
96 | if v == nil {
97 | return append(dst, "null"...), nil
98 | }
99 | if opts.flags.has(noCompact) {
100 | return append(dst, v...), nil
101 | }
102 | return appendCompactJSON(dst, v, !opts.flags.has(noHTMLEscaping))
103 | }
104 |
105 | // encodeTime appends the time.Time value pointed by
106 | // p to dst based on the format configured in opts.
107 | func encodeTime(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
108 | t := *(*time.Time)(p)
109 | y := t.Year()
110 |
111 | if y < 0 || y >= 10000 {
112 | // See comment golang.org/issue/4556#c15.
113 | return dst, errors.New("time: year outside of range [0,9999]")
114 | }
115 | if opts.flags.has(unixTime) {
116 | return strconv.AppendInt(dst, t.Unix(), 10), nil
117 | }
118 | switch opts.timeLayout {
119 | case time.RFC3339:
120 | return appendRFC3339Time(t, dst, false), nil
121 | case time.RFC3339Nano:
122 | return appendRFC3339Time(t, dst, true), nil
123 | default:
124 | dst = append(dst, '"')
125 | dst = t.AppendFormat(dst, opts.timeLayout)
126 | dst = append(dst, '"')
127 | return dst, nil
128 | }
129 | }
130 |
131 | // encodeDuration appends the time.Duration value pointed
132 | // by p to dst based on the format configured in opts.
133 | func encodeDuration(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
134 | d := *(*time.Duration)(p)
135 |
136 | switch opts.durationFmt {
137 | default: // DurationNanoseconds
138 | return strconv.AppendInt(dst, d.Nanoseconds(), 10), nil
139 | case DurationMinutes:
140 | return appendFloat(dst, d.Minutes(), 64)
141 | case DurationSeconds:
142 | return appendFloat(dst, d.Seconds(), 64)
143 | case DurationMicroseconds:
144 | return strconv.AppendInt(dst, int64(d)/1e3, 10), nil
145 | case DurationMilliseconds:
146 | return strconv.AppendInt(dst, int64(d)/1e6, 10), nil
147 | case DurationString:
148 | dst = append(dst, '"')
149 | dst = appendDuration(dst, d)
150 | dst = append(dst, '"')
151 | return dst, nil
152 | }
153 | }
154 |
155 | func appendFloat(dst []byte, f float64, bs int) ([]byte, error) {
156 | if math.IsInf(f, 0) || math.IsNaN(f) {
157 | return dst, &UnsupportedValueError{
158 | reflect.ValueOf(f),
159 | strconv.FormatFloat(f, 'g', -1, bs),
160 | }
161 | }
162 | // Convert as it was an ES6 number to string conversion.
163 | // This matches most other JSON generators. The following
164 | // code is taken from the floatEncoder implementation of
165 | // the encoding/json package of the Go standard library.
166 | abs := math.Abs(f)
167 | format := byte('f')
168 | if abs != 0 {
169 | if bs == 64 && (abs < 1e-6 || abs >= 1e21) ||
170 | bs == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
171 | format = 'e'
172 | }
173 | }
174 | dst = strconv.AppendFloat(dst, f, format, -1, bs)
175 | if format == 'e' {
176 | n := len(dst)
177 | if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' {
178 | dst[n-2] = dst[n-1]
179 | dst = dst[:n-1]
180 | }
181 | }
182 | return dst, nil
183 | }
184 |
185 | func encodePointer(p unsafe.Pointer, dst []byte, opts encOpts, ins instruction) ([]byte, error) {
186 | if p = *(*unsafe.Pointer)(p); p != nil {
187 | return ins(p, dst, opts)
188 | }
189 | return append(dst, "null"...), nil
190 | }
191 |
192 | func encodeStruct(
193 | p unsafe.Pointer, dst []byte, opts encOpts, flds []field,
194 | ) ([]byte, error) {
195 | var (
196 | nxt = byte('{')
197 | key []byte // key of the field
198 | )
199 | noHTMLEscape := opts.flags.has(noHTMLEscaping)
200 |
201 | fieldLoop:
202 | for i := 0; i < len(flds); i++ {
203 | f := &flds[i] // get pointer to prevent copy
204 | if opts.isDeniedField(f.name) {
205 | continue
206 | }
207 | fp := p
208 |
209 | // Find the nested struct field by following
210 | // the offset sequence, indirecting encountered
211 | // pointers as needed.
212 | for i := 0; i < len(f.embedSeq); i++ {
213 | s := &f.embedSeq[i]
214 | fp = unsafe.Pointer(uintptr(fp) + s.offset)
215 | if s.indir {
216 | if fp = *(*unsafe.Pointer)(fp); fp == nil {
217 | // When we encounter a nil pointer
218 | // in the chain, we have no choice
219 | // but to ignore the field.
220 | continue fieldLoop
221 | }
222 | }
223 | }
224 | // Ignore the field if it is a nil pointer and has
225 | // the omitnil option in his tag.
226 | if f.omitNil && *(*unsafe.Pointer)(fp) == nil {
227 | continue
228 | }
229 | // Ignore the field if it represents the zero-value
230 | // of its type and has the omitempty option in his tag.
231 | // Empty func is non-nil only if the field has the
232 | // omitempty option in its tag.
233 | if f.omitEmpty && f.empty(fp) {
234 | continue
235 | }
236 | key = f.keyEscHTML
237 | if noHTMLEscape {
238 | key = f.keyNonEsc
239 | }
240 | lastKeyOffset := len(dst)
241 | dst = append(dst, nxt)
242 | if nxt == '{' {
243 | lastKeyOffset++
244 | }
245 | nxt = ','
246 | dst = append(dst, key...)
247 |
248 | var err error
249 | if dst, err = f.instr(fp, dst, opts); err != nil {
250 | return dst, err
251 | }
252 | if f.omitNullMarshaler && len(dst) > 4 && bytes.Compare(dst[len(dst)-4:], []byte("null")) == 0 {
253 | dst = dst[:lastKeyOffset]
254 | }
255 | }
256 | if nxt == '{' {
257 | return append(dst, "{}"...), nil
258 | }
259 | return append(dst, '}'), nil
260 | }
261 |
262 | func encodeSlice(
263 | p unsafe.Pointer, dst []byte, opts encOpts, ins instruction, es uintptr,
264 | ) ([]byte, error) {
265 | shdr := (*sliceHeader)(p)
266 | if shdr.Data == nil {
267 | if opts.flags.has(nilSliceEmpty) {
268 | return append(dst, "[]"...), nil
269 | }
270 | return append(dst, "null"...), nil
271 | }
272 | if shdr.Len == 0 {
273 | return append(dst, "[]"...), nil
274 | }
275 | return encodeArray(shdr.Data, dst, opts, ins, es, shdr.Len, false)
276 | }
277 |
278 | // encodeByteSlice appends a byte slice to dst as
279 | // a JSON string. If the options flag rawByteSlice
280 | // is set, the escaped bytes are appended to the
281 | // buffer directly, otherwise in base64 form.
282 | // nolint:unparam
283 | func encodeByteSlice(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
284 | b := *(*[]byte)(p)
285 | if b == nil {
286 | return append(dst, "null"...), nil
287 | }
288 | dst = append(dst, '"')
289 |
290 | if opts.flags.has(rawByteSlice) {
291 | dst = appendEscapedBytes(dst, b, opts)
292 | } else {
293 | n := base64.StdEncoding.EncodedLen(len(b))
294 | if a := cap(dst) - len(dst); a < n {
295 | new := make([]byte, cap(dst)+(n-a))
296 | copy(new, dst)
297 | dst = new[:len(dst)]
298 | }
299 | end := len(dst) + n
300 | base64.StdEncoding.Encode(dst[len(dst):end], b)
301 |
302 | dst = dst[:end]
303 | }
304 | return append(dst, '"'), nil
305 | }
306 |
307 | func encodeArray(
308 | p unsafe.Pointer, dst []byte, opts encOpts, ins instruction, es uintptr, len int, isByteArray bool,
309 | ) ([]byte, error) {
310 | if isByteArray && opts.flags.has(byteArrayAsString) {
311 | return encodeByteArrayAsString(p, dst, opts, len), nil
312 | }
313 | var err error
314 | nxt := byte('[')
315 |
316 | for i := 0; i < len; i++ {
317 | dst = append(dst, nxt)
318 | nxt = ','
319 | v := unsafe.Pointer(uintptr(p) + (uintptr(i) * es))
320 | if dst, err = ins(v, dst, opts); err != nil {
321 | return dst, err
322 | }
323 | }
324 | if nxt == '[' {
325 | return append(dst, "[]"...), nil
326 | }
327 | return append(dst, ']'), nil
328 | }
329 |
330 | // encodeByteArrayAsString appends the escaped
331 | // bytes of the byte array pointed by p to dst
332 | // as a JSON string.
333 | func encodeByteArrayAsString(p unsafe.Pointer, dst []byte, opts encOpts, len int) []byte {
334 | // For byte type, size is guaranteed to be 1,
335 | // so the slice length is the same as the array's.
336 | // see golang.org/ref/spec#Size_and_alignment_guarantees
337 | b := *(*[]byte)(unsafe.Pointer(&sliceHeader{
338 | Data: p,
339 | Len: len,
340 | Cap: len,
341 | }))
342 | dst = append(dst, '"')
343 | dst = appendEscapedBytes(dst, b, opts)
344 | dst = append(dst, '"')
345 |
346 | return dst
347 | }
348 |
349 | func encodeMap(
350 | p unsafe.Pointer, dst []byte, opts encOpts, t reflect.Type, ki, vi instruction,
351 | ) ([]byte, error) {
352 | m := *(*unsafe.Pointer)(p)
353 | if m == nil {
354 | if opts.flags.has(nilMapEmpty) {
355 | return append(dst, "{}"...), nil
356 | }
357 | return append(dst, "null"...), nil
358 | }
359 | ml := maplen(m)
360 | if ml == 0 {
361 | return append(dst, "{}"...), nil
362 | }
363 | dst = append(dst, '{')
364 |
365 | rt := unpackEface(t).word
366 | it := newHiter(rt, m)
367 |
368 | var err error
369 | if opts.flags.has(unsortedMap) {
370 | dst, err = encodeUnsortedMap(it, dst, opts, ki, vi)
371 | } else {
372 | dst, err = encodeSortedMap(it, dst, opts, ki, vi, ml)
373 | }
374 | hiterPool.Put(it)
375 |
376 | if err != nil {
377 | return dst, err
378 | }
379 | return append(dst, '}'), err
380 | }
381 |
382 | // encodeUnsortedMap appends the elements of the map
383 | // pointed by p as comma-separated k/v pairs to dst,
384 | // in unspecified order.
385 | func encodeUnsortedMap(
386 | it *hiter, dst []byte, opts encOpts, ki, vi instruction,
387 | ) ([]byte, error) {
388 | var (
389 | n int
390 | err error
391 | )
392 | for ; it.key != nil; mapiternext(it) {
393 | if n != 0 {
394 | dst = append(dst, ',')
395 | }
396 | // Encode entry's key.
397 | if dst, err = ki(it.key, dst, opts); err != nil {
398 | return dst, err
399 | }
400 | dst = append(dst, ':')
401 |
402 | // Encode entry's value.
403 | if dst, err = vi(it.val, dst, opts); err != nil {
404 | return dst, err
405 | }
406 | n++
407 | }
408 | return dst, nil
409 | }
410 |
411 | // encodeUnsortedMap appends the elements of the map
412 | // pointed by p as comma-separated k/v pairs to dst,
413 | // sorted by key in lexicographical order.
414 | func encodeSortedMap(
415 | it *hiter, dst []byte, opts encOpts, ki, vi instruction, ml int,
416 | ) ([]byte, error) {
417 | var (
418 | off int
419 | err error
420 | buf = cachedBuffer()
421 | mel *mapElems
422 | )
423 | if v := mapElemsPool.Get(); v != nil {
424 | mel = v.(*mapElems)
425 | } else {
426 | mel = &mapElems{s: make([]kv, 0, ml)}
427 | }
428 | for ; it.key != nil; mapiternext(it) {
429 | kv := kv{}
430 |
431 | // Encode the key and store the buffer
432 | // portion to use during sort.
433 | if buf.B, err = ki(it.key, buf.B, opts); err != nil {
434 | break
435 | }
436 | // Omit quotes of keys.
437 | kv.key = buf.B[off+1 : len(buf.B)-1]
438 |
439 | // Add separator after key.
440 | buf.B = append(buf.B, ':')
441 |
442 | // Encode the value and store the buffer
443 | // portion corresponding to the semicolon
444 | // delimited key/value pair.
445 | if buf.B, err = vi(it.val, buf.B, opts); err != nil {
446 | break
447 | }
448 | kv.keyval = buf.B[off:len(buf.B)]
449 | mel.s = append(mel.s, kv)
450 | off = len(buf.B)
451 | }
452 | if err == nil {
453 | // Sort map entries by key in
454 | // lexicographical order.
455 | sort.Sort(mel)
456 |
457 | // Append sorted comma-delimited k/v
458 | // pairs to the given buffer.
459 | for i, kv := range mel.s {
460 | if i != 0 {
461 | dst = append(dst, ',')
462 | }
463 | dst = append(dst, kv.keyval...)
464 | }
465 | }
466 | // The map elements must be released before
467 | // the buffer, because each k/v pair holds
468 | // two sublices that points to the buffer's
469 | // backing array.
470 | releaseMapElems(mel)
471 | bufferPool.Put(buf)
472 |
473 | return dst, err
474 | }
475 |
476 | // encodeSyncMap appends the elements of a sync.Map pointed
477 | // to by p to dst and returns the extended buffer.
478 | // This function replicates the behavior of encoding Go maps,
479 | // by returning an error for keys that are not of type string
480 | // or int, or that does not implement encoding.TextMarshaler.
481 | func encodeSyncMap(p unsafe.Pointer, dst []byte, opts encOpts) ([]byte, error) {
482 | sm := (*sync.Map)(p)
483 | dst = append(dst, '{')
484 |
485 | // The sync.Map type does not have a Len() method to
486 | // determine if it has no entries, to bail out early,
487 | // so we just range over it to encode all available
488 | // entries.
489 | // If an error arises while encoding a key or a value,
490 | // the error is stored and the method used by Range()
491 | // returns false to stop the map's iteration.
492 | var err error
493 | if opts.flags.has(unsortedMap) {
494 | dst, err = encodeUnsortedSyncMap(sm, dst, opts)
495 | } else {
496 | dst, err = encodeSortedSyncMap(sm, dst, opts)
497 | }
498 | if err != nil {
499 | return dst, err
500 | }
501 | return append(dst, '}'), nil
502 | }
503 |
504 | // encodeUnsortedSyncMap is similar to encodeUnsortedMap
505 | // but operates on a sync.Map type instead of a Go map.
506 | func encodeUnsortedSyncMap(sm *sync.Map, dst []byte, opts encOpts) ([]byte, error) {
507 | var (
508 | n int
509 | err error
510 | )
511 | sm.Range(func(key, value interface{}) bool {
512 | if n != 0 {
513 | dst = append(dst, ',')
514 | }
515 | // Encode the key.
516 | if dst, err = appendSyncMapKey(dst, key, opts); err != nil {
517 | return false
518 | }
519 | dst = append(dst, ':')
520 |
521 | // Encode the value.
522 | if dst, err = appendJSON(dst, value, opts); err != nil {
523 | return false
524 | }
525 | n++
526 | return true
527 | })
528 | return dst, err
529 | }
530 |
531 | // encodeSortedSyncMap is similar to encodeSortedMap
532 | // but operates on a sync.Map type instead of a Go map.
533 | func encodeSortedSyncMap(sm *sync.Map, dst []byte, opts encOpts) ([]byte, error) {
534 | var (
535 | off int
536 | err error
537 | buf = cachedBuffer()
538 | mel *mapElems
539 | )
540 | if v := mapElemsPool.Get(); v != nil {
541 | mel = v.(*mapElems)
542 | } else {
543 | mel = &mapElems{s: make([]kv, 0)}
544 | }
545 | sm.Range(func(key, value interface{}) bool {
546 | kv := kv{}
547 |
548 | // Encode the key and store the buffer
549 | // portion to use during the later sort.
550 | if buf.B, err = appendSyncMapKey(buf.B, key, opts); err != nil {
551 | return false
552 | }
553 | // Omit quotes of keys.
554 | kv.key = buf.B[off+1 : len(buf.B)-1]
555 |
556 | // Add separator after key.
557 | buf.B = append(buf.B, ':')
558 |
559 | // Encode the value and store the buffer
560 | // portion corresponding to the semicolon
561 | // delimited key/value pair.
562 | if buf.B, err = appendJSON(buf.B, value, opts); err != nil {
563 | return false
564 | }
565 | kv.keyval = buf.B[off:len(buf.B)]
566 | mel.s = append(mel.s, kv)
567 | off = len(buf.B)
568 |
569 | return true
570 | })
571 | if err == nil {
572 | // Sort map entries by key in
573 | // lexicographical order.
574 | sort.Sort(mel)
575 |
576 | // Append sorted comma-delimited k/v
577 | // pairs to the given buffer.
578 | for i, kv := range mel.s {
579 | if i != 0 {
580 | dst = append(dst, ',')
581 | }
582 | dst = append(dst, kv.keyval...)
583 | }
584 | }
585 | releaseMapElems(mel)
586 | bufferPool.Put(buf)
587 |
588 | return dst, err
589 | }
590 |
591 | func appendSyncMapKey(dst []byte, key interface{}, opts encOpts) ([]byte, error) {
592 | if key == nil {
593 | return dst, errors.New("unsupported nil key in sync.Map")
594 | }
595 | kt := reflect.TypeOf(key)
596 | var (
597 | isStr = isString(kt)
598 | isInt = isInteger(kt)
599 | isTxt = kt.Implements(textMarshalerType)
600 | )
601 | if !isStr && !isInt && !isTxt {
602 | return dst, fmt.Errorf("unsupported key of type %s in sync.Map", kt)
603 | }
604 | var err error
605 |
606 | // Quotes the key if the type is not
607 | // encoded with quotes by default.
608 | quoted := !isStr && !isTxt
609 |
610 | // Ensure map key precedence for keys of type
611 | // string by using the encodeString function
612 | // directly instead of the generic appendJSON.
613 | if isStr {
614 | dst, err = encodeString(unpackEface(key).word, dst, opts)
615 | runtime.KeepAlive(key)
616 | } else {
617 | if quoted {
618 | dst = append(dst, '"')
619 | }
620 | dst, err = appendJSON(dst, key, opts)
621 | }
622 | if err != nil {
623 | return dst, err
624 | }
625 | if quoted {
626 | dst = append(dst, '"')
627 | }
628 | return dst, nil
629 | }
630 |
631 | func encodeMarshaler(
632 | p unsafe.Pointer, dst []byte, opts encOpts, t reflect.Type, canAddr bool, fn marshalerEncodeFunc,
633 | ) ([]byte, error) {
634 | // The content of this function and packEface
635 | // is similar to the following code using the
636 | // reflect package.
637 | //
638 | // v := reflect.NewAt(t, p)
639 | // if !canAddr {
640 | // v = v.Elem()
641 | // k := v.Kind()
642 | // if (k == reflect.Ptr || k == reflect.Interface) && v.IsNil() {
643 | // return append(dst, "null"...), nil
644 | // }
645 | // } else if v.IsNil() {
646 | // return append(dst, "null"...), nil
647 | // }
648 | // return fn(v.Interface(), dst, opts, t)
649 | //
650 | if !canAddr {
651 | if t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface {
652 | if *(*unsafe.Pointer)(p) == nil {
653 | return append(dst, "null"...), nil
654 | }
655 | }
656 | } else {
657 | if p == nil {
658 | return append(dst, "null"...), nil
659 | }
660 | t = reflect.PtrTo(t)
661 | }
662 | var i interface{}
663 |
664 | if t.Kind() == reflect.Interface {
665 | // Special case: return the element inside the
666 | // interface. The empty interface has one layout,
667 | // all interfaces with methods have another one.
668 | if t.NumMethod() == 0 {
669 | i = *(*interface{})(p)
670 | } else {
671 | i = *(*interface{ M() })(p)
672 | }
673 | } else {
674 | i = packEface(p, t, t.Kind() == reflect.Ptr && !canAddr)
675 | }
676 | return fn(i, dst, opts, t)
677 | }
678 |
679 | func encodeAppendMarshalerCtx(
680 | i interface{}, dst []byte, opts encOpts, t reflect.Type,
681 | ) ([]byte, error) {
682 | dst2, err := i.(AppendMarshalerCtx).AppendJSONContext(opts.ctx, dst)
683 | if err != nil {
684 | return dst, &MarshalerError{t, err, marshalerAppendJSONCtx}
685 | }
686 | return dst2, nil
687 | }
688 |
689 | func encodeAppendMarshaler(
690 | i interface{}, dst []byte, _ encOpts, t reflect.Type,
691 | ) ([]byte, error) {
692 | dst2, err := i.(AppendMarshaler).AppendJSON(dst)
693 | if err != nil {
694 | return dst, &MarshalerError{t, err, marshalerAppendJSON}
695 | }
696 | return dst2, nil
697 | }
698 |
699 | func encodeJSONMarshaler(i interface{}, dst []byte, opts encOpts, t reflect.Type) ([]byte, error) {
700 | b, err := i.(json.Marshaler).MarshalJSON()
701 | if err != nil {
702 | return dst, &MarshalerError{t, err, marshalerJSON}
703 | }
704 | if opts.flags.has(noCompact) {
705 | return append(dst, b...), nil
706 | }
707 | // This is redundant with the parsing done
708 | // by appendCompactJSON, but for the time
709 | // being, we can't use the scanner of the
710 | // standard library.
711 | if !json.Valid(b) {
712 | return dst, &MarshalerError{t, &SyntaxError{
713 | msg: "json: invalid value",
714 | }, marshalerJSON}
715 | }
716 | return appendCompactJSON(dst, b, !opts.flags.has(noHTMLEscaping))
717 | }
718 |
719 | func encodeTextMarshaler(i interface{}, dst []byte, _ encOpts, t reflect.Type) ([]byte, error) {
720 | b, err := i.(encoding.TextMarshaler).MarshalText()
721 | if err != nil {
722 | return dst, &MarshalerError{t, err, marshalerText}
723 | }
724 | dst = append(dst, '"')
725 | dst = append(dst, b...)
726 | dst = append(dst, '"')
727 |
728 | return dst, nil
729 | }
730 |
731 | // appendCompactJSON appends to dst the JSON-encoded src
732 | // with insignificant space characters elided. If escHTML
733 | // is true, HTML-characters are also escaped.
734 | func appendCompactJSON(dst, src []byte, escHTML bool) ([]byte, error) {
735 | var (
736 | inString bool
737 | skipNext bool
738 | )
739 | at := 0 // accumulated bytes start index
740 |
741 | for i, c := range src {
742 | if escHTML {
743 | // Escape HTML characters.
744 | if c == '<' || c == '>' || c == '&' {
745 | if at < i {
746 | dst = append(dst, src[at:i]...)
747 | }
748 | dst = append(dst, `\u00`...)
749 | dst = append(dst, hex[c>>4], hex[c&0xF])
750 | at = i + 1
751 | continue
752 | }
753 | }
754 | // Convert U+2028 and U+2029.
755 | // (E2 80 A8 and E2 80 A9).
756 | if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
757 | if at < i {
758 | dst = append(dst, src[at:i]...)
759 | }
760 | dst = append(dst, `\u202`...)
761 | dst = append(dst, hex[src[i+2]&0xF])
762 | at = i + 3
763 | continue
764 | }
765 | if !inString {
766 | switch c {
767 | case '"':
768 | // Within a string, we don't elide
769 | // insignificant space characters.
770 | inString = true
771 | case ' ', '\n', '\r', '\t':
772 | // Append the accumulated bytes,
773 | // and skip the current character.
774 | if at < i {
775 | dst = append(dst, src[at:i]...)
776 | }
777 | at = i + 1
778 | }
779 | continue
780 | }
781 | // Next character is escaped, and must
782 | // not be interpreted as the end of a
783 | // string by mistake.
784 | if skipNext {
785 | skipNext = false
786 | continue
787 | }
788 | // Next character must be skipped.
789 | if c == '\\' {
790 | skipNext = true
791 | continue
792 | }
793 | // Leaving a string value.
794 | if c == '"' {
795 | inString = false
796 | }
797 | }
798 | if at < len(src) {
799 | dst = append(dst, src[at:]...)
800 | }
801 | return dst, nil
802 | }
803 |
804 | func appendEscapedBytes(dst []byte, b []byte, opts encOpts) []byte {
805 | if opts.flags.has(noStringEscaping) {
806 | return append(dst, b...)
807 | }
808 | var (
809 | i = 0
810 | at = 0
811 | )
812 | noCoerce := opts.flags.has(noUTF8Coercion)
813 | noEscape := opts.flags.has(noHTMLEscaping)
814 |
815 | for i < len(b) {
816 | if c := b[i]; c < utf8.RuneSelf {
817 | // Check whether c can be used in a JSON string
818 | // without escaping, or it is a problematic HTML
819 | // character.
820 | if c >= ' ' && c != '\\' && c != '"' && (noEscape || (c != '<' && c != '>' && c != '&')) {
821 | // If the current character doesn't need
822 | // to be escaped, accumulate the bytes to
823 | // save some operations.
824 | i++
825 | continue
826 | }
827 | // Write accumulated single-byte characters.
828 | if at < i {
829 | dst = append(dst, b[at:i]...)
830 | }
831 | // The encoding/json package implements only
832 | // a few of the special two-character escape
833 | // sequence described in the RFC 8259, Section 7.
834 | // \b and \f were ignored on purpose, see
835 | // https://codereview.appspot.com/4678046.
836 | switch c {
837 | case '"', '\\':
838 | dst = append(dst, '\\', c)
839 | case '\n': // 0xA, line feed
840 | dst = append(dst, '\\', 'n')
841 | case '\r': // 0xD, carriage return
842 | dst = append(dst, '\\', 'r')
843 | case '\t': // 0x9, horizontal tab
844 | dst = append(dst, '\\', 't')
845 | default:
846 | dst = append(dst, `\u00`...)
847 | dst = append(dst, hex[c>>4])
848 | dst = append(dst, hex[c&0xF])
849 | }
850 | i++
851 | at = i
852 | continue
853 | }
854 | r, size := utf8.DecodeRune(b[i:])
855 |
856 | if !noCoerce {
857 | // Coerce to valid UTF-8, by replacing invalid
858 | // bytes with the Unicode replacement rune.
859 | if r == utf8.RuneError && size == 1 {
860 | if at < i {
861 | dst = append(dst, b[at:i]...)
862 | }
863 | dst = append(dst, `\ufffd`...)
864 | i += size
865 | at = i
866 | continue
867 | }
868 | // U+2028 is LINE SEPARATOR.
869 | // U+2029 is PARAGRAPH SEPARATOR.
870 | // They are both technically valid characters in
871 | // JSON strings, but don't work in JSONP, which has
872 | // to be evaluated as JavaScript, and can lead to
873 | // security holes there. It is valid JSON to escape
874 | // them, so we do so unconditionally.
875 | // See http://timelessrepo.com/json-isnt-a-javascript-subset.
876 | if r == '\u2028' || r == '\u2029' {
877 | if at < i {
878 | dst = append(dst, b[at:i]...)
879 | }
880 | dst = append(dst, `\u202`...)
881 | dst = append(dst, hex[r&0xF])
882 | i += size
883 | at = i
884 | continue
885 | }
886 | i += size
887 | continue
888 | }
889 | i += size
890 | }
891 | if at < len(b) {
892 | dst = append(dst, b[at:]...)
893 | }
894 | return dst
895 | }
896 |
--------------------------------------------------------------------------------
/json_test.go:
--------------------------------------------------------------------------------
1 | package jettison
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "crypto/rand"
7 | "encoding/json"
8 | "errors"
9 | "fmt"
10 | "math"
11 | "math/big"
12 | "net"
13 | "reflect"
14 | "regexp"
15 | "strconv"
16 | "sync"
17 | "testing"
18 | "time"
19 | )
20 |
21 | type (
22 | jmr string
23 | jmv []string
24 | )
25 |
26 | func (*jmr) MarshalJSON() ([]byte, error) { return []byte(`"XYZ"`), nil }
27 | func (jmv) MarshalJSON() ([]byte, error) { return []byte(`"ZYX"`), nil }
28 |
29 | type (
30 | mapss struct {
31 | M map[string]string
32 | }
33 | inner struct {
34 | M map[string]string
35 | }
36 | outer struct {
37 | M map[string]inner
38 | }
39 | z struct {
40 | S []byte
41 | }
42 | y struct {
43 | P float64 `json:"p,omitempty"`
44 | Q uint64 `json:"q,omitempty"`
45 | R uint8
46 | }
47 | x struct {
48 | A *string `json:"a,string"`
49 | B1 int64 `json:"b1,string"`
50 | B2 uint16 `json:"b2"`
51 | C *bool `json:"c,string"`
52 | D float32
53 | E1 *[]int
54 | E2 []string
55 | E3 []jmr
56 | F1 [4]string
57 | F2 [1]jmr
58 | F3 *[1]jmr
59 | G1 map[int]*string
60 | G2 map[string]*map[string]string
61 | G3 map[int]map[string]map[int]string
62 | G4 map[string]mapss
63 | G5 outer
64 | G6 map[int]jmr
65 | G7 map[int]*jmr
66 | G8 map[int]bool `json:",omitempty"`
67 | H1 jmr
68 | H2 *jmr
69 | H3 jmv
70 | H4 *jmv
71 | I time.Time
72 | J time.Duration
73 | K json.Number
74 | L json.RawMessage
75 | M1 interface{}
76 | M2 interface{}
77 | N struct{}
78 | X *x
79 | *y
80 | z `json:"z"`
81 | }
82 | )
83 |
84 | var (
85 | s = "Loreum"
86 | b = true
87 | m = map[string]string{"b": "c"}
88 | xx = x{
89 | A: &s,
90 | B1: -42,
91 | B2: 42,
92 | C: &b,
93 | D: math.MaxFloat32,
94 | E1: &[]int{1, 2, 3},
95 | E2: []string{"x", "y", "z"},
96 | E3: []jmr{"1"},
97 | F1: [4]string{"a", "b", "c", "d"},
98 | F2: [1]jmr{"1"},
99 | F3: &[1]jmr{"1"},
100 | G1: map[int]*string{2: &s, 3: new(string)},
101 | G2: map[string]*map[string]string{"a": &m},
102 | G3: map[int]map[string]map[int]string{1: {"a": {2: "b"}}},
103 | G4: map[string]mapss{"1": {M: map[string]string{"2": "3"}}},
104 | G5: outer{map[string]inner{"outer": {map[string]string{"key": "val"}}}},
105 | G6: map[int]jmr{1: "jmr"},
106 | G7: map[int]*jmr{1: new(jmr)},
107 | G8: map[int]bool{},
108 | H1: "jmp",
109 | H2: nil,
110 | H3: nil,
111 | H4: nil,
112 | I: time.Now(),
113 | J: 3 * time.Minute,
114 | K: "3.14",
115 | L: []byte(`{ "a":"b" }`),
116 | M1: uint32(255),
117 | M2: &s,
118 | X: &x{H1: "jmv"},
119 | y: &y{R: math.MaxUint8},
120 | z: z{S: []byte("Loreum")},
121 | }
122 | )
123 |
124 | // marshalCompare compares the JSON encoding
125 | // of v between Jettison and encoding/json.
126 | func marshalCompare(t *testing.T, v interface{}, name string) {
127 | jb1, err := Marshal(v)
128 | if err != nil {
129 | t.Fatal(err)
130 | }
131 | jb2, err := MarshalOpts(v)
132 | if err != nil {
133 | t.Fatal(err)
134 | }
135 | if !bytes.Equal(jb1, jb2) {
136 | t.Error("non-equal outputs for Marshal and MarshalOpts")
137 | }
138 | jb3, err := Append([]byte(nil), v)
139 | if err != nil {
140 | t.Fatal(err)
141 | }
142 | if !bytes.Equal(jb1, jb3) {
143 | t.Error("non-equal outputs for Marshal and Append")
144 | }
145 | sb, err := json.Marshal(v)
146 | if err != nil {
147 | t.Fatal(err)
148 | }
149 | t.Logf("standard: %s", string(sb))
150 | t.Logf("jettison: %s", string(jb1))
151 |
152 | if !bytes.Equal(jb1, sb) {
153 | t.Errorf("%s: non-equal outputs", name)
154 | }
155 | }
156 |
157 | func marshalCompareError(t *testing.T, v interface{}, name string) {
158 | _, errj := Marshal(v)
159 | if errj == nil {
160 | t.Fatalf("expected non-nil error")
161 | }
162 | _, errs := json.Marshal(v)
163 | if errs == nil {
164 | t.Fatalf("expected non-nil error")
165 | }
166 | t.Logf("standard: %s", errs)
167 | t.Logf("jettison: %s", errj)
168 |
169 | if errs.Error() != errj.Error() {
170 | t.Errorf("%s: non-equal outputs", name)
171 | }
172 | }
173 |
174 | func TestAll(t *testing.T) {
175 | marshalCompare(t, nil, "nil")
176 | marshalCompare(t, xx, "non-pointer")
177 | marshalCompare(t, &xx, "pointer")
178 | }
179 |
180 | func TestInvalidEncodeOpts(t *testing.T) {
181 | for _, opt := range []Option{
182 | TimeLayout(""),
183 | DurationFormat(DurationFmt(-1)),
184 | DurationFormat(DurationFmt(6)),
185 | WithContext(nil), // nolint:staticcheck
186 | } {
187 | _, err1 := MarshalOpts(struct{}{}, opt)
188 | _, err2 := AppendOpts([]byte(nil), struct{}{}, opt)
189 |
190 | for _, err := range []error{err1, err2} {
191 | if err != nil {
192 | e, ok := err.(*InvalidOptionError)
193 | if !ok {
194 | t.Errorf("got %T, want InvalidOptionError", err)
195 | }
196 | if e.Error() == "" {
197 | t.Errorf("expected non-empty error message")
198 | }
199 | } else {
200 | t.Error("expected non-nil error")
201 | }
202 | }
203 | }
204 | }
205 |
206 | // TestBasicTypes tests the marshaling of basic types.
207 | func TestBasicTypes(t *testing.T) {
208 | testdata := []interface{}{
209 | true,
210 | false,
211 | "Loreum",
212 | int8(math.MaxInt8),
213 | int16(math.MaxInt16),
214 | int32(math.MaxInt32),
215 | int64(math.MaxInt64),
216 | uint8(math.MaxUint8),
217 | uint16(math.MaxUint16),
218 | uint32(math.MaxUint32),
219 | uint64(math.MaxUint64),
220 | uintptr(0xBEEF),
221 | (*bool)(nil),
222 | (*int)(nil),
223 | (*string)(nil),
224 | }
225 | for _, v := range testdata {
226 | marshalCompare(t, v, "")
227 | }
228 | }
229 |
230 | // TestCompositeTypes tests the marshaling of composite types.
231 | func TestCompositeTypes(t *testing.T) {
232 | var (
233 | jmref = jmr("jmr")
234 | jmval = jmv([]string{"a", "b", "c"})
235 | )
236 | testdata := []interface{}{
237 | []uint{},
238 | []int{1, 2, 3},
239 | []int(nil),
240 | (*[]int)(nil),
241 | []string{"a", "b", "c"},
242 | [2]bool{true, false},
243 | (*[4]string)(nil),
244 | map[string]int{"a": 1, "b": 2},
245 | &map[int]string{1: "a", 2: "b"},
246 | (map[string]int)(nil),
247 | time.Now(),
248 | 3*time.Minute + 35*time.Second,
249 | jmref,
250 | &jmref,
251 | jmval,
252 | &jmval,
253 | }
254 | for _, v := range testdata {
255 | marshalCompare(t, v, "")
256 | }
257 | }
258 |
259 | // TestUnsupportedTypes tests that marshaling an
260 | // unsupported type such as channel, complex, and
261 | // function value returns an UnsupportedTypeError.
262 | // The error message is compared with the one that
263 | // is returned by json.Marshal.
264 | func TestUnsupportedTypes(t *testing.T) {
265 | testdata := []interface{}{
266 | make(chan int),
267 | func() {},
268 | complex64(0),
269 | complex128(0),
270 | make([]chan int, 1),
271 | [1]complex64{},
272 | &[1]complex128{},
273 | map[int]chan bool{1: make(chan bool)},
274 | struct{ F func() }{func() {}},
275 | &struct{ C complex64 }{0},
276 | }
277 | for _, v := range testdata {
278 | marshalCompareError(t, v, "")
279 | }
280 | }
281 |
282 | // TestInvalidFloatValues tests that encoding an
283 | // invalid float value returns UnsupportedValueError.
284 | func TestInvalidFloatValues(t *testing.T) {
285 | for _, v := range []float64{
286 | math.NaN(),
287 | math.Inf(-1),
288 | math.Inf(1),
289 | } {
290 | _, err := Marshal(v)
291 | if err != nil {
292 | if _, ok := err.(*UnsupportedValueError); !ok {
293 | t.Errorf("got %T, want UnsupportedValueError", err)
294 | }
295 | } else {
296 | t.Error("got nil, want non-nil error")
297 | }
298 | // Error message must be the same as
299 | // the one of the standard library.
300 | marshalCompareError(t, v, "")
301 | }
302 | }
303 |
304 | // TestJSONNumber tests that a json.Number literal value
305 | // can be marshaled, and that an error is returned if it
306 | // isn't a valid number according to the JSON grammar.
307 | func TestJSONNumber(t *testing.T) {
308 | valid := []json.Number{
309 | "42",
310 | "-42",
311 | "24.42",
312 | "-666.66",
313 | "3.14",
314 | "-3.14",
315 | "1e3",
316 | "1E-6",
317 | "1E+42",
318 | // Special case to keep backward
319 | // compatibility with Go1.5, that
320 | // encodes the empty string as "0".
321 | "",
322 | }
323 | for _, v := range valid {
324 | marshalCompare(t, v, "valid")
325 | }
326 | invalid := []json.Number{
327 | "1E+4.0",
328 | "084",
329 | "-03.14",
330 | "-",
331 | "invalid",
332 | }
333 | for _, v := range invalid {
334 | marshalCompareError(t, v, "invalid")
335 | }
336 | }
337 |
338 | func TestInvalidTime(t *testing.T) {
339 | // Special case to test error when the year
340 | // of the date is outside of range [0.9999].
341 | // see golang.org/issue/4556#c15.
342 | for _, tm := range []time.Time{
343 | time.Date(-1, time.January, 1, 0, 0, 0, 0, time.UTC),
344 | time.Date(10000, time.January, 1, 0, 0, 0, 0, time.UTC),
345 | } {
346 | _, err := Marshal(tm)
347 | if err != nil {
348 | want := "time: year outside of range [0,9999]"
349 | if err.Error() != want {
350 | t.Errorf("got %q, want %q", err.Error(), want)
351 | }
352 | } else {
353 | t.Error("got nil, want non-nil error")
354 | }
355 | }
356 | }
357 |
358 | // TestRenamedByteSlice tests that a name type
359 | // that represents a slice of bytes is marshaled
360 | // the same way as a regular byte slice.
361 | func TestRenamedByteSlice(t *testing.T) {
362 | type (
363 | b byte
364 | b1 []byte
365 | b2 []b
366 | )
367 | testdata := []interface{}{
368 | b1("byte slice 1"),
369 | b2("byte slice 2"),
370 | }
371 | for _, v := range testdata {
372 | marshalCompare(t, v, "")
373 | }
374 | }
375 |
376 | func TestByteSliceSizes(t *testing.T) {
377 | makeSlice := func(size int) []byte {
378 | b := make([]byte, size)
379 | if _, err := rand.Read(b); err != nil {
380 | t.Fatal(err)
381 | }
382 | return b
383 | }
384 | for _, v := range []interface{}{
385 | makeSlice(0),
386 | makeSlice(1024),
387 | makeSlice(2048),
388 | makeSlice(4096),
389 | makeSlice(8192),
390 | } {
391 | marshalCompare(t, v, "")
392 | }
393 | }
394 |
395 | // TestSortedSyncMap tests the marshaling
396 | // of a sorted sync.Map value.
397 | func TestSortedSyncMap(t *testing.T) {
398 | var sm sync.Map
399 |
400 | sm.Store(1, "one")
401 | sm.Store("a", 42)
402 | sm.Store("b", false)
403 | sm.Store(mkvstrMarshaler("c"), -42)
404 | sm.Store(mkrstrMarshaler("d"), true)
405 | sm.Store(mkvintMarshaler(42), 1)
406 | sm.Store(mkrintMarshaler(42), 2)
407 |
408 | b, err := Marshal(&sm)
409 | if err != nil {
410 | t.Fatal(err)
411 | }
412 | want := `{"1":"one","42":2,"MKVINT":1,"a":42,"b":false,"c":-42,"d":true}`
413 |
414 | if !bytes.Equal(b, []byte(want)) {
415 | t.Errorf("got %#q, want %#q", b, want)
416 | }
417 | }
418 |
419 | // TestUnsortedSyncMap tests the marshaling
420 | // of an unsorted sync.Map value.
421 | func TestUnsortedSyncMap(t *testing.T) {
422 | // entries maps each interface k/v
423 | // pair to the string representation
424 | // of the key in payload.
425 | entries := map[string]struct {
426 | key interface{}
427 | val interface{}
428 | }{
429 | "1": {1, "one"},
430 | "a": {"a", 42},
431 | "b": {"b", false},
432 | "c": {mkvstrMarshaler("c"), -42},
433 | "d": {mkrstrMarshaler("d"), true},
434 | "MKVINT": {mkvintMarshaler(42), 1},
435 | "42": {mkrintMarshaler(42), 2},
436 | }
437 | var sm sync.Map
438 | for _, e := range entries {
439 | sm.Store(e.key, e.val)
440 | }
441 | bts, err := MarshalOpts(&sm, UnsortedMap())
442 | if err != nil {
443 | t.Fatal(err)
444 | }
445 | m := make(map[string]interface{})
446 | if err := json.Unmarshal(bts, &m); err != nil {
447 | t.Fatal(err)
448 | }
449 | // Unmarshaled map must contain exactly the
450 | // number of entries added to the sync map.
451 | if g, w := len(m), len(entries); g != w {
452 | t.Errorf("invalid lengths: got %d, want %d", g, w)
453 | }
454 | for k, v := range m {
455 | // Compare the marshaled representation
456 | // of each value to avoid false-positive
457 | // between integer and float types.
458 | b1, err1 := json.Marshal(v)
459 | b2, err2 := json.Marshal(entries[k].val)
460 | if err1 != nil {
461 | t.Fatal(err)
462 | }
463 | if err2 != nil {
464 | t.Fatal(err2)
465 | }
466 | if !bytes.Equal(b1, b2) {
467 | t.Errorf("for key %s: got %v, want %v", k, b1, b2)
468 | }
469 | }
470 | }
471 |
472 | // TestInvalidSyncMapKeys tests that marshaling a
473 | // sync.Map with unsupported key types returns an
474 | // error.
475 | func TestInvalidSyncMapKeys(t *testing.T) {
476 | testInvalidSyncMapKeys(t, true)
477 | testInvalidSyncMapKeys(t, false)
478 | }
479 |
480 | func testInvalidSyncMapKeys(t *testing.T, sorted bool) {
481 | for _, f := range []func(sm *sync.Map){
482 | func(sm *sync.Map) { sm.Store(false, nil) },
483 | func(sm *sync.Map) { sm.Store(new(int), nil) },
484 | func(sm *sync.Map) { sm.Store(nil, nil) },
485 | } {
486 | var (
487 | sm sync.Map
488 | err error
489 | )
490 | f(&sm) // add entries to sm
491 | if sorted {
492 | _, err = Marshal(&sm)
493 | } else {
494 | _, err = MarshalOpts(&sm, UnsortedMap())
495 | }
496 | if err == nil {
497 | t.Error("expected a non-nil error")
498 | }
499 | }
500 | }
501 |
502 | // TestCompositeMapValue tests the marshaling
503 | // of maps with composite values.
504 | func TestCompositeMapValue(t *testing.T) {
505 | type x struct {
506 | A string `json:"a"`
507 | B int `json:"b"`
508 | C bool `json:"c"`
509 | }
510 | type y []uint32
511 |
512 | for _, v := range []interface{}{
513 | map[string]x{
514 | "1": {A: "A", B: 42, C: true},
515 | "2": {A: "A", B: 84, C: false},
516 | },
517 | map[string]y{
518 | "3": {7, 8, 9},
519 | "2": {4, 5, 6},
520 | "1": nil,
521 | },
522 | map[string]*x{
523 | "b": {A: "A", B: 128, C: true},
524 | "a": nil,
525 | "c": {},
526 | },
527 | map[string]interface{}{
528 | "1": 42,
529 | "2": "two",
530 | "3": nil,
531 | "4": (*int64)(nil),
532 | "5": x{A: "A"},
533 | "6": &x{A: "A", B: 256, C: true},
534 | },
535 | } {
536 | marshalCompare(t, v, "")
537 | }
538 | }
539 |
540 | type (
541 | mkstr string
542 | mkint int64
543 | mkvstrMarshaler string
544 | mkrstrMarshaler string
545 | mkvintMarshaler uint64
546 | mkrintMarshaler int
547 | mkvcmpMarshaler struct{}
548 | )
549 |
550 | func (mkvstrMarshaler) MarshalText() ([]byte, error) { return []byte("MKVSTR"), nil }
551 | func (*mkrstrMarshaler) MarshalText() ([]byte, error) { return []byte("MKRSTR"), nil }
552 | func (mkvintMarshaler) MarshalText() ([]byte, error) { return []byte("MKVINT"), nil }
553 | func (*mkrintMarshaler) MarshalText() ([]byte, error) { return []byte("MKRINT"), nil }
554 | func (mkvcmpMarshaler) MarshalText() ([]byte, error) { return []byte("MKVCMP"), nil }
555 |
556 | // TestMapKeyPrecedence tests that the precedence
557 | // order of map key types is respected during marshaling.
558 | func TestMapKeyPrecedence(t *testing.T) {
559 | testdata := []interface{}{
560 | map[mkstr]string{"K": "V"},
561 | map[mkint]string{1: "V"},
562 | map[mkvstrMarshaler]string{"K": "V"},
563 | map[mkrstrMarshaler]string{"K": "V"},
564 | map[mkvintMarshaler]string{42: "V"},
565 | map[mkrintMarshaler]string{1: "one"},
566 | map[mkvcmpMarshaler]string{{}: "V"},
567 | }
568 | for _, v := range testdata {
569 | marshalCompare(t, v, "")
570 | }
571 | }
572 |
573 | // TestJSONMarshaler tests that a type implementing the
574 | // json.Marshaler interface is marshaled using the result
575 | // of its MarshalJSON method call result.
576 | // Because the types big.Int and time.Time also implements
577 | // the encoding.TextMarshaler interface, the test ensures
578 | // that MarshalJSON has priority.
579 | func TestJSONMarshaler(t *testing.T) {
580 | type x struct {
581 | T1 time.Time `json:""`
582 | T2 time.Time `json:",omitempty"`
583 | T3 *time.Time `json:""`
584 | T4 *time.Time `json:""` // nil
585 | T5 *time.Time `json:",omitempty"` // nil
586 | S1 bvjm `json:",omitempty"`
587 | S2 bvjm `json:",omitempty"`
588 | S3 bvjm `json:""`
589 | S4 *bvjm `json:""`
590 | S5 *bvjm `json:""` // nil
591 | S6 *bvjm `json:",omitempty"` // nil
592 | I1 big.Int `json:""`
593 | I2 big.Int `json:",omitempty"`
594 | I3 *big.Int `json:""`
595 | I4 *big.Int `json:""` // nil
596 | I5 *big.Int `json:",omitempty"` // nil
597 | P1 brjm `json:",omitempty"`
598 | P2 brjm `json:",omitempty"`
599 | P3 brjm `json:""`
600 | P4 *brjm `json:""`
601 | P5 *brjm `json:""` // nil
602 | P6 *brjm `json:",omitempty"` // nil
603 |
604 | // NOTE
605 | // time.Time = Non-pointer receiver of composite type.
606 | // bvjm = Non-pointer receiver of basic type.
607 | // big.Int = Pointer receiver of composite type.
608 | // brjm = Pointer receiver of basic type.
609 | }
610 | var (
611 | now = time.Now()
612 | bval = bvjm("bval")
613 | bref = brjm("bref")
614 | xx = x{
615 | T1: now,
616 | T3: &now,
617 | S1: "S1",
618 | S4: &bval,
619 | I1: *big.NewInt(math.MaxInt64),
620 | I3: big.NewInt(math.MaxInt64),
621 | P1: "P1",
622 | P4: &bref,
623 | }
624 | )
625 | marshalCompare(t, xx, "non-pointer")
626 | marshalCompare(t, &xx, "pointer")
627 | }
628 |
629 | // TestTextMarshaler tests that a type implementing
630 | // the encoding.TextMarshaler interface encodes to a
631 | // quoted string of its MashalText method result.
632 | func TestTextMarshaler(t *testing.T) {
633 | type x struct {
634 | S1 net.IP `json:""`
635 | S2 net.IP `json:",omitempty"`
636 | S3 *net.IP `json:""`
637 | S4 *net.IP `json:""` // nil
638 | S5 *net.IP `json:",omitempty"` // nil
639 | I1 bvtm `json:",omitempty"`
640 | I2 bvtm `json:",omitempty"`
641 | I3 bvtm `json:""`
642 | I4 *bvtm `json:""`
643 | I5 *bvtm `json:""` // nil
644 | I6 *bvtm `json:",omitempty"` // nil
645 | F1 big.Float `json:""`
646 | F2 big.Float `json:",omitempty"`
647 | F3 *big.Float `json:""`
648 | F4 *big.Float `json:""` // nil
649 | F5 *big.Float `json:",omitempty"` // nil
650 | P1 brtm `json:",omitempty"`
651 | P2 brtm `json:",omitempty"`
652 | P3 brtm `json:""`
653 | P4 *brtm `json:""`
654 | P5 *brtm `json:""` // nil
655 | P6 *brtm `json:",omitempty"` // nil
656 |
657 | // NOTE
658 | // net.IP = Non-pointer receiver of composite type.
659 | // bvtm = Non-pointer receiver of basic type.
660 | // big.Float = Pointer receiver of composite type.
661 | // brtm = Pointer receiver of basic type.
662 | }
663 | var (
664 | bval = bvtm(42)
665 | bref = brtm(42)
666 | xx = x{
667 | S1: net.IP{192, 168, 0, 1},
668 | S3: &net.IP{127, 0, 0, 1},
669 | I1: 42,
670 | I4: &bval,
671 | F1: *big.NewFloat(math.MaxFloat64),
672 | F3: big.NewFloat(math.MaxFloat64),
673 | P1: 42,
674 | P4: &bref,
675 | }
676 | )
677 | marshalCompare(t, xx, "non-pointer")
678 | marshalCompare(t, &xx, "pointer")
679 | }
680 |
681 | type (
682 | bvm string
683 | brm string
684 | cvm struct{}
685 | crm struct{}
686 | )
687 |
688 | func (m bvm) AppendJSON(dst []byte) ([]byte, error) {
689 | return append(dst, strconv.Quote(string(m))...), nil
690 | }
691 | func (m *brm) AppendJSON(dst []byte) ([]byte, error) {
692 | return append(dst, strconv.Quote(string(*m))...), nil
693 | }
694 | func (m bvm) MarshalJSON() ([]byte, error) { return []byte(strconv.Quote(string(m))), nil }
695 | func (m *brm) MarshalJSON() ([]byte, error) { return []byte(strconv.Quote(string(*m))), nil }
696 | func (cvm) AppendJSON(dst []byte) ([]byte, error) { return append(dst, `"X"`...), nil }
697 | func (cvm) MarshalJSON() ([]byte, error) { return []byte(`"X"`), nil }
698 | func (*crm) AppendJSON(dst []byte) ([]byte, error) { return append(dst, `"Y"`...), nil }
699 | func (*crm) MarshalJSON() ([]byte, error) { return []byte(`"Y"`), nil }
700 |
701 | //nolint:dupl
702 | func TestMarshaler(t *testing.T) {
703 | type x struct {
704 | S1 cvm `json:""`
705 | S2 cvm `json:",omitempty"`
706 | S3 *cvm `json:""`
707 | S4 *cvm `json:""` // nil
708 | S5 *cvm `json:",omitempty"` // nil
709 | I1 bvm `json:",omitempty"`
710 | I2 bvm `json:",omitempty"`
711 | I3 bvm `json:""`
712 | I4 *bvm `json:""`
713 | I5 *bvm `json:""` // nil
714 | I6 *bvm `json:",omitempty"` // nil
715 | F1 crm `json:""`
716 | F2 crm `json:",omitempty"`
717 | F3 *crm `json:""`
718 | F4 *crm `json:""` // nil
719 | F5 *crm `json:",omitempty"` // nil
720 | P1 brm `json:",omitempty"`
721 | P2 brm `json:",omitempty"`
722 | P3 brm `json:""`
723 | P4 *brm `json:""`
724 | P5 *brm `json:""` // nil
725 | P6 *brm `json:",omitempty"` // nil
726 |
727 | // NOTE
728 | // cvm = Non-pointer receiver of composite type.
729 | // bvm = Non-pointer receiver of basic type.
730 | // crm = Pointer receiver of composite type.
731 | // brm = Pointer receiver of basic type.
732 | }
733 | var (
734 | bval = bvm("bval")
735 | bref = brm("bref")
736 | xx = x{
737 | S1: cvm{},
738 | S3: &cvm{},
739 | I1: "I1",
740 | I4: &bval,
741 | F1: crm{},
742 | F3: &crm{},
743 | P1: "P1",
744 | P4: &bref,
745 | }
746 | )
747 | marshalCompare(t, xx, "non-pointer")
748 | marshalCompare(t, &xx, "pointer")
749 | }
750 |
751 | type (
752 | bvmctx string
753 | brmctx string
754 | cvmctx struct{}
755 | crmctx struct{}
756 | )
757 |
758 | func (m bvmctx) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
759 | return append(dst, strconv.Quote(string(m))...), nil
760 | }
761 | func (m bvmctx) MarshalJSON() ([]byte, error) {
762 | return []byte(strconv.Quote(string(m))), nil
763 | }
764 | func (m *brmctx) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
765 | return append(dst, strconv.Quote(string(*m))...), nil
766 | }
767 | func (m *brmctx) MarshalJSON() ([]byte, error) {
768 | return []byte(strconv.Quote(string(*m))), nil
769 | }
770 | func (cvmctx) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
771 | return append(dst, `"X"`...), nil
772 | }
773 | func (cvmctx) MarshalJSON() ([]byte, error) {
774 | return []byte(`"X"`), nil
775 | }
776 | func (*crmctx) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
777 | return append(dst, `"Y"`...), nil
778 | }
779 | func (*crmctx) MarshalJSON() ([]byte, error) {
780 | return []byte(`"Y"`), nil
781 | }
782 |
783 | //nolint:dupl
784 | func TestMarshalerCtx(t *testing.T) {
785 | type x struct {
786 | S1 cvmctx `json:""`
787 | S2 cvmctx `json:",omitempty"`
788 | S3 *cvmctx `json:""`
789 | S4 *cvmctx `json:""` // nil
790 | S5 *cvmctx `json:",omitempty"` // nil
791 | I1 bvmctx `json:",omitempty"`
792 | I2 bvmctx `json:",omitempty"`
793 | I3 bvmctx `json:""`
794 | I4 *bvmctx `json:""`
795 | I5 *bvmctx `json:""` // nil
796 | I6 *bvmctx `json:",omitempty"` // nil
797 | F1 crmctx `json:""`
798 | F2 crmctx `json:",omitempty"`
799 | F3 *crmctx `json:""`
800 | F4 *crmctx `json:""` // nil
801 | F5 *crmctx `json:",omitempty"` // nil
802 | P1 brmctx `json:",omitempty"`
803 | P2 brmctx `json:",omitempty"`
804 | P3 brmctx `json:""`
805 | P4 *brmctx `json:""`
806 | P5 *brmctx `json:""` // nil
807 | P6 *brmctx `json:",omitempty"` // nil
808 |
809 | // NOTE
810 | // cvmctx = Non-pointer receiver of composite type.
811 | // bvmctx = Non-pointer receiver of basic type.
812 | // crmctx = Pointer receiver of composite type.
813 | // brmctx = Pointer receiver of basic type.
814 | }
815 | var (
816 | bval = bvmctx("bval")
817 | bref = brmctx("bref")
818 | xx = x{
819 | S1: cvmctx{},
820 | S3: &cvmctx{},
821 | I1: "I1",
822 | I4: &bval,
823 | F1: crmctx{},
824 | F3: &crmctx{},
825 | P1: "P1",
826 | P4: &bref,
827 | }
828 | )
829 | marshalCompare(t, xx, "non-pointer")
830 | marshalCompare(t, &xx, "pointer")
831 | }
832 |
833 | type (
834 | niljetim string // jettison.Marshaler
835 | nilmjctx string // jettison.MarshalerCtx
836 | niljsonm string // json.Marshaler
837 | niltextm string // encoding.TextMarshaler
838 | )
839 |
840 | // comboMarshaler combines the json.Marshaler
841 | // and jettison.AppendMarshaler interfaces so
842 | // that tests outputs can be compared.
843 | type comboMarshaler interface {
844 | AppendMarshaler
845 | json.Marshaler
846 | }
847 |
848 | // comboMarshalerCtx combines the json.Marshaler
849 | // and jettison.AppendMarshalerCtx interfaces so
850 | // that tests outputs can be compared.
851 | type comboMarshalerCtx interface {
852 | AppendMarshalerCtx
853 | json.Marshaler
854 | }
855 |
856 | func (*niljetim) MarshalJSON() ([]byte, error) { return []byte(`"W"`), nil }
857 | func (*nilmjctx) MarshalJSON() ([]byte, error) { return []byte(`"X"`), nil }
858 | func (*niljsonm) MarshalJSON() ([]byte, error) { return []byte(`"Y"`), nil }
859 | func (*niltextm) MarshalText() ([]byte, error) { return []byte("Z"), nil }
860 |
861 | func (*niljetim) AppendJSON(dst []byte) ([]byte, error) {
862 | return append(dst, `"W"`...), nil
863 | }
864 | func (*nilmjctx) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
865 | return append(dst, `"X"`...), nil
866 | }
867 |
868 | type (
869 | errvjm struct{}
870 | errrjm struct{}
871 | errvtm struct{}
872 | errrtm struct{}
873 | errvm struct{}
874 | errrm struct{}
875 | errvmctx struct{}
876 | errrmctx struct{}
877 | )
878 |
879 | var errMarshaler = errors.New("error")
880 |
881 | func (errvjm) MarshalJSON() ([]byte, error) { return nil, errMarshaler }
882 | func (*errrjm) MarshalJSON() ([]byte, error) { return nil, errMarshaler }
883 | func (errvtm) MarshalText() ([]byte, error) { return nil, errMarshaler }
884 | func (*errrtm) MarshalText() ([]byte, error) { return nil, errMarshaler }
885 | func (errvm) AppendJSON(dst []byte) ([]byte, error) { return dst, errMarshaler }
886 | func (*errrm) AppendJSON(dst []byte) ([]byte, error) { return dst, errMarshaler }
887 |
888 | func (errvmctx) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
889 | return dst, errMarshaler
890 | }
891 | func (*errrmctx) AppendJSONContext(_ context.Context, dst []byte) ([]byte, error) {
892 | return dst, errMarshaler
893 | }
894 |
895 | // TestMarshalerError tests that a MarshalerError is
896 | // returned when a MarshalText, MarshalJSON, WriteJSON
897 | // or WriteJSONContext method returns an error.
898 | func TestMarshalerError(t *testing.T) {
899 | testdata := []interface{}{
900 | errvjm{},
901 | &errrjm{},
902 | errvtm{},
903 | &errrtm{},
904 | errvm{},
905 | &errrm{},
906 | errvmctx{},
907 | &errrmctx{},
908 | }
909 | for _, v := range testdata {
910 | _, err := Marshal(v)
911 | if err != nil {
912 | me, ok := err.(*MarshalerError)
913 | if !ok {
914 | t.Fatalf("got %T, want MarshalerError", err)
915 | }
916 | typ := reflect.TypeOf(v)
917 | if me.Type != typ {
918 | t.Errorf("got %s, want %s", me.Type, typ)
919 | }
920 | if err := me.Unwrap(); err == nil {
921 | t.Error("expected non-nil error")
922 | }
923 | if me.Error() == "" {
924 | t.Error("expected non-empty error message")
925 | }
926 | } else {
927 | t.Error("got nil, want non-nil error")
928 | }
929 | }
930 | }
931 |
932 | // TestStructFieldName tests that invalid struct
933 | // field names are ignored during marshaling.
934 | func TestStructFieldName(t *testing.T) {
935 | //nolint:staticcheck
936 | type x struct {
937 | A string `json:" "` // valid, spaces
938 | B string `json:"0123"` // valid, digits
939 | C int `json:","` // invalid, comma
940 | D int8 `json:"\\"` // invalid, backslash,
941 | E int16 `json:"\""` // invalid, quotation mark
942 | F int `json:"Вилиам"` // valid, UTF-8 runes
943 | G bool `json:""` // valid, HTML-escaped chars
944 | Aβ int
945 | }
946 | marshalCompare(t, x{}, "")
947 | }
948 |
949 | // TestStructFieldOmitempty tests that the fields of
950 | // a struct with the omitempty option are not encoded
951 | // when they have the zero-value of their type.
952 | func TestStructFieldOmitempty(t *testing.T) {
953 | type x struct {
954 | A string `json:",omitempty"`
955 | B string `json:",omitempty"`
956 | C *string `json:",omitempty"`
957 | Ca *string `json:"a,omitempty"`
958 | D *string `json:",omitempty"`
959 | E bool `json:",omitempty"`
960 | F int `json:",omitempty"`
961 | F1 int8 `json:",omitempty"`
962 | F2 int16 `json:",omitempty"`
963 | F3 int32 `json:",omitempty"`
964 | F4 int64 `json:",omitempty"`
965 | G1 uint `json:",omitempty"`
966 | G2 uint8 `json:",omitempty"`
967 | G3 uint16 `json:",omitempty"`
968 | G4 uint32 `json:",omitempty"`
969 | G5 uint64 `json:",omitempty"`
970 | G6 uintptr `json:",omitempty"`
971 | H float32 `json:",omitempty"`
972 | I float64 `json:",omitempty"`
973 | J1 map[int]int `json:",omitempty"`
974 | J2 map[int]int `json:",omitempty"`
975 | J3 map[int]int `json:",omitempty"`
976 | K1 []string `json:",omitempty"`
977 | K2 []string `json:",omitempty"`
978 | L1 [0]int `json:",omitempty"`
979 | L2 [2]int `json:",omitempty"`
980 | M1 interface{} `json:",omitempty"`
981 | M2 interface{} `json:",omitempty"`
982 | }
983 | var (
984 | s1 = "Loreum"
985 | s2 = ""
986 | xx = &x{
987 | A: "A",
988 | B: "",
989 | C: &s1,
990 | Ca: &s2,
991 | D: nil,
992 | J2: map[int]int{},
993 | J3: map[int]int{1: 42},
994 | K2: []string{"K2"},
995 | M2: (*int)(nil),
996 | }
997 | )
998 | marshalCompare(t, xx, "")
999 | }
1000 |
1001 | // TestStructFieldOmitnil tests that the fields of a
1002 | // struct with the omitnil option are not encoded
1003 | // when they have a nil value.
1004 | func TestStructFieldOmitnil(t *testing.T) {
1005 | // nolint:staticcheck
1006 | type x struct {
1007 | Sn string `json:"sn,omitnil"`
1008 | In int `json:"in,omitnil"`
1009 | Un uint `json:"un,omitnil"`
1010 | Fn float64 `json:"fn,omitnil"`
1011 | Bn bool `json:"bn,omitnil"`
1012 | Sln []string `json:"sln,omitnil"`
1013 | Mpn map[string]interface{} `json:"mpn,omitnil"`
1014 | Stn struct{} `json:"stn,omitnil"`
1015 | Ptn *string `json:"ptn,omitnil"`
1016 | Ifn interface{} `json:"ifn,omitnil"`
1017 | }
1018 | var (
1019 | xx = x{}
1020 | before = `{"sn":"","in":0,"un":0,"fn":0,"bn":false,"stn":{}}`
1021 | after = `{"sn":"","in":0,"un":0,"fn":0,"bn":false,"sln":[],"mpn":{},"stn":{},"ptn":"Loreum","ifn":42}`
1022 | )
1023 | b, err := Marshal(xx)
1024 | if err != nil {
1025 | t.Fatal(err)
1026 | }
1027 | if got := string(b); got != before {
1028 | t.Errorf("before: got: %#q, want: %#q", got, before)
1029 | }
1030 | s := "Loreum"
1031 |
1032 | xx.Sln = make([]string, 0)
1033 | xx.Mpn = map[string]interface{}{}
1034 | xx.Stn = struct{}{}
1035 | xx.Ptn = &s
1036 | xx.Ifn = 42
1037 |
1038 | b, err = Marshal(xx)
1039 | if err != nil {
1040 | t.Fatal(err)
1041 | }
1042 | if got := string(b); got != after {
1043 | t.Errorf("after: got: %#q, want: %#q", got, after)
1044 | }
1045 | }
1046 |
1047 | // TestQuotedStructFields tests that the fields of
1048 | // a struct with the string option are quoted during
1049 | // marshaling if the type support it.
1050 | //
1051 | //nolint:staticcheck
1052 | func TestQuotedStructFields(t *testing.T) {
1053 | type x struct {
1054 | A1 int `json:",string"`
1055 | A2 *int `json:",string"`
1056 | A3 *int `json:",string"`
1057 | B uint `json:",string"`
1058 | C1 bool `json:",string"`
1059 | C2 *bool `json:",string"`
1060 | D float32 `json:",string"`
1061 | E string `json:",string"`
1062 | F []int `json:",string"`
1063 | G map[int]int `json:",string"`
1064 | }
1065 | var (
1066 | i = 84
1067 | b = false
1068 | xx = &x{
1069 | A1: -42,
1070 | A2: nil,
1071 | A3: &i,
1072 | B: 42,
1073 | C1: true,
1074 | C2: &b,
1075 | D: math.Pi,
1076 | E: "E",
1077 | F: []int{1, 2, 3},
1078 | G: map[int]int{1: 2},
1079 | }
1080 | )
1081 | marshalCompare(t, xx, "")
1082 | }
1083 |
1084 | // TestBasicStructFieldTypes tests that struct
1085 | // fields of basic types can be marshaled.
1086 | func TestBasicStructFieldTypes(t *testing.T) {
1087 | type x struct {
1088 | A string `json:"a"`
1089 | B1 int `json:"b1"`
1090 | B2 int8 `json:"b2"`
1091 | B3 int16 `json:"b3"`
1092 | B4 int32 `json:"b4"`
1093 | B5 int64 `json:"b5"`
1094 | C1 uint `json:"c1"`
1095 | C2 uint8 `json:"c2"`
1096 | C3 uint16 `json:"c3"`
1097 | C4 uint32 `json:"c4"`
1098 | C5 uint64 `json:"c5"`
1099 | D1 bool `json:"d1"`
1100 | D2 bool `json:"d2"`
1101 | E float32 `json:"e"`
1102 | F float64 `json:"f"`
1103 | G string `json:"-"` // ignored
1104 | H string `json:"-,"` // use "-" as key
1105 | i string
1106 | }
1107 | xx := &x{
1108 | A: "A",
1109 | B1: -42,
1110 | B2: math.MinInt8,
1111 | B3: math.MinInt16,
1112 | B4: math.MinInt32,
1113 | B5: math.MinInt64,
1114 | C1: 42,
1115 | C2: math.MaxUint8,
1116 | C3: math.MaxUint16,
1117 | C4: math.MaxUint32,
1118 | C5: math.MaxUint64,
1119 | D1: true,
1120 | D2: false,
1121 | E: 3.14169,
1122 | F: math.MaxFloat64,
1123 | G: "ignored",
1124 | H: "not-ignored",
1125 | i: "unexported",
1126 | }
1127 | marshalCompare(t, xx, "non-pointer")
1128 | marshalCompare(t, &xx, "pointer")
1129 | }
1130 |
1131 | // TestBasicStructFieldPointerTypes tests
1132 | // that nil and non-nil struct field pointers
1133 | // of basic types can be marshaled.
1134 | func TestBasicStructFieldPointerTypes(t *testing.T) {
1135 | type x struct {
1136 | A *string `json:"a"`
1137 | B *int `json:"b"`
1138 | C *uint64 `json:"c"`
1139 | D *bool `json:"d"`
1140 | E *float32 `json:"e"`
1141 | F *float64 `json:"f"`
1142 | }
1143 | var (
1144 | a = "a"
1145 | b = 42
1146 | d = true
1147 | f = math.MaxFloat64
1148 | xx = x{A: &a, B: &b, C: nil, D: &d, E: nil, F: &f}
1149 | )
1150 | marshalCompare(t, xx, "non-pointer")
1151 | marshalCompare(t, &xx, "pointer")
1152 | }
1153 |
1154 | // TestCompositeStructFieldTypes tests that struct
1155 | // fields of composite types, such as struct, slice,
1156 | // array and map can be marshaled.
1157 | func TestCompositeStructFieldTypes(t *testing.T) {
1158 | type y struct {
1159 | X string `json:"x"`
1160 | }
1161 | type x struct {
1162 | A y `json:"a"`
1163 | B1 *y
1164 | B2 *y
1165 | b3 *y
1166 | c1 []string
1167 | C2 []string
1168 | D []int
1169 | E []bool
1170 | F []float32
1171 | G []*uint
1172 | H [3]string
1173 | I [1]int
1174 | J [0]bool
1175 | K1 []byte
1176 | K2 []byte
1177 | L []*int
1178 | M1 []y
1179 | M2 *[]y
1180 | N1 []*y
1181 | N2 []*y
1182 | O1 [3]*int
1183 | O2 *[3]*bool
1184 | P [3]*y
1185 | Q [][]int
1186 | R [2][2]string
1187 | S1 map[int]string
1188 | S2 map[int]string
1189 | S3 map[int]string
1190 | S4 map[string]interface{}
1191 | T1 *map[string]int
1192 | T2 *map[string]int
1193 | T3 *map[string]int
1194 | U1 interface{}
1195 | U2 interface{}
1196 | U3 interface{}
1197 | U4 interface{}
1198 | U5 interface{}
1199 | U6 interface{}
1200 | u7 interface{}
1201 | }
1202 | k := make([]byte, 32)
1203 | if _, err := rand.Read(k); err != nil {
1204 | t.Error(err)
1205 | }
1206 | var (
1207 | l1 = 0
1208 | l2 = 42
1209 | m1 = y{X: "X"}
1210 | m2 = y{}
1211 | i0 = 42
1212 | i1 = &i0
1213 | i2 = &i1
1214 | i3 = &i2
1215 | xx = x{
1216 | A: y{X: "X"},
1217 | B1: nil,
1218 | B2: &y{X: "Ipsum"},
1219 | b3: nil,
1220 | c1: nil,
1221 | C2: []string{"one", "two", "three"},
1222 | D: []int{1, 2, 3},
1223 | E: []bool{},
1224 | H: [3]string{"alpha", "beta", "gamma"},
1225 | I: [1]int{42},
1226 | K1: k,
1227 | K2: []byte(nil),
1228 | L: []*int{&l1, &l2, nil},
1229 | M1: []y{m1, m2},
1230 | N1: []*y{&m1, &m2, nil},
1231 | N2: []*y{},
1232 | O1: [3]*int{&l1, &l2, nil},
1233 | P: [3]*y{&m1, &m2, nil},
1234 | Q: [][]int{{1, 2}, {3, 4}},
1235 | R: [2][2]string{{"a", "b"}, {"c", "d"}},
1236 | S1: nil,
1237 | S3: map[int]string{1: "x", 2: "y", 3: "z"},
1238 | S4: map[string]interface{}{"a": 1, "b": "2"},
1239 | T3: &map[string]int{"x": 1, "y": 2, "z": 3},
1240 | U1: "U1",
1241 | U2: &l2,
1242 | U3: nil,
1243 | U4: false,
1244 | U5: (*int)(nil), // typed nil
1245 | U6: i3, // chain of pointers
1246 | u7: nil,
1247 | }
1248 | )
1249 | marshalCompare(t, xx, "non-pointer")
1250 | marshalCompare(t, &xx, "pointer")
1251 | }
1252 |
1253 | // TestEmbeddedTypes tests that composite and basic
1254 | // embedded struct fields types are encoded whether
1255 | // they are exported.
1256 | func TestEmbeddedTypes(t *testing.T) {
1257 | type (
1258 | P1 int
1259 | P2 string
1260 | P3 bool
1261 | p4 uint32
1262 | C1 map[string]int
1263 | C2 [3]string
1264 | C3 []int
1265 | c4 []bool
1266 | )
1267 | type x struct {
1268 | P1
1269 | P2
1270 | P3
1271 | p4
1272 | C1
1273 | C2
1274 | C3
1275 | c4 `json:"c4"`
1276 | }
1277 | xx := &x{
1278 | P1: P1(42),
1279 | P2: P2("P2"),
1280 | P3: P3(true),
1281 | p4: p4(math.MaxUint32),
1282 | C1: C1{"A": 1, "B": 2},
1283 | C2: C2{"A", "B", "C"},
1284 | C3: C3{1, 2, 3},
1285 | c4: c4{true, false},
1286 | }
1287 | marshalCompare(t, xx, "")
1288 | }
1289 |
1290 | // TestRecursiveType tests the marshaling of
1291 | // recursive types.
1292 | func TestRecursiveType(t *testing.T) {
1293 | type x struct {
1294 | A string `json:"a"`
1295 | X *x `json:"x"`
1296 | }
1297 | xx := &x{
1298 | A: "A1",
1299 | X: &x{A: "A2"},
1300 | }
1301 | marshalCompare(t, xx, "")
1302 | }
1303 |
1304 | // TestTaggedFieldDominates tests that a struct
1305 | // field with a tag dominates untagged fields.
1306 | func TestTaggedFieldDominates(t *testing.T) {
1307 | type (
1308 | A struct{ S string }
1309 | D struct {
1310 | XXX string `json:"S"`
1311 | }
1312 | Y struct {
1313 | A
1314 | D
1315 | }
1316 | )
1317 | y := Y{
1318 | A{"A"},
1319 | D{"D"},
1320 | }
1321 | marshalCompare(t, y, "")
1322 | }
1323 |
1324 | // TestDuplicatedFieldDisappears tests that
1325 | // duplicate struct field at the same level
1326 | // of embedding are ignored.
1327 | func TestDuplicatedFieldDisappears(t *testing.T) {
1328 | type (
1329 | A struct{ S string }
1330 | C struct{ S string }
1331 | D struct {
1332 | XXX string `json:"S"`
1333 | }
1334 | Y struct {
1335 | A
1336 | D
1337 | }
1338 | Z struct {
1339 | A
1340 | C
1341 | Y
1342 | }
1343 | )
1344 | z := Z{A{"A"}, C{"C"}, Y{A{"S"}, D{"D"}}}
1345 |
1346 | marshalCompare(t, z, "")
1347 | }
1348 |
1349 | // TestEmbeddedStructs tests that named and unnamed
1350 | // embedded structs fields can be marshaled.
1351 | func TestEmbeddedStructs(t *testing.T) {
1352 | type (
1353 | r struct {
1354 | J string `json:"j"`
1355 | }
1356 | v struct {
1357 | H bool `json:"h,omitempty"`
1358 | I string `json:"i"`
1359 | }
1360 | y struct {
1361 | D int8 `json:"d"`
1362 | E uint8 `json:"e,omitempty"`
1363 | r
1364 | v
1365 | }
1366 | z struct {
1367 | F int16 `json:"f,omitempty"`
1368 | G uint16 `json:"g"`
1369 | y
1370 | v
1371 | }
1372 | // According to the Go rules for embedded fields,
1373 | // y.r.J should be encoded while z.y.r.J is not,
1374 | // because is one-level up.
1375 | // However, y.v.H and z.v.H are present at the same
1376 | // level, and therefore are both hidden.
1377 | x1 struct {
1378 | A string `json:"a,omitempty"`
1379 | y
1380 | B string `json:"b"`
1381 | v `json:"v"`
1382 | C string `json:"c,omitempty"`
1383 | z `json:",omitempty"`
1384 | *x1
1385 | }
1386 | // x2 is a variant of the x1 type without
1387 | // the omitempty option on the first field.
1388 | x2 struct {
1389 | A int16 `json:"a"`
1390 | v `json:"v"`
1391 | }
1392 | )
1393 | xx1 := &x1{
1394 | A: "A",
1395 | y: y{
1396 | D: math.MinInt8,
1397 | r: r{J: "J"},
1398 | v: v{H: false},
1399 | },
1400 | z: z{
1401 | G: math.MaxUint16,
1402 | y: y{D: 21, r: r{J: "J"}},
1403 | v: v{H: true},
1404 | },
1405 | x1: &x1{
1406 | A: "A",
1407 | },
1408 | }
1409 | xx2 := &x2{A: 42, v: v{I: "I"}}
1410 |
1411 | marshalCompare(t, xx1, "")
1412 | marshalCompare(t, xx2, "")
1413 | }
1414 |
1415 | // TestAnonymousFields tests the marshaling of
1416 | // advanced cases for anonymous struct fields.
1417 | // Adapted from the encoding/json testsuite.
1418 | func TestAnonymousFields(t *testing.T) {
1419 | testdata := []struct {
1420 | label string
1421 | input func() []interface{}
1422 | }{{
1423 | // Both S1 and S2 have a field named X.
1424 | // From the perspective of S, it is
1425 | // ambiguous which one X refers to.
1426 | // This should not encode either field.
1427 | label: "AmbiguousField",
1428 | input: func() []interface{} {
1429 | type (
1430 | S1 struct{ x, X int }
1431 | S2 struct{ x, X int }
1432 | S struct {
1433 | S1
1434 | S2
1435 | }
1436 | )
1437 | return []interface{}{
1438 | S{S1{1, 2}, S2{3, 4}},
1439 | &S{S1{5, 6}, S2{7, 8}},
1440 | }
1441 | },
1442 | }, {
1443 | // Both S1 and S2 have a field named X, but
1444 | // since S has an X field as well, it takes
1445 | // precedence over S1.X and S2.X.
1446 | label: "DominantField",
1447 | input: func() []interface{} {
1448 | type (
1449 | S1 struct{ x, X int }
1450 | S2 struct{ x, X int }
1451 | S struct {
1452 | S1
1453 | S2
1454 | x, X int
1455 | }
1456 | )
1457 | return []interface{}{
1458 | S{S1{1, 2}, S2{3, 4}, 5, 6},
1459 | &S{S1{6, 5}, S2{4, 3}, 2, 1},
1460 | }
1461 | },
1462 | }, {
1463 | // Unexported embedded field of non-struct type
1464 | // should not be serialized.
1465 | label: "UnexportedEmbeddedInt",
1466 | input: func() []interface{} {
1467 | type (
1468 | i int
1469 | S struct{ i }
1470 | )
1471 | return []interface{}{S{5}, &S{6}}
1472 | },
1473 | }, {
1474 | // Exported embedded field of non-struct type
1475 | // should be serialized.
1476 | label: "ExportedEmbeddedInt",
1477 | input: func() []interface{} {
1478 | type (
1479 | I int
1480 | S struct{ I }
1481 | )
1482 | return []interface{}{S{5}, &S{6}}
1483 | },
1484 | }, {
1485 | // Unexported embedded field of pointer to
1486 | // non-struct type should not be serialized.
1487 | label: "UnexportedEmbeddedIntPointer",
1488 | input: func() []interface{} {
1489 | type (
1490 | i int
1491 | S struct{ *i }
1492 | )
1493 | s := S{new(i)}
1494 | *s.i = 5
1495 | return []interface{}{s, &s}
1496 | },
1497 | }, {
1498 | // Exported embedded field of pointer to
1499 | // non-struct type should be serialized.
1500 | label: "ExportedEmbeddedIntPointer",
1501 | input: func() []interface{} {
1502 | type (
1503 | I int
1504 | S struct{ *I }
1505 | )
1506 | s := S{new(I)}
1507 | *s.I = 5
1508 | return []interface{}{s, &s}
1509 | },
1510 | }, {
1511 | // Exported embedded field of nil pointer
1512 | // to non-struct type should be serialized.
1513 | label: "ExportedEmbeddedNilIntPointer",
1514 | input: func() []interface{} {
1515 | type (
1516 | I int
1517 | S struct{ *I }
1518 | )
1519 | s := S{new(I)}
1520 | s.I = nil
1521 | return []interface{}{s, &s}
1522 | },
1523 | }, {
1524 | // Exported embedded field of nil pointer to
1525 | // non-struct type should not be serialized
1526 | // if it has the omitempty option.
1527 | label: "ExportedEmbeddedNilIntPointerOmitempty",
1528 | input: func() []interface{} {
1529 | type (
1530 | I int
1531 | S struct {
1532 | *I `json:",omitempty"`
1533 | }
1534 | )
1535 | s := S{new(I)}
1536 | s.I = nil
1537 | return []interface{}{s, &s}
1538 | },
1539 | }, {
1540 | // Exported embedded field of pointer to
1541 | // struct type should be serialized.
1542 | label: "ExportedEmbeddedStructPointer",
1543 | input: func() []interface{} {
1544 | type (
1545 | S struct{ X string }
1546 | T struct{ *S }
1547 | )
1548 | t := T{S: &S{
1549 | X: "X",
1550 | }}
1551 | return []interface{}{t, &t}
1552 | },
1553 | }, {
1554 | // Exported fields of embedded structs should
1555 | // have their exported fields be serialized
1556 | // regardless of whether the struct types
1557 | // themselves are exported.
1558 | label: "EmbeddedStructNonPointer",
1559 | input: func() []interface{} {
1560 | type (
1561 | s1 struct{ x, X int }
1562 | S2 struct{ y, Y int }
1563 | S struct {
1564 | s1
1565 | S2
1566 | }
1567 | )
1568 | return []interface{}{
1569 | S{s1{1, 2}, S2{3, 4}},
1570 | &S{s1{5, 6}, S2{7, 8}},
1571 | }
1572 | },
1573 | }, {
1574 | // Exported fields of pointers to embedded
1575 | // structs should have their exported fields
1576 | // be serialized regardless of whether the
1577 | // struct types themselves are exported.
1578 | label: "EmbeddedStructPointer",
1579 | input: func() []interface{} {
1580 | type (
1581 | s1 struct{ x, X int }
1582 | S2 struct{ y, Y int }
1583 | S struct {
1584 | *s1
1585 | *S2
1586 | }
1587 | )
1588 | return []interface{}{
1589 | S{&s1{1, 2}, &S2{3, 4}},
1590 | &S{&s1{5, 6}, &S2{7, 8}},
1591 | }
1592 | },
1593 | }, {
1594 | // Exported fields on embedded unexported
1595 | // structs at multiple levels of nesting
1596 | // should still be serialized.
1597 | label: "NestedStructAndInts",
1598 | input: func() []interface{} {
1599 | type (
1600 | I1 int
1601 | I2 int
1602 | i int
1603 | s2 struct {
1604 | I2
1605 | i
1606 | }
1607 | s1 struct {
1608 | I1
1609 | i
1610 | s2
1611 | }
1612 | S struct {
1613 | s1
1614 | i
1615 | }
1616 | )
1617 | return []interface{}{
1618 | S{s1{1, 2, s2{3, 4}}, 5},
1619 | &S{s1{5, 4, s2{3, 2}}, 1},
1620 | }
1621 | },
1622 | }, {
1623 | // If an anonymous struct pointer field is nil,
1624 | // we should ignore the embedded fields behind it.
1625 | // Not properly doing so may result in the wrong
1626 | // output or a panic.
1627 | label: "EmbeddedFieldBehindNilPointer",
1628 | input: func() []interface{} {
1629 | type (
1630 | S2 struct{ Field string }
1631 | S struct{ *S2 }
1632 | )
1633 | return []interface{}{S{}, &S{}}
1634 | },
1635 | }, {
1636 | // A field behind a chain of pointer and
1637 | // non-pointer embedded fields should be
1638 | // accessible and serialized.
1639 | label: "BasicEmbeddedFieldChain",
1640 | input: func() []interface{} {
1641 | type (
1642 | A struct {
1643 | X1 string
1644 | X2 *string
1645 | }
1646 | B struct{ *A }
1647 | C struct{ B }
1648 | D struct{ *C }
1649 | E struct{ D }
1650 | F struct{ *E }
1651 | )
1652 | s := "Loreum"
1653 | f := F{E: &E{D: D{C: &C{B: B{A: &A{X1: "X1", X2: &s}}}}}}
1654 | return []interface{}{f, &f}
1655 | },
1656 | }, {
1657 | // Variant of the test above, with embedded
1658 | // fields of type struct that contain one or
1659 | // more fields themselves.
1660 | label: "ComplexEmbeddedFieldChain",
1661 | input: func() []interface{} {
1662 | type (
1663 | A struct {
1664 | X1 string `json:",omitempty"`
1665 | X2 string
1666 | }
1667 | B struct {
1668 | Z3 *bool
1669 | A
1670 | }
1671 | C struct{ B }
1672 | D struct {
1673 | *C
1674 | Z2 int
1675 | }
1676 | E struct{ *D }
1677 | F struct {
1678 | Z1 string `json:",omitempty"`
1679 | *E
1680 | }
1681 | )
1682 | f := F{Z1: "Z1", E: &E{D: &D{C: &C{B: B{A: A{X2: "X2"}, Z3: new(bool)}}, Z2: 1}}}
1683 | return []interface{}{f, &f}
1684 | },
1685 | }}
1686 | for i := range testdata {
1687 | e := testdata[i]
1688 | t.Run(e.label, func(t *testing.T) {
1689 | for i, input := range e.input() {
1690 | input := input
1691 | var label string
1692 | if i == 0 {
1693 | label = "non-pointer"
1694 | } else {
1695 | label = "pointer"
1696 | }
1697 | t.Run(label, func(t *testing.T) {
1698 | marshalCompare(t, input, label)
1699 | })
1700 | }
1701 | })
1702 | }
1703 | }
1704 |
1705 | func TestBytesEscaping(t *testing.T) {
1706 | testdata := []struct {
1707 | in, out string
1708 | }{
1709 | {"\x00", `"\u0000"`},
1710 | {"\x01", `"\u0001"`},
1711 | {"\x02", `"\u0002"`},
1712 | {"\x03", `"\u0003"`},
1713 | {"\x04", `"\u0004"`},
1714 | {"\x05", `"\u0005"`},
1715 | {"\x06", `"\u0006"`},
1716 | {"\x07", `"\u0007"`},
1717 | {"\x08", `"\u0008"`},
1718 | {"\x09", `"\t"`},
1719 | {"\x0a", `"\n"`},
1720 | {"\x0b", `"\u000b"`},
1721 | {"\x0c", `"\u000c"`},
1722 | {"\x0d", `"\r"`},
1723 | {"\x0e", `"\u000e"`},
1724 | {"\x0f", `"\u000f"`},
1725 | {"\x10", `"\u0010"`},
1726 | {"\x11", `"\u0011"`},
1727 | {"\x12", `"\u0012"`},
1728 | {"\x13", `"\u0013"`},
1729 | {"\x14", `"\u0014"`},
1730 | {"\x15", `"\u0015"`},
1731 | {"\x16", `"\u0016"`},
1732 | {"\x17", `"\u0017"`},
1733 | {"\x18", `"\u0018"`},
1734 | {"\x19", `"\u0019"`},
1735 | {"\x1a", `"\u001a"`},
1736 | {"\x1b", `"\u001b"`},
1737 | {"\x1c", `"\u001c"`},
1738 | {"\x1d", `"\u001d"`},
1739 | {"\x1e", `"\u001e"`},
1740 | {"\x1f", `"\u001f"`},
1741 | }
1742 | for _, tt := range testdata {
1743 | b, err := Marshal(tt.in)
1744 | if err != nil {
1745 | t.Error(err)
1746 | }
1747 | if s := string(b); s != tt.out {
1748 | t.Errorf("got %#q, want %#q", s, tt.out)
1749 | }
1750 | }
1751 | }
1752 |
1753 | // TestStringEscaping tests that control and reserved
1754 | // JSON characters are properly escaped when a string
1755 | // is marshaled.
1756 | func TestStringEscaping(t *testing.T) {
1757 | b := []byte{
1758 | 'A', 1, 2, 3,
1759 | '"', '\\', '/', '\b', '\f', '\n', '\r', '\t',
1760 | 0xC7, 0xA3, 0xE2, 0x80, 0xA8, 0xE2, 0x80, 0xA9,
1761 | }
1762 | testdata := []struct {
1763 | b []byte
1764 | s string
1765 | opt Option
1766 | cmp bool
1767 | }{
1768 | {b, `"A\u0001\u0002\u0003\"\\/\u0008\u000c\n\r\tǣ\u2028\u2029"`, nil, true},
1769 | {b, `"` + string(b) + `"`, NoStringEscaping(), false},
1770 | }
1771 | for _, tt := range testdata {
1772 | b, err := MarshalOpts(string(tt.b), tt.opt)
1773 | if err != nil {
1774 | t.Error(err)
1775 | }
1776 | if s := string(b); s != tt.s {
1777 | t.Errorf("got %#q, want %#q", s, tt.s)
1778 | }
1779 | if tt.cmp {
1780 | bs, err := json.Marshal(string(tt.b))
1781 | if err != nil {
1782 | t.Error(err)
1783 | }
1784 | if !bytes.Equal(bs, b) {
1785 | t.Logf("standard: %s", bs)
1786 | t.Logf("jettison: %s", b)
1787 | t.Errorf("expected equal outputs")
1788 | }
1789 | }
1790 | }
1791 | }
1792 |
1793 | // TestStringHTMLEscaping tests that HTML characters
1794 | // are properly escaped when a string is marshaled.
1795 | func TestStringHTMLEscaping(t *testing.T) {
1796 | htmlChars := []byte{'<', '>', '&'}
1797 | testdata := []struct {
1798 | b []byte
1799 | s string
1800 | opts []Option
1801 | }{
1802 | {htmlChars, `"\u003c\u003e\u0026"`, nil},
1803 | {htmlChars, `"<>&"`, []Option{NoHTMLEscaping()}},
1804 |
1805 | // NoHTMLEscaping is ignored when NoStringEscaping
1806 | // is set, because it's part of the escaping options.
1807 | {htmlChars, `"<>&"`, []Option{NoStringEscaping()}},
1808 | {htmlChars, `"<>&"`, []Option{NoStringEscaping(), NoHTMLEscaping()}},
1809 | }
1810 | for _, tt := range testdata {
1811 | b, err := MarshalOpts(string(tt.b), tt.opts...)
1812 | if err != nil {
1813 | t.Error(err)
1814 | }
1815 | if s := string(b); s != tt.s {
1816 | t.Errorf("got %#q, want %#q", s, tt.s)
1817 | }
1818 | }
1819 | }
1820 |
1821 | // TestStringUTF8Coercion tests that invalid bytes
1822 | // are replaced by the Unicode replacement rune when
1823 | // a string is marshaled.
1824 | func TestStringUTF8Coercion(t *testing.T) {
1825 | utf8Seq := string([]byte{'H', 'e', 'l', 'l', 'o', ',', ' ', 0xff, 0xfe, 0xff})
1826 | testdata := []struct {
1827 | b string
1828 | s string
1829 | opt Option
1830 | }{
1831 | {utf8Seq, `"Hello, \ufffd\ufffd\ufffd"`, nil},
1832 | {utf8Seq, `"` + utf8Seq + `"`, NoUTF8Coercion()},
1833 | }
1834 | for _, tt := range testdata {
1835 | b, err := MarshalOpts(tt.b, tt.opt)
1836 | if err != nil {
1837 | t.Error(err)
1838 | }
1839 | if s := string(b); s != tt.s {
1840 | t.Errorf("got %#q, want %#q", s, tt.s)
1841 | }
1842 | }
1843 | }
1844 |
1845 | func TestMarshalFloat(t *testing.T) {
1846 | // Taken from encoding/json.
1847 | t.Parallel()
1848 |
1849 | nf := 0
1850 | mc := regexp.MustCompile
1851 | re := []*regexp.Regexp{
1852 | mc(`p`),
1853 | mc(`^\+`),
1854 | mc(`^-?0[^.]`),
1855 | mc(`^-?\.`),
1856 | mc(`\.(e|$)`),
1857 | mc(`\.[0-9]+0(e|$)`),
1858 | mc(`^-?(0|[0-9]{2,})\..*e`),
1859 | mc(`e[0-9]`),
1860 | mc(`e[+-]0`),
1861 | mc(`e-[1-6]$`),
1862 | mc(`e+(.|1.|20)$`),
1863 | mc(`^-?0\.0000000`),
1864 | mc(`^-?[0-9]{22}`),
1865 | mc(`[1-9][0-9]{16}[1-9]`),
1866 | mc(`[1-9][0-9.]{17}[1-9]`),
1867 | mc(`[1-9][0-9]{8}[1-9]`),
1868 | mc(`[1-9][0-9.]{9}[1-9]`),
1869 | }
1870 | fn := func(f float64, bits int) {
1871 | vf := interface{}(f)
1872 | if bits == 32 {
1873 | f = float64(float32(f)) // round
1874 | vf = float32(f)
1875 | }
1876 | bout, err := Marshal(vf)
1877 | if err != nil {
1878 | t.Errorf("Encode(%T(%g)): %v", vf, vf, err)
1879 | nf++
1880 | return
1881 | }
1882 | out := string(bout)
1883 |
1884 | // Result must convert back to the same float.
1885 | g, err := strconv.ParseFloat(out, bits)
1886 | if err != nil {
1887 | t.Errorf("%T(%g) = %q, cannot parse back: %v", vf, vf, out, err)
1888 | nf++
1889 | return
1890 | }
1891 | if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0
1892 | t.Errorf("%T(%g) = %q (is %g, not %g)", vf, vf, out, float32(g), vf)
1893 | nf++
1894 | return
1895 | }
1896 | bad := re
1897 | if bits == 64 {
1898 | // Last two regexps are for 32-bits values only.
1899 | bad = bad[:len(bad)-2]
1900 | }
1901 | for _, re := range bad {
1902 | if re.MatchString(out) {
1903 | t.Errorf("%T(%g) = %q, must not match /%s/", vf, vf, out, re)
1904 | nf++
1905 | return
1906 | }
1907 | }
1908 | }
1909 | fn(0, 64)
1910 | fn(math.Copysign(0, -1), 64)
1911 | fn(0, 32)
1912 | fn(math.Copysign(0, -1), 32)
1913 |
1914 | var (
1915 | bigger = math.Inf(+1)
1916 | smaller = math.Inf(-1)
1917 | digits = "1.2345678901234567890123"
1918 | )
1919 | for i := len(digits); i >= 2; i-- {
1920 | if testing.Short() && i < len(digits)-4 {
1921 | break
1922 | }
1923 | for exp := -30; exp <= 30; exp++ {
1924 | for _, sign := range "+-" {
1925 | for bits := 32; bits <= 64; bits += 32 {
1926 | s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp)
1927 | f, err := strconv.ParseFloat(s, bits)
1928 | if err != nil {
1929 | t.Fatal(err)
1930 | }
1931 | next := math.Nextafter
1932 | if bits == 32 {
1933 | next = func(g, h float64) float64 {
1934 | return float64(math.Nextafter32(float32(g), float32(h)))
1935 | }
1936 | }
1937 | fn(f, bits)
1938 | fn(next(f, bigger), bits)
1939 | fn(next(f, smaller), bits)
1940 |
1941 | if nf > 50 {
1942 | t.Fatalf("too many fails, stopping tests early")
1943 | }
1944 | }
1945 | }
1946 | }
1947 | }
1948 | }
1949 |
1950 | type (
1951 | jm int
1952 | jmp int
1953 | )
1954 |
1955 | func (m jm) MarshalJSON() ([]byte, error) {
1956 | if m == 0 {
1957 | return []byte("null"), nil
1958 | }
1959 | return []byte(strconv.Itoa(int(m))), nil
1960 | }
1961 |
1962 | func (m *jmp) MarshalJSON() ([]byte, error) {
1963 | if m == nil || *m == 0 {
1964 | return []byte("null"), nil
1965 | }
1966 | return []byte(strconv.Itoa(int(*m))), nil
1967 | }
1968 |
1969 | func TestIssue5(t *testing.T) {
1970 | type X struct {
1971 | JMA jm `json:"jma,omitnil"`
1972 | JMB jm `json:"jmb,omitnil"`
1973 | JMC *jm `json:"jmc,omitnil"`
1974 | JMD *jm `json:"jmd,omitnil"`
1975 | JME jm `json:"jme"`
1976 | JMF *jm `json:"jmf"`
1977 | JMG *jm `json:"jmg,omitnil"`
1978 | JMPA jmp `json:"jmpa,omitnil"`
1979 | JMPB jmp `json:"jmpb,omitnil"`
1980 | JMPC *jmp `json:"jmpc,omitnil"`
1981 | JMPD *jmp `json:"jmpd,omitnil"`
1982 | JMPE *jmp `json:"jmpe"`
1983 | JMPF *jmp `json:"jmpf"`
1984 | JMPG jmp `json:"jmpg"`
1985 | JMPH *jmp `json:"jmph,omitnil"`
1986 | }
1987 | var (
1988 | jmc = jm(2)
1989 | jmd = jm(0)
1990 | jmpc = jmp(2)
1991 | jmpd = jmp(0)
1992 | )
1993 | x := X{
1994 | JMA: jm(4),
1995 | JMB: jm(0),
1996 | JMC: &jmc,
1997 | JMD: &jmd,
1998 | JME: jm(0),
1999 | JMF: &jmd,
2000 | JMG: nil,
2001 | JMPA: jmp(4),
2002 | // note: the JMPB field implementation of MarshalJSON
2003 | // has a pointer-receiver, but the field itself is not
2004 | // a pointer, therefore the method is not invoked and
2005 | // the omitnil option does not apply.
2006 | JMPB: jmp(0),
2007 | JMPC: &jmpc,
2008 | JMPD: &jmpd,
2009 | JMPE: nil,
2010 | JMPF: &jmpd,
2011 | JMPG: jmp(0), // same as JMPB,
2012 | JMPH: nil,
2013 | }
2014 | b, err := Marshal(x)
2015 | if err != nil {
2016 | t.Error(err)
2017 | }
2018 | want := []byte(`{"jma":4,"jmc":2,"jme":null,"jmf":null,"jmpa":4,"jmpb":0,"jmpc":2,"jmpe":null,"jmpf":null,"jmpg":0}`)
2019 | if bytes.Compare(b, want) != 0 {
2020 | t.Errorf("got %s, want %s,", string(b), string(want))
2021 | }
2022 | }
2023 |
--------------------------------------------------------------------------------