---
title: Render helpers
description: render_table and render_json — exact output shape, trailing newlines, empty input, failure modes.
tags: [reference, output]
---
# Render helpers
Two functions that turn a slice of rows into a string. They exist as a
pair so one `--output text|json` flag can pick between them without the
calling command formatting anything itself.
```rust
#[must_use]
pub fn render_table<R: Tabled>(rows: &[R]) -> String;
pub fn render_json<R: Serialize>(rows: &[R]) -> Result<String, RenderError>;
```
Both take a slice, so both accept `&vec` and `&[..]`. Neither prints
anything — they return the string and leave the writing to you.
## `render_table` output shape
The style is `tabled`'s `Style::psql()`, applied unconditionally. There
is no parameter, no builder and no configuration key that changes it —
see [Why the table style is fixed](../explanation/03-why-the-output-helpers-are-a-pair.md#why-the-table-style-is-not-configurable).
For two rows of `struct Row { name: &'static str, count: u32 }`:
```text
alpha | 1
beta | 2
```
Columns are padded to the widest cell, so the rule width follows the
data.
Column headers come from the field names of the `Tabled` derive, in
declaration order. Rename them with `#[tabled(rename = "...")]` on the
field; that is a `tabled` attribute, not an `rtb-tui` one.
The returned string always ends with a newline. If `tabled` did not
produce one, `render_table` appends it, so `print!` is the right macro
and `println!` will add a blank line.
`render_table` is infallible — there is no error type and no panic path
for a type that implements `Tabled`.
## What `render_table` does with an empty slice
An empty slice still renders the header and the separator rule, because
the headers come from the type rather than from the data:
```text
```
It does not return an empty string, and it does not return a "no rows"
message. If you want one, check `rows.is_empty()` before calling.
## `render_json` output shape
`serde_json::to_string_pretty`, which is a two-space indent, plus a
trailing newline appended by `render_json`. The top level is always a
JSON array, including for one row and for zero rows:
```json
[
{
"name": "alpha",
"count": 1
}
]
```
An empty slice renders as `[]` followed by a newline. Field names come
from `serde`, so `#[serde(rename = "...")]` and `#[serde(skip)]` apply
in the usual way — and note that `serde` and `tabled` have separate
rename attributes, so a field renamed for one is not renamed for the
other.
## When `render_json` returns an error
`render_json` returns `RenderError::Json(String)` when
`serde_json::to_string_pretty` fails on the rows. The payload is the
underlying `serde_json::Error` already converted to a string.
In practice `serde_json` fails on very little. Measured against this
crate at v0.6.3:
| `HashMap<i32, _>` — integer map keys | `Ok` — keys are stringified, `{"7": ...}` |
| `f64::NAN`, `f64::INFINITY` | `Ok` — both serialise as `null` |
| `HashMap<(u8, u8), _>` — tuple map keys | `Err(RenderError::Json("key must be a string"))` |
| A hand-written `Serialize` impl that returns an error | `Err(RenderError::Json(_))` with that impl's message |
So the realistic triggers are a map keyed by something that is not
string-like, and a hand-written `Serialize` impl that refuses a value.
Both are programmer mistakes in the row type rather than anything a
user typed, which is why the error carries a string rather than the
original `serde_json::Error`.
The rustdoc on `render_json` names non-finite floats and non-string map
keys as the typical causes. Half of that is wrong — see
[Known documentation defects](05-crate.md#known-documentation-defects).
## Using both from one row type
A type that feeds both helpers needs both derives, and they are
independent:
```rust
use serde::Serialize;
use tabled::Tabled;
#[derive(Tabled, Serialize)]
struct Row {
name: &'static str,
count: u32,
}
```
`Tabled` renders each field through `Display` unless you tell it
otherwise, which rules out nesting: a field whose type is another
struct will not derive `Tabled` without a per-field `tabled` attribute,
even though `Serialize` handles it happily. A nested shape is the one
case where the text and JSON views of the same type genuinely diverge —
the table flattens or drops what the JSON keeps.