Skip to content

Commit 06ab251

Browse files
authored
feat: add json colored formatter (#25)
1 parent 459345c commit 06ab251

File tree

2 files changed

+190
-19
lines changed

2 files changed

+190
-19
lines changed

pkg/printer/formatter.go

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package printer
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"net/http"
76
"sort"
7+
"strconv"
88
"strings"
99
"time"
1010

@@ -28,7 +28,7 @@ func (f Formatter) RequestLine(line result.RequestLine) string {
2828
}
2929

3030
func (f Formatter) Json(j map[string]any) string {
31-
return formatJson(j)
31+
return formatJson(j, f.colored)
3232
}
3333

3434
func formatHeaders(headers http.Header, colorized bool) string {
@@ -113,10 +113,128 @@ func formatRequestLine(line result.RequestLine, colorized bool) string {
113113
return fmt.Sprintf(fstring, Colorize(line.Method, methodColor), line.Url, line.Protocol)
114114
}
115115

116-
func formatJson(j map[string]any) string {
117-
if prettyJson, err := json.MarshalIndent(j, "", " "); err != nil {
118-
return fmt.Sprintf("unable to parse json: %v\n", err)
119-
} else {
120-
return string(prettyJson)
116+
func formatJson(j map[string]any, colorized bool) string {
117+
var f func(inner map[string]any, level int) string
118+
119+
var formatValue func(value any, level int) string
120+
121+
f = func(inner map[string]any, level int) string {
122+
sb := strings.Builder{}
123+
sb.WriteString("{")
124+
125+
keys := []string{}
126+
for k := range inner {
127+
keys = append(keys, k)
128+
}
129+
130+
sort.Strings(keys)
131+
132+
padding := strings.Repeat(" ", level+1)
133+
134+
for i, k := range keys {
135+
sb.WriteString("\n")
136+
137+
value := inner[k]
138+
isLast := i == len(keys)-1
139+
140+
sb.WriteString(padding)
141+
sb.WriteString(formatJsonKey(k, colorized))
142+
143+
sb.WriteString(formatValue(value, level))
144+
145+
if !isLast {
146+
sb.WriteString(",")
147+
} else {
148+
sb.WriteString("\n")
149+
}
150+
}
151+
152+
padding = strings.Repeat(" ", level)
153+
if sb.Len() != 1 {
154+
sb.WriteString(padding)
155+
}
156+
157+
sb.WriteString("}")
158+
159+
return sb.String()
160+
}
161+
162+
formatValue = func(value any, level int) string {
163+
switch v := value.(type) {
164+
case string:
165+
return formatJsonStringValue(v, colorized)
166+
case int, float64:
167+
return formatJsonNumberValue(v, colorized)
168+
case bool:
169+
return formatJsonBoolValue(v, colorized)
170+
case nil:
171+
return formatJsonNilValue(colorized)
172+
case map[string]any:
173+
return f(v, level+1)
174+
case []any:
175+
sb := strings.Builder{}
176+
sb.WriteString("[")
177+
178+
for i, vv := range v {
179+
sb.WriteString(formatValue(vv, level))
180+
181+
if i != len(v)-1 {
182+
sb.WriteString(", ")
183+
}
184+
}
185+
186+
sb.WriteString("]")
187+
188+
return sb.String()
189+
}
190+
191+
return ""
121192
}
193+
194+
return f(j, 0)
195+
}
196+
197+
func formatJsonKey(s string, colorized bool) string {
198+
fstring := `%s: `
199+
value := strconv.Quote(s)
200+
201+
if !colorized {
202+
return fmt.Sprintf(fstring, value)
203+
}
204+
205+
return fmt.Sprintf(fstring, Yellow(value))
206+
}
207+
208+
func formatJsonStringValue(s string, colorized bool) string {
209+
if !colorized {
210+
return strconv.Quote(s)
211+
}
212+
213+
return Green(strconv.Quote(s))
214+
}
215+
216+
func formatJsonNumberValue(n any, colorized bool) string {
217+
s := fmt.Sprintf("%v", n)
218+
if !colorized {
219+
return s
220+
}
221+
222+
return Cyan(s)
223+
}
224+
225+
func formatJsonBoolValue(b bool, colorized bool) string {
226+
s := fmt.Sprintf("%v", b)
227+
if !colorized {
228+
return s
229+
}
230+
231+
return Magenta(s)
232+
}
233+
234+
func formatJsonNilValue(colorized bool) string {
235+
if !colorized {
236+
return "null"
237+
}
238+
239+
return Red("null")
122240
}

pkg/printer/formatter_test.go

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,17 +174,19 @@ func Test_formatRequestLine(t *testing.T) {
174174

175175
func Test_formatJson(t *testing.T) {
176176
tests := []struct {
177-
name string
178-
json map[string]any
179-
want string
177+
name string
178+
json map[string]any
179+
useColor bool
180+
want string
180181
}{
181182
{
182183
name: "simple",
183184
json: map[string]any{
184185
"name": "Alice",
185186
"age": 30,
186187
},
187-
want: "{\n \"age\": 30,\n \"name\": \"Alice\"\n}",
188+
useColor: false,
189+
want: "{\n \"age\": 30,\n \"name\": \"Alice\"\n}",
188190
},
189191
{
190192
name: "nested",
@@ -194,25 +196,76 @@ func Test_formatJson(t *testing.T) {
194196
"name": "Bob",
195197
},
196198
},
197-
want: "{\n \"user\": {\n \"id\": 1,\n \"name\": \"Bob\"\n }\n}",
199+
useColor: false,
200+
want: "{\n \"user\": {\n \"id\": 1,\n \"name\": \"Bob\"\n }\n}",
201+
},
202+
{
203+
name: "empty",
204+
json: map[string]any{},
205+
useColor: false,
206+
want: "{}",
207+
},
208+
{
209+
name: "colored int",
210+
json: map[string]any{
211+
"int": 1,
212+
},
213+
useColor: true,
214+
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"int"`), Cyan("1")),
215+
},
216+
{
217+
name: "colored string",
218+
json: map[string]any{
219+
"string": "Hello, World",
220+
},
221+
useColor: true,
222+
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"string"`), Green(`"Hello, World"`)),
198223
},
199224
{
200-
name: "empty",
201-
json: map[string]any{},
202-
want: "{}",
225+
name: "colored boolean",
226+
json: map[string]any{
227+
"boolean": true,
228+
},
229+
useColor: true,
230+
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"boolean"`), Magenta("true")),
203231
},
204232
{
205-
name: "unmarshalable value (channel)",
233+
name: "colored float",
206234
json: map[string]any{
207-
"invalid": make(chan int),
235+
"float": 1.23,
208236
},
209-
want: "unable to parse json: json: unsupported type: chan int\n",
237+
useColor: true,
238+
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"float"`), Cyan("1.23")),
239+
},
240+
{
241+
name: "colored nil",
242+
json: map[string]any{
243+
"nil": nil,
244+
},
245+
useColor: true,
246+
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"nil"`), Red("null")),
247+
},
248+
{
249+
name: "colored list",
250+
json: map[string]any{
251+
"list": []any{1, "string", false, nil, map[string]any{"one": 1}},
252+
},
253+
useColor: true,
254+
want: fmt.Sprintf("{\n %v: [%v, %v, %v, %v, {\n %v: %v\n }]\n}",
255+
Yellow(`"list"`),
256+
Cyan("1"),
257+
Green(`"string"`),
258+
Magenta("false"),
259+
Red("null"),
260+
Yellow(`"one"`),
261+
Cyan("1"),
262+
),
210263
},
211264
}
212265

213266
for _, tt := range tests {
214267
t.Run(tt.name, func(t *testing.T) {
215-
formatter := Formatter{}
268+
formatter := Formatter{colored: tt.useColor}
216269
got := formatter.Json(tt.json)
217270
assert.Equal(t, tt.want, got)
218271
})

0 commit comments

Comments
 (0)