| name | markout-output-formats |
| version | 0.35.2 |
| description | Use when you need output other than default Markdown — plain text / Unicode, ANSI terminal (Spectre), pretty aligned tables, or TSV/JSONL exports — or when one model must serve several formats from a single render path (a CLI `--format` switch). Also covers semantic inline code that renders as Markdown code spans but decodes to plain text in TSV/JSONL. Pick the format with a formatter + MarkoutWriterOptions; never hand-build TSV/JSONL. Don't decompile the assembly or web-search the API — the multi-format idioms are all here. |
Output formats — one model, many formats
Default Serialize(...) emits Markdown. Pass a formatter (and optional MarkoutWriterOptions)
to get other formats. The anti-pattern is per-format string building (string.Join("\t", ...)); let
Markout project the same model to each format so columns/headers stay consistent.
Required setup
Markout has no reflection fallback. Every report needs a partial context registering each model
type and a Serialize call that passes it. [MarkoutSerializable] is optional customization, not
a prerequisite for registration.
using Markout;
[MarkoutSerializable(TitleProperty = nameof(Title))]
public class Report
{
public string Title { get; set; } = "";
[MarkoutSection(Name = "Rows")] public List<Row>? Rows { get; set; }
}
[MarkoutSerializable]
public class Row { public string Name { get; set; } = ""; public int Count { get; set; } }
[MarkoutContext(typeof(Report))]
[MarkoutContext(typeof(Row))]
public partial class ReportContext : MarkoutSerializerContext { }
var ctx = ReportContext.Default;
Formatters
MarkoutSerializer.Serialize(r, Console.Out, ctx);
MarkoutSerializer.Serialize(r, Console.Out, new MarkdownFormatter(), ctx);
MarkoutSerializer.Serialize(r, Console.Out, new PlainTextFormatter(), ctx);
MarkoutSerializer.Serialize(r, Console.Out, new UnicodeFormatter(), ctx);
MarkoutSerializer.Serialize(r, Console.Out, new TableFormatter(), ctx);
using Markout.Ansi.Spectre;
MarkoutSerializer.Serialize(r, Console.Out, new SpectreFormatter(AnsiConsole.Console), ctx);
Markout.Ansi.Spectre is a separate NuGet package; Markdown/plain/table/TSV/JSONL need only Markout.
Graph sections become Markdown edge tables by default. To embed the same
graph as Mermaid without rebuilding it, select the Markdown graph mode:
MarkoutSerializer.Serialize(
report,
Console.Out,
new MarkdownFormatter(MarkdownGraphMode.Mermaid),
ctx);
TableFormatter modes + writer options
TableFormatter + MarkoutWriterOptions.TableMode selects the tabular shape:
var opts = new MarkoutWriterOptions
{
TableMode = MarkoutTableMode.Tsv,
IncludeDescription = false,
IncludeSections = new HashSet<string> { "Results" },
JsonTypedValues = true,
MaxItems = 3,
};
MarkoutSerializer.Serialize(report, Console.Out, new TableFormatter(), ctx, opts);
MarkoutTableMode.Pretty — columns aligned to a uniform start position.
MarkoutTableMode.Tsv — stable snake_case headers; never emits embedded tabs/newlines in cells.
MarkoutTableMode.Jsonl — one record per row, carrying only that row's keys (heterogeneous).
MaxItems = N caps every table to N rows. Presentation and TSV output append
... and {count} more; JSONL omits the notice because every line must remain a data record.
Apply the cap only to presentation options when TSV/JSONL exports must remain complete.
(Or cap a property with [MarkoutMaxItems(3)] and an optional EllipsisFormat.)
Central multi-format dispatch (the CLI --format pattern)
Route one model through one method so every command gets every format for free:
static void Render<TView, TJson>(TView view, OutputFormat fmt, MarkoutSerializerContext ctx)
{
switch (fmt)
{
case OutputFormat.Text: MarkoutSerializer.Serialize(view, Console.Out, new PlainTextFormatter(), ctx); break;
case OutputFormat.Md: MarkoutSerializer.Serialize(view, Console.Out, new MarkdownFormatter(), ctx,
new MarkoutWriterOptions { MaxItems = 3 }); break;
case OutputFormat.Table: MarkoutSerializer.Serialize(view, Console.Out, new TableFormatter(), ctx); break;
case OutputFormat.Tsv: MarkoutSerializer.Serialize(view, Console.Out, new TableFormatter(), ctx,
new MarkoutWriterOptions { TableMode = MarkoutTableMode.Tsv }); break;
case OutputFormat.Jsonl: MarkoutSerializer.Serialize(view, Console.Out, new TableFormatter(), ctx,
new MarkoutWriterOptions { TableMode = MarkoutTableMode.Jsonl, JsonTypedValues = true }); break;
}
}
If a format needs byte-exact output (e.g. a verbatim packet), bypass Markout for that one case and
keep Markout for the structured formats — don't try to force it.
Format promises (rely on these)
- Markdown table cells normalize
| to | (not \|).
- TSV uses stable
snake_case headers and never embeds tabs/newlines in cells.
- Pretty renders the same projection as TSV, aligned.
Format-neutral inline code
Store code-like values with semantic <code>...</code> tags. XML-escape angle brackets inside
the tags:
row.Operation = "<code>dotnet test</code>";
row.Signature = "<code>Result<T> Parse<T>(string value)</code>";
Markdown renders code spans such as `Result<T> Parse<T>(string value)`. Pretty, TSV, and
JSONL remove the tags, decode the entities, and emit plain text. Do not store raw backticks or use
[MarkoutDisplayFormat("`{0}`")]; that hard-codes Markdown into the model.
JSONL must still go through Markout:
var jsonl = new MarkoutWriterOptions
{
TableMode = MarkoutTableMode.Jsonl,
JsonTypedValues = true,
};
MarkoutSerializer.Serialize(report, Console.Out, new TableFormatter(), ctx, jsonl);
Do not substitute System.Text.Json: it does not apply Markout's table projection, stable property
names, or semantic-tag decoding.
Guardrails
- One model → many formats; never hand-assemble TSV/JSONL rows.
- Reach for
IncludeSections/IncludeDescription to trim exports rather than a second model.
- Spectre/ANSI needs the
Markout.Ansi.Spectre package; the rest do not.