1package pretty
2
3import (
4 "bytes"
5 "encoding/json"
6 "sort"
7 "strconv"
8)
9
10// Options is Pretty options
11type Options struct {
12 // Width is an max column width for single line arrays
13 // Default is 80
14 Width int
15 // Prefix is a prefix for all lines
16 // Default is an empty string
17 Prefix string
18 // Indent is the nested indentation
19 // Default is two spaces
20 Indent string
21 // SortKeys will sort the keys alphabetically
22 // Default is false
23 SortKeys bool
24}
25
26// DefaultOptions is the default options for pretty formats.
27var DefaultOptions = &Options{Width: 80, Prefix: "", Indent: " ", SortKeys: false}
28
29// Pretty converts the input json into a more human readable format where each
30// element is on it's own line with clear indentation.
31func Pretty(json []byte) []byte { return PrettyOptions(json, nil) }
32
33// PrettyOptions is like Pretty but with customized options.
34func PrettyOptions(json []byte, opts *Options) []byte {
35 if opts == nil {
36 opts = DefaultOptions
37 }
38 buf := make([]byte, 0, len(json))
39 if len(opts.Prefix) != 0 {
40 buf = append(buf, opts.Prefix...)
41 }
42 buf, _, _, _ = appendPrettyAny(buf, json, 0, true,
43 opts.Width, opts.Prefix, opts.Indent, opts.SortKeys,
44 0, 0, -1)
45 if len(buf) > 0 {
46 buf = append(buf, '\n')
47 }
48 return buf
49}
50
51// Ugly removes insignificant space characters from the input json byte slice
52// and returns the compacted result.
53func Ugly(json []byte) []byte {
54 buf := make([]byte, 0, len(json))
55 return ugly(buf, json)
56}
57
58// UglyInPlace removes insignificant space characters from the input json
59// byte slice and returns the compacted result. This method reuses the
60// input json buffer to avoid allocations. Do not use the original bytes
61// slice upon return.
62func UglyInPlace(json []byte) []byte { return ugly(json, json) }
63
64func ugly(dst, src []byte) []byte {
65 dst = dst[:0]
66 for i := 0; i < len(src); i++ {
67 if src[i] > ' ' {
68 dst = append(dst, src[i])
69 if src[i] == '"' {
70 for i = i + 1; i < len(src); i++ {
71 dst = append(dst, src[i])
72 if src[i] == '"' {
73 j := i - 1
74 for ; ; j-- {
75 if src[j] != '\\' {
76 break
77 }
78 }
79 if (j-i)%2 != 0 {
80 break
81 }
82 }
83 }
84 }
85 }
86 }
87 return dst
88}
89
90func isNaNOrInf(src []byte) bool {
91 return src[0] == 'i' || //Inf
92 src[0] == 'I' || // inf
93 src[0] == '+' || // +Inf
94 src[0] == 'N' || // Nan
95 (src[0] == 'n' && len(src) > 1 && src[1] != 'u') // nan
96}
97
98func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) {
99 for ; i < len(json); i++ {
100 if json[i] <= ' ' {
101 continue
102 }
103 if json[i] == '"' {
104 return appendPrettyString(buf, json, i, nl)
105 }
106
107 if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' || isNaNOrInf(json[i:]) {
108 return appendPrettyNumber(buf, json, i, nl)
109 }
110 if json[i] == '{' {
111 return appendPrettyObject(buf, json, i, '{', '}', pretty, width, prefix, indent, sortkeys, tabs, nl, max)
112 }
113 if json[i] == '[' {
114 return appendPrettyObject(buf, json, i, '[', ']', pretty, width, prefix, indent, sortkeys, tabs, nl, max)
115 }
116 switch json[i] {
117 case 't':
118 return append(buf, 't', 'r', 'u', 'e'), i + 4, nl, true
119 case 'f':
120 return append(buf, 'f', 'a', 'l', 's', 'e'), i + 5, nl, true
121 case 'n':
122 return append(buf, 'n', 'u', 'l', 'l'), i + 4, nl, true
123 }
124 }
125 return buf, i, nl, true
126}
127
128type pair struct {
129 kstart, kend int
130 vstart, vend int
131}
132
133type byKeyVal struct {
134 sorted bool
135 json []byte
136 buf []byte
137 pairs []pair
138}
139
140func (arr *byKeyVal) Len() int {
141 return len(arr.pairs)
142}
143func (arr *byKeyVal) Less(i, j int) bool {
144 if arr.isLess(i, j, byKey) {
145 return true
146 }
147 if arr.isLess(j, i, byKey) {
148 return false
149 }
150 return arr.isLess(i, j, byVal)
151}
152func (arr *byKeyVal) Swap(i, j int) {
153 arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i]
154 arr.sorted = true
155}
156
157type byKind int
158
159const (
160 byKey byKind = 0
161 byVal byKind = 1
162)
163
164type jtype int
165
166const (
167 jnull jtype = iota
168 jfalse
169 jnumber
170 jstring
171 jtrue
172 jjson
173)
174
175func getjtype(v []byte) jtype {
176 if len(v) == 0 {
177 return jnull
178 }
179 switch v[0] {
180 case '"':
181 return jstring
182 case 'f':
183 return jfalse
184 case 't':
185 return jtrue
186 case 'n':
187 return jnull
188 case '[', '{':
189 return jjson
190 default:
191 return jnumber
192 }
193}
194
195func (arr *byKeyVal) isLess(i, j int, kind byKind) bool {
196 k1 := arr.json[arr.pairs[i].kstart:arr.pairs[i].kend]
197 k2 := arr.json[arr.pairs[j].kstart:arr.pairs[j].kend]
198 var v1, v2 []byte
199 if kind == byKey {
200 v1 = k1
201 v2 = k2
202 } else {
203 v1 = bytes.TrimSpace(arr.buf[arr.pairs[i].vstart:arr.pairs[i].vend])
204 v2 = bytes.TrimSpace(arr.buf[arr.pairs[j].vstart:arr.pairs[j].vend])
205 if len(v1) >= len(k1)+1 {
206 v1 = bytes.TrimSpace(v1[len(k1)+1:])
207 }
208 if len(v2) >= len(k2)+1 {
209 v2 = bytes.TrimSpace(v2[len(k2)+1:])
210 }
211 }
212 t1 := getjtype(v1)
213 t2 := getjtype(v2)
214 if t1 < t2 {
215 return true
216 }
217 if t1 > t2 {
218 return false
219 }
220 if t1 == jstring {
221 s1 := parsestr(v1)
222 s2 := parsestr(v2)
223 return string(s1) < string(s2)
224 }
225 if t1 == jnumber {
226 n1, _ := strconv.ParseFloat(string(v1), 64)
227 n2, _ := strconv.ParseFloat(string(v2), 64)
228 return n1 < n2
229 }
230 return string(v1) < string(v2)
231
232}
233
234func parsestr(s []byte) []byte {
235 for i := 1; i < len(s); i++ {
236 if s[i] == '\\' {
237 var str string
238 json.Unmarshal(s, &str)
239 return []byte(str)
240 }
241 if s[i] == '"' {
242 return s[1:i]
243 }
244 }
245 return nil
246}
247
248func appendPrettyObject(buf, json []byte, i int, open, close byte, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) {
249 var ok bool
250 if width > 0 {
251 if pretty && open == '[' && max == -1 {
252 // here we try to create a single line array
253 max := width - (len(buf) - nl)
254 if max > 3 {
255 s1, s2 := len(buf), i
256 buf, i, _, ok = appendPrettyObject(buf, json, i, '[', ']', false, width, prefix, "", sortkeys, 0, 0, max)
257 if ok && len(buf)-s1 <= max {
258 return buf, i, nl, true
259 }
260 buf = buf[:s1]
261 i = s2
262 }
263 } else if max != -1 && open == '{' {
264 return buf, i, nl, false
265 }
266 }
267 buf = append(buf, open)
268 i++
269 var pairs []pair
270 if open == '{' && sortkeys {
271 pairs = make([]pair, 0, 8)
272 }
273 var n int
274 for ; i < len(json); i++ {
275 if json[i] <= ' ' {
276 continue
277 }
278 if json[i] == close {
279 if pretty {
280 if open == '{' && sortkeys {
281 buf = sortPairs(json, buf, pairs)
282 }
283 if n > 0 {
284 nl = len(buf)
285 if buf[nl-1] == ' ' {
286 buf[nl-1] = '\n'
287 } else {
288 buf = append(buf, '\n')
289 }
290 }
291 if buf[len(buf)-1] != open {
292 buf = appendTabs(buf, prefix, indent, tabs)
293 }
294 }
295 buf = append(buf, close)
296 return buf, i + 1, nl, open != '{'
297 }
298 if open == '[' || json[i] == '"' {
299 if n > 0 {
300 buf = append(buf, ',')
301 if width != -1 && open == '[' {
302 buf = append(buf, ' ')
303 }
304 }
305 var p pair
306 if pretty {
307 nl = len(buf)
308 if buf[nl-1] == ' ' {
309 buf[nl-1] = '\n'
310 } else {
311 buf = append(buf, '\n')
312 }
313 if open == '{' && sortkeys {
314 p.kstart = i
315 p.vstart = len(buf)
316 }
317 buf = appendTabs(buf, prefix, indent, tabs+1)
318 }
319 if open == '{' {
320 buf, i, nl, _ = appendPrettyString(buf, json, i, nl)
321 if sortkeys {
322 p.kend = i
323 }
324 buf = append(buf, ':')
325 if pretty {
326 buf = append(buf, ' ')
327 }
328 }
329 buf, i, nl, ok = appendPrettyAny(buf, json, i, pretty, width, prefix, indent, sortkeys, tabs+1, nl, max)
330 if max != -1 && !ok {
331 return buf, i, nl, false
332 }
333 if pretty && open == '{' && sortkeys {
334 p.vend = len(buf)
335 if p.kstart > p.kend || p.vstart > p.vend {
336 // bad data. disable sorting
337 sortkeys = false
338 } else {
339 pairs = append(pairs, p)
340 }
341 }
342 i--
343 n++
344 }
345 }
346 return buf, i, nl, open != '{'
347}
348func sortPairs(json, buf []byte, pairs []pair) []byte {
349 if len(pairs) == 0 {
350 return buf
351 }
352 vstart := pairs[0].vstart
353 vend := pairs[len(pairs)-1].vend
354 arr := byKeyVal{false, json, buf, pairs}
355 sort.Stable(&arr)
356 if !arr.sorted {
357 return buf
358 }
359 nbuf := make([]byte, 0, vend-vstart)
360 for i, p := range pairs {
361 nbuf = append(nbuf, buf[p.vstart:p.vend]...)
362 if i < len(pairs)-1 {
363 nbuf = append(nbuf, ',')
364 nbuf = append(nbuf, '\n')
365 }
366 }
367 return append(buf[:vstart], nbuf...)
368}
369
370func appendPrettyString(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
371 s := i
372 i++
373 for ; i < len(json); i++ {
374 if json[i] == '"' {
375 var sc int
376 for j := i - 1; j > s; j-- {
377 if json[j] == '\\' {
378 sc++
379 } else {
380 break
381 }
382 }
383 if sc%2 == 1 {
384 continue
385 }
386 i++
387 break
388 }
389 }
390 return append(buf, json[s:i]...), i, nl, true
391}
392
393func appendPrettyNumber(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
394 s := i
395 i++
396 for ; i < len(json); i++ {
397 if json[i] <= ' ' || json[i] == ',' || json[i] == ':' || json[i] == ']' || json[i] == '}' {
398 break
399 }
400 }
401 return append(buf, json[s:i]...), i, nl, true
402}
403
404func appendTabs(buf []byte, prefix, indent string, tabs int) []byte {
405 if len(prefix) != 0 {
406 buf = append(buf, prefix...)
407 }
408 if len(indent) == 2 && indent[0] == ' ' && indent[1] == ' ' {
409 for i := 0; i < tabs; i++ {
410 buf = append(buf, ' ', ' ')
411 }
412 } else {
413 for i := 0; i < tabs; i++ {
414 buf = append(buf, indent...)
415 }
416 }
417 return buf
418}
419
420// Style is the color style
421type Style struct {
422 Key, String, Number [2]string
423 True, False, Null [2]string
424 Escape [2]string
425 Brackets [2]string
426 Append func(dst []byte, c byte) []byte
427}
428
429func hexp(p byte) byte {
430 switch {
431 case p < 10:
432 return p + '0'
433 default:
434 return (p - 10) + 'a'
435 }
436}
437
438// TerminalStyle is for terminals
439var TerminalStyle *Style
440
441func init() {
442 TerminalStyle = &Style{
443 Key: [2]string{"\x1B[1m\x1B[94m", "\x1B[0m"},
444 String: [2]string{"\x1B[32m", "\x1B[0m"},
445 Number: [2]string{"\x1B[33m", "\x1B[0m"},
446 True: [2]string{"\x1B[36m", "\x1B[0m"},
447 False: [2]string{"\x1B[36m", "\x1B[0m"},
448 Null: [2]string{"\x1B[2m", "\x1B[0m"},
449 Escape: [2]string{"\x1B[35m", "\x1B[0m"},
450 Brackets: [2]string{"\x1B[1m", "\x1B[0m"},
451 Append: func(dst []byte, c byte) []byte {
452 if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') {
453 dst = append(dst, "\\u00"...)
454 dst = append(dst, hexp((c>>4)&0xF))
455 return append(dst, hexp((c)&0xF))
456 }
457 return append(dst, c)
458 },
459 }
460}
461
462// Color will colorize the json. The style parma is used for customizing
463// the colors. Passing nil to the style param will use the default
464// TerminalStyle.
465func Color(src []byte, style *Style) []byte {
466 if style == nil {
467 style = TerminalStyle
468 }
469 apnd := style.Append
470 if apnd == nil {
471 apnd = func(dst []byte, c byte) []byte {
472 return append(dst, c)
473 }
474 }
475 type stackt struct {
476 kind byte
477 key bool
478 }
479 var dst []byte
480 var stack []stackt
481 for i := 0; i < len(src); i++ {
482 if src[i] == '"' {
483 key := len(stack) > 0 && stack[len(stack)-1].key
484 if key {
485 dst = append(dst, style.Key[0]...)
486 } else {
487 dst = append(dst, style.String[0]...)
488 }
489 dst = apnd(dst, '"')
490 esc := false
491 uesc := 0
492 for i = i + 1; i < len(src); i++ {
493 if src[i] == '\\' {
494 if key {
495 dst = append(dst, style.Key[1]...)
496 } else {
497 dst = append(dst, style.String[1]...)
498 }
499 dst = append(dst, style.Escape[0]...)
500 dst = apnd(dst, src[i])
501 esc = true
502 if i+1 < len(src) && src[i+1] == 'u' {
503 uesc = 5
504 } else {
505 uesc = 1
506 }
507 } else if esc {
508 dst = apnd(dst, src[i])
509 if uesc == 1 {
510 esc = false
511 dst = append(dst, style.Escape[1]...)
512 if key {
513 dst = append(dst, style.Key[0]...)
514 } else {
515 dst = append(dst, style.String[0]...)
516 }
517 } else {
518 uesc--
519 }
520 } else {
521 dst = apnd(dst, src[i])
522 }
523 if src[i] == '"' {
524 j := i - 1
525 for ; ; j-- {
526 if src[j] != '\\' {
527 break
528 }
529 }
530 if (j-i)%2 != 0 {
531 break
532 }
533 }
534 }
535 if esc {
536 dst = append(dst, style.Escape[1]...)
537 } else if key {
538 dst = append(dst, style.Key[1]...)
539 } else {
540 dst = append(dst, style.String[1]...)
541 }
542 } else if src[i] == '{' || src[i] == '[' {
543 stack = append(stack, stackt{src[i], src[i] == '{'})
544 dst = append(dst, style.Brackets[0]...)
545 dst = apnd(dst, src[i])
546 dst = append(dst, style.Brackets[1]...)
547 } else if (src[i] == '}' || src[i] == ']') && len(stack) > 0 {
548 stack = stack[:len(stack)-1]
549 dst = append(dst, style.Brackets[0]...)
550 dst = apnd(dst, src[i])
551 dst = append(dst, style.Brackets[1]...)
552 } else if (src[i] == ':' || src[i] == ',') && len(stack) > 0 && stack[len(stack)-1].kind == '{' {
553 stack[len(stack)-1].key = !stack[len(stack)-1].key
554 dst = append(dst, style.Brackets[0]...)
555 dst = apnd(dst, src[i])
556 dst = append(dst, style.Brackets[1]...)
557 } else {
558 var kind byte
559 if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' || isNaNOrInf(src[i:]) {
560 kind = '0'
561 dst = append(dst, style.Number[0]...)
562 } else if src[i] == 't' {
563 kind = 't'
564 dst = append(dst, style.True[0]...)
565 } else if src[i] == 'f' {
566 kind = 'f'
567 dst = append(dst, style.False[0]...)
568 } else if src[i] == 'n' {
569 kind = 'n'
570 dst = append(dst, style.Null[0]...)
571 } else {
572 dst = apnd(dst, src[i])
573 }
574 if kind != 0 {
575 for ; i < len(src); i++ {
576 if src[i] <= ' ' || src[i] == ',' || src[i] == ':' || src[i] == ']' || src[i] == '}' {
577 i--
578 break
579 }
580 dst = apnd(dst, src[i])
581 }
582 if kind == '0' {
583 dst = append(dst, style.Number[1]...)
584 } else if kind == 't' {
585 dst = append(dst, style.True[1]...)
586 } else if kind == 'f' {
587 dst = append(dst, style.False[1]...)
588 } else if kind == 'n' {
589 dst = append(dst, style.Null[1]...)
590 }
591 }
592 }
593 }
594 return dst
595}
596
597// Spec strips out comments and trailing commas and convert the input to a
598// valid JSON per the official spec: https://tools.ietf.org/html/rfc8259
599//
600// The resulting JSON will always be the same length as the input and it will
601// include all of the same line breaks at matching offsets. This is to ensure
602// the result can be later processed by a external parser and that that
603// parser will report messages or errors with the correct offsets.
604func Spec(src []byte) []byte {
605 return spec(src, nil)
606}
607
608// SpecInPlace is the same as Spec, but this method reuses the input json
609// buffer to avoid allocations. Do not use the original bytes slice upon return.
610func SpecInPlace(src []byte) []byte {
611 return spec(src, src)
612}
613
614func spec(src, dst []byte) []byte {
615 dst = dst[:0]
616 for i := 0; i < len(src); i++ {
617 if src[i] == '/' {
618 if i < len(src)-1 {
619 if src[i+1] == '/' {
620 dst = append(dst, ' ', ' ')
621 i += 2
622 for ; i < len(src); i++ {
623 if src[i] == '\n' {
624 dst = append(dst, '\n')
625 break
626 } else if src[i] == '\t' || src[i] == '\r' {
627 dst = append(dst, src[i])
628 } else {
629 dst = append(dst, ' ')
630 }
631 }
632 continue
633 }
634 if src[i+1] == '*' {
635 dst = append(dst, ' ', ' ')
636 i += 2
637 for ; i < len(src)-1; i++ {
638 if src[i] == '*' && src[i+1] == '/' {
639 dst = append(dst, ' ', ' ')
640 i++
641 break
642 } else if src[i] == '\n' || src[i] == '\t' ||
643 src[i] == '\r' {
644 dst = append(dst, src[i])
645 } else {
646 dst = append(dst, ' ')
647 }
648 }
649 continue
650 }
651 }
652 }
653 dst = append(dst, src[i])
654 if src[i] == '"' {
655 for i = i + 1; i < len(src); i++ {
656 dst = append(dst, src[i])
657 if src[i] == '"' {
658 j := i - 1
659 for ; ; j-- {
660 if src[j] != '\\' {
661 break
662 }
663 }
664 if (j-i)%2 != 0 {
665 break
666 }
667 }
668 }
669 } else if src[i] == '}' || src[i] == ']' {
670 for j := len(dst) - 2; j >= 0; j-- {
671 if dst[j] <= ' ' {
672 continue
673 }
674 if dst[j] == ',' {
675 dst[j] = ' '
676 }
677 break
678 }
679 }
680 }
681 return dst
682}