diff --git a/fmt.go b/fmt.go index 16db57d..5476a6d 100644 --- a/fmt.go +++ b/fmt.go @@ -301,6 +301,11 @@ func FormatBytes(src []byte, invalid []byte, invalidWidth int, isJSON, isRaw boo res.Buf = append(res.Buf, invalid...) res.Width += invalidWidth res.Quoted = true + } else if isJSON { + res.Buf = append(res.Buf, '\\', 'u') + for s := 12; s >= 0; s -= 4 { + res.Buf = append(res.Buf, lowerhex[r>>uint(s)&0xf]) + } } else { res.Buf = append(res.Buf, '\\', 'x', lowerhex[src[0]>>4], lowerhex[src[0]&0xf]) res.Width += 4 @@ -311,22 +316,38 @@ func FormatBytes(src []byte, invalid []byte, invalidWidth int, isJSON, isRaw boo // handle json encoding if isJSON { switch r { - case '\t': - res.Buf = append(res.Buf, '\\', 't') + case '\a': + res.Buf = append(res.Buf, '\\', 'u', '0', '0', '0', '7') + res.Width += 6 + continue + case '\b': + res.Buf = append(res.Buf, '\\', 'b') + res.Width += 2 + continue + case '\f': + res.Buf = append(res.Buf, '\\', 'f') res.Width += 2 continue case '\n': res.Buf = append(res.Buf, '\\', 'n') res.Width += 2 continue - case '\\': - res.Buf = append(res.Buf, '\\', '\\') + case '\r': + res.Buf = append(res.Buf, '\\', 'r') + res.Width += 2 + continue + case '\t': + res.Buf = append(res.Buf, '\\', 't') res.Width += 2 continue case '"': res.Buf = append(res.Buf, '\\', '"') res.Width += 2 continue + case '\\': + res.Buf = append(res.Buf, '\\', '\\') + res.Width += 2 + continue } } // handle raw encoding @@ -386,7 +407,7 @@ func FormatBytes(src []byte, invalid []byte, invalidWidth int, isJSON, isRaw boo default: switch { // escape as \x00 - case r < ' ': + case r < ' ' && !isJSON: res.Buf = append(res.Buf, '\\', 'x', lowerhex[byte(r)>>4], lowerhex[byte(r)&0xf]) res.Width += 4 // escape as \u0000 diff --git a/fmt_test.go b/fmt_test.go index 172a8c8..09bf6b6 100644 --- a/fmt_test.go +++ b/fmt_test.go @@ -1,8 +1,10 @@ package tblfmt import ( + "encoding/json" "reflect" "regexp" + "slices" "strconv" "strings" "testing" @@ -103,6 +105,43 @@ func TestFormatBytesComplex(t *testing.T) { } } +func TestFormatJSON(t *testing.T) { + s := strings.Join( + []string{ + "\a", + "\b", + "\f", + "\n", + "\r", + "\t", + "\x1a", + "\x2b", + "\x3f", + "\\", + " ", + "\x9f", + "\xaf", + "\xff", + "\u1998", + "👀", + "🤰", + "foo", + }, + ";", + ) + exp, err := json.Marshal(s) + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + exp = exp[1 : len(exp)-1] + t.Logf("exp: %q", string(exp)) + v := FormatBytes([]byte(s), nil, 0, true, false, 0, 0) + t.Logf("v : %q", v) + if b := []byte(v.String()); !slices.Equal(b, exp) { + t.Errorf("expected: %q, got: %q", string(exp), string(b)) + } +} + func TestFormatBytesRaw(t *testing.T) { tests := []struct { s string