prettyx

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 25, 2025 License: MIT Imports: 13 Imported by: 2

README

prettyx

prettyx formats JSON with deterministic indentation and optional syntax highlighting. The default output is jq-ish (one key/value per line). It can also unwrap JSON values that are themselves encoded as JSON strings, so nested JSON becomes readable without manual decoding (this behaviour is similar to how the jq tool renders JSON with the fromjson builtin).

Install the CLI

go install pkt.systems/prettyx/cmd/prettyx@latest

Build from source

make            # builds bin/prettyx
make test       # run tests with -race
make fuzz       # run fuzzers (10s per fuzzer)
make bench      # run benchmarks
make install    # installs to /usr/local/bin by default

Use PREFIX=/path (and optionally DESTDIR=/path) to change the install location.

Usage

Run prettyx with one or more JSON files (use - for stdin). Add --no-color (or --palette none) to force plain output, or -C/--color-force to force color on non-TTY output. Use --palette <name> to pick from the bundled themes (see --list-palettes). The default palette matches jq’s built-in colours. Use -u/--unwrap to decode JSON appearing inside string values. Use --semi-compact for tidwall-style semi-compact formatting with soft wrapping (-w/--width controls the wrap width). Use -c/--compact to emit one compacted JSON document per line. When reading from URLs, use -k/--insecure to skip TLS verification and --accept-all to send Accept: */*.

By default prettyx leaves JSON strings untouched, matching jq's default behaviour. Both require an explicit fromjson (for example: jq '.payload |= fromjson') or --unwrap to recursively decode JSON-looking strings.

prettyx originally borrowed the tidwall/pretty output style. The current formatter is a fully rewritten zero-alloc streaming implementation, and the old layout is now available via --semi-compact.

prettyx payload.json other.json
prettyx -u payload.json
prettyx --unwrap payload.json
prettyx --semi-compact payload.json
prettyx --semi-compact -w 120 payload.json
prettyx -c payload.json
prettyx https://example.com/data.json
prettyx --accept-all https://example.com/data
cat payload.json | prettyx --no-color
cat payload.json | prettyx -C | less -R
cat payload.json | prettyx --palette tokyo-night
prettyx --list-palettes

Bundled palettes: default/jq (jq colour scheme), catppuccin-mocha, doom-dracula, doom-gruvbox, doom-iosvkem, doom-nord, gruvbox-light, monokai-vibrant, one-dark-aurora, outrun-electric, solarized-nightfall, synthwave84, tokyo-night, pslog (classic pslog default), and none.

jq equivalent

With --unwrap, this example renders identically with jq and prettyx:

$ echo '{"value":"[{"inner": true},{"inner": true,"msg":"true aswell"}]"}' | jq '.value |= fromjson'
{
  "value": [
    {
      "inner": true
    },
    {
      "inner": true,
      "msg": "true aswell"
    }
  ]
}

Use as a library

Import the package and call Pretty.

package main

import (
    "fmt"
    "log"

    "pkt.systems/prettyx"
)

func main() {
    src := []byte(`{"foo":"{\"nested\":true}"}`)
    pretty, err := prettyx.Pretty(src, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Print(string(pretty))
}

Pretty respects the configurable prettyx.MaxNestedJSONDepth, and you can pass custom Options to tweak width (when SemiCompact is enabled), indentation, and Unwrap when you want the jq-style behaviour of decoding embedded JSON strings.

Allocations and streaming

prettyx focuses on streaming performance and minimizing heap allocations. In this context, “zero-alloc” means no heap allocations per document in steady state (after pools are warmed).

  • PrettyStream and PrettyReader are the zero-alloc paths when Unwrap is false.
  • CompactTo is likewise allocation-free in steady state for non-unwrap input.
  • Pretty and PrettyToBuffer allocate because they build an in-memory buffer for the output.
  • CompactToBuffer allocates for its output buffer.
  • When Unwrap is true, additional work is required to decode escaped strings and re-parse embedded JSON; the streaming path reuses internal buffers, but allocations can still occur depending on input.
// Stream from a reader into a writer.
if err := prettyx.PrettyStream(os.Stdout, os.Stdin, nil); err != nil {
    log.Fatal(err)
}

// Pipe-style usage (close to stop the goroutine).
r := prettyx.PrettyReader(os.Stdin, nil)
defer r.Close()
if _, err := io.Copy(os.Stdout, r); err != nil {
    log.Fatal(err)
}

Designed for recursive jq-style unwrapping

prettyx was designed to replicate the behaviour of the following jq program, which repeatedly calls fromjson on any string that looks like JSON and walks the entire structure:

jq 'def trim($s):
  $s | sub("^\\s+";"") | sub("\\s+$";"");

def looks_like_json($s):
  ($s | length > 1)
  and (
    (($s[0:1] == "{") and ($s[-1:] == "}"))
    or (($s[0:1] == "[") and ($s[-1:] == "]"))
  );

def unwrap:
  if type == "string" then
    (. as $original
     | trim($original) as $trimmed
     | if looks_like_json($trimmed) then
         (try ($trimmed | fromjson | unwrap) catch $original)
       else
         $original
       end)
  elif type == "array" then
    map(unwrap)
  elif type == "object" then
    with_entries(.value |= unwrap)
  else
    .
  end;
unwrap'

Example comparison with sample data:

$ cat sample.json
{"count":2,"message":"ok","payload":"{\"bar\":{\"list\":\"[10,20]\",\"nested\":\"{\\\"inner\\\":true}\"},\"foo\":1}"}

$ jq 'def trim($s):
  $s | sub("^\\s+";"") | sub("\\s+$";"");

def looks_like_json($s):
  ($s | length > 1)
  and (
    (($s[0:1] == "{") and ($s[-1:] == "}"))
    or (($s[0:1] == "[") and ($s[-1:] == "]"))
  );

def unwrap:
  if type == "string" then
    (. as $original
     | trim($original) as $trimmed
     | if looks_like_json($trimmed) then
         (try ($trimmed | fromjson | unwrap) catch $original)
       else
         $original
       end)
  elif type == "array" then
    map(unwrap)
  elif type == "object" then
    with_entries(.value |= unwrap)
  else
    .
  end;
unwrap' sample.json
{
  "count": 2,
  "message": "ok",
  "payload": {
    "bar": {
      "list": [
        10,
        20
      ],
      "nested": {
        "inner": true
      }
    },
    "foo": 1
  }
}

$ prettyx -u sample.json
{
  "count": 2,
  "message": "ok",
  "payload": {
    "bar": {
      "list": [
        10,
        20
      ],
      "nested": {
        "inner": true
      }
    },
    "foo": 1
  }
}

Documentation

Overview

Package prettyx provides streaming JSON pretty printing with optional ANSI coloring and recursive unwrapping of JSON-looking strings.

By default, output is jq-ish (one element or key per line). Set Options.SemiCompact to enable tidwall-style formatting with soft wrapping. Options.Width controls the wrap column in SemiCompact mode; when Width <= 0, output always breaks after commas. For maximum throughput, PrettyStream and PrettyReader operate without allocations when Options.Unwrap is false.

Basic usage:

src := []byte(`{"foo":"{\"nested\":true}"}`)
opts := &prettyx.Options{Unwrap: true, Palette: "none"}
out, err := prettyx.Pretty(src, opts)
if err != nil {
	log.Fatal(err)
}
fmt.Print(string(out))

Streaming:

opts := &prettyx.Options{Palette: "none"}
if err := prettyx.PrettyStream(os.Stdout, os.Stdin, opts); err != nil {
	log.Fatal(err)
}

Semi-compact formatting:

opts := &prettyx.Options{SemiCompact: true, Width: 80, Palette: "none"}
if err := prettyx.PrettyStream(os.Stdout, bytes.NewReader(src), opts); err != nil {
	log.Fatal(err)
}

Index

Constants

This section is empty.

Variables

View Source
var DefaultOptions = &Options{
	Width:       80,
	SemiCompact: false,
	Prefix:      "",
	Indent:      "  ",
	Unwrap:      false,
	ForceColor:  false,
	Palette:     "default",
}

DefaultOptions holds the fallback pretty-print configuration.

View Source
var MaxNestedJSONDepth = 10

MaxNestedJSONDepth controls how deep we recursively parse JSON that appears inside string values when Unwrap is enabled. Set to 10 by default. Special case:

  • If MaxNestedJSONDepth == 0, we still unwrap one level (i.e., parse the string as JSON once, but do not recurse further).

Example meanings:

0  -> unwrap once (non-recursive)
1  -> unwrap once (same as 0)
2+ -> unwrap up to that many recursive levels

Functions

func CompactTo added in v0.4.0

func CompactTo(w io.Writer, r io.Reader, opts *Options) error

CompactTo streams compacted JSON to the provided writer. It supports multiple JSON documents in the input stream, emitting one compacted document per line. When opts.Unwrap is true, JSON-looking strings are decoded recursively before compaction.

func CompactToBuffer added in v0.4.0

func CompactToBuffer(r io.Reader, opts *Options) ([]byte, error)

CompactToBuffer compacts JSON into memory. It preserves the one-document-per-line behavior of CompactTo.

func PaletteNames added in v0.4.0

func PaletteNames() []string

PaletteNames returns the sorted list of palette names, including "none".

func Pretty

func Pretty(in []byte, opts *Options) ([]byte, error)

Pretty parses the input JSON, optionally unwraps nested JSON strings (recursing up to MaxNestedJSONDepth when Unwrap is enabled), formats it, and colorizes it using ANSI SGR sequences before returning the resulting bytes. Color output is automatically disabled when the destination is not a TTY unless ForceColor is set.

func PrettyReader added in v0.4.0

func PrettyReader(r io.Reader, opts *Options) io.ReadCloser

PrettyReader returns a reader that streams pretty-printed JSON from r. The returned reader must be closed to stop the internal goroutine.

func PrettyStream added in v0.4.0

func PrettyStream(w io.Writer, r io.Reader, opts *Options) error

PrettyStream formats one or more JSON documents from r and writes them to w. It is the streaming, zero-alloc path when opts.Unwrap is false. When opts.SemiCompact is true, output uses tidwall-style soft wrapping.

func PrettyTo

func PrettyTo(w io.Writer, in []byte, opts *Options) error

PrettyTo writes a pretty-printed, colorized JSON document to the provided writer. Colors degrade automatically when the writer is not a TTY.

func PrettyToBuffer added in v0.4.0

func PrettyToBuffer(w io.Writer, in []byte, opts *Options) ([]byte, error)

PrettyToBuffer renders a pretty-printed JSON document into memory and returns the resulting bytes.

func PrettyWithRenderer

func PrettyWithRenderer(in []byte, opts *Options, _ any) ([]byte, error)

PrettyWithRenderer mirrored Pretty when lipgloss was used. It is kept for callers temporarily but ignores the renderer. Prefer Pretty or PrettyTo.

Types

type ColorPalette

type ColorPalette struct {
	Key         string
	String      string
	Number      string
	True        string
	False       string
	Null        string
	Brackets    string
	Punctuation string
}

ColorPalette configures the ANSI styles for each JSON token class.

func NoColorPalette

func NoColorPalette() ColorPalette

NoColorPalette disables all styling while keeping the formatter path shared.

type Options

type Options struct {
	// Width is the soft-wrap column width for SemiCompact mode. When <= 0,
	// SemiCompact always breaks after commas.
	Width int
	// SemiCompact enables tidwall-style formatting with soft wrapping.
	// When false, output is jq-ish (one element/key per line).
	SemiCompact bool
	// Prefix is applied to every output line. Default "".
	Prefix string
	// Indent defines the nested indentation. Default two spaces.
	Indent string
	// Unwrap enables recursive decoding of JSON strings. This mirrors the CLI's
	// -u/--unwrap flag. When false, prettyx leaves any JSON-looking strings as-is.
	Unwrap bool
	// ForceColor emits ANSI color even when the destination is not a TTY.
	ForceColor bool
	// Palette selects the named colour palette. Empty chooses the default.
	// Use "none" to disable colour.
	Palette string
}

Options controls the pretty-printing behavior.

Directories

Path Synopsis
cmd
prettyx command
internal
ansi
Package ansi provides ANSI escape sequences and palette presets.
Package ansi provides ANSI escape sequences and palette presets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL