@@ -27,17 +27,23 @@ var _ json.Marshaler = Map(nil)
27
27
//
28
28
// Every field value is encoded with the following process:
29
29
//
30
- // 1. slog.Value is handled to allow any type to replace its representation for logging.
30
+ // 1. slog.Value is checked to allow any type to replace its representation for logging.
31
+ //
32
+ // 2. json.Marshaller is handled.
31
33
//
32
34
// 2. xerrors.Formatter is handled.
33
35
//
34
- // 3. Protobufs are handled with json.Marshal.
36
+ // 3. Protobufs are encoded with json.Marshal.
37
+ //
38
+ // 4. error and fmt.Stringer are used if possible.
35
39
//
36
- // 4. error and fmt.Stringer are handled .
40
+ // 5. slices and arrays go through the encode function for every element .
37
41
//
38
- // 5. slices and arrays are handled to go through the encode function for every value.
42
+ // 6. If the value is a struct without exported fields or a type that
43
+ // cannot be encoded with json.Marshal (like channels) then
44
+ // fmt.Sprintf("%+v") is used.
39
45
//
40
- // 6 . json.Marshal is invoked as the default case .
46
+ // 8 . json.Marshal(v) is used for all other values .
41
47
func (m Map ) MarshalJSON () ([]byte , error ) {
42
48
b := & bytes.Buffer {}
43
49
b .WriteByte ('{' )
@@ -56,8 +62,11 @@ func (m Map) MarshalJSON() ([]byte, error) {
56
62
return b .Bytes (), nil
57
63
}
58
64
59
- // ForceJSON ensures the value is logged via json.Marshal even
60
- // if it implements fmt.Stringer or error.
65
+ // ForceJSON ensures the value is logged via json.Marshal.
66
+ //
67
+ // Use it when implementing SlogValue to ensure a structured
68
+ // representation of a struct if you know it's capable even
69
+ // when it implements fmt.Stringer or error.
61
70
func ForceJSON (v interface {}) interface {} {
62
71
return jsonVal {v : v }
63
72
}
@@ -66,13 +75,6 @@ type jsonVal struct {
66
75
v interface {}
67
76
}
68
77
69
- var _ json.Marshaler = jsonVal {}
70
-
71
- // MarshalJSON implements json.Marshaler.
72
- func (v jsonVal ) MarshalJSON () ([]byte , error ) {
73
- return json .Marshal (v .v )
74
- }
75
-
76
78
func marshalList (rv reflect.Value ) []byte {
77
79
b := & bytes.Buffer {}
78
80
b .WriteByte ('[' )
@@ -93,6 +95,10 @@ func encode(v interface{}) []byte {
93
95
switch v := v .(type ) {
94
96
case Value :
95
97
return encode (v .SlogValue ())
98
+ case json.Marshaler :
99
+ return encodeJSON (v )
100
+ case jsonVal :
101
+ return encodeJSON (v .v )
96
102
case xerrors.Formatter :
97
103
return encode (errorChain (v ))
98
104
case interface {
@@ -103,28 +109,47 @@ func encode(v interface{}) []byte {
103
109
return encode (fmt .Sprint (v ))
104
110
default :
105
111
rv := reflect .Indirect (reflect .ValueOf (v ))
106
- if rv .IsValid () {
107
- switch rv .Type ().Kind () {
108
- case reflect .Slice :
109
- if rv .IsNil () {
110
- break
111
- }
112
- fallthrough
113
- case reflect .Array :
112
+ if ! rv .IsValid () {
113
+ return encodeJSON (v )
114
+ }
115
+
116
+ switch rv .Type ().Kind () {
117
+ case reflect .Slice :
118
+ if ! rv .IsNil () {
114
119
return marshalList (rv )
115
120
}
116
- }
121
+ case reflect .Array :
122
+ return marshalList (rv )
123
+ case reflect .Struct :
124
+ typ := rv .Type ()
125
+ for i := 0 ; i < rv .NumField (); i ++ {
126
+ // Found an exported field.
127
+ if typ .Field (i ).PkgPath == "" {
128
+ return encodeJSON (v )
129
+ }
130
+ }
117
131
118
- b , err := json .Marshal (v )
119
- if err != nil {
120
- return encode (M (
121
- Error (xerrors .Errorf ("failed to marshal to JSON: %w" , err )),
122
- F ("type" , reflect .TypeOf (v )),
123
- F ("value" , fmt .Sprintf ("%+v" , v )),
124
- ))
132
+ return encodeJSON (fmt .Sprintf ("%+v" , v ))
133
+ case reflect .Chan , reflect .Complex64 , reflect .Complex128 , reflect .Func :
134
+ // These types cannot be directly encoded with json.Marshal.
135
+ // See https://golang.org/pkg/encoding/json/#Marshal
136
+ return encodeJSON (fmt .Sprintf ("%+v" , v ))
125
137
}
126
- return b
138
+
139
+ return encodeJSON (v )
140
+ }
141
+ }
142
+
143
+ func encodeJSON (v interface {}) []byte {
144
+ b , err := json .Marshal (v )
145
+ if err != nil {
146
+ return encode (M (
147
+ Error (xerrors .Errorf ("failed to marshal to JSON: %w" , err )),
148
+ F ("type" , reflect .TypeOf (v )),
149
+ F ("value" , fmt .Sprintf ("%+v" , v )),
150
+ ))
127
151
}
152
+ return b
128
153
}
129
154
130
155
func errorChain (f xerrors.Formatter ) []interface {} {
0 commit comments