Skip to main content

Row Types & Parsers

Row parsers define how to read a complete row from a ResultSet. They're composable and type-safe.

Defining a Row Parser

A RowParser<T> knows how to read all columns of a row and construct an instance of T. It also knows how to decompose T back into column values for writing.

RowParser<Person> personParser = RowParsers.of(
PgTypes.int4, // id
PgTypes.text, // name
PgTypes.timestamptz, // createdAt
Person::new,
person -> new Object[]{person.id(), person.name(), person.createdAt()}
);

List<Person> people = personParser.parseList(resultSet);

How It Works

The RowParsers.of(...) factory takes:

  1. Database types — one DbType<T> per column, in order. These know how to read and write values correctly for the target database.
  2. Constructor — a function that takes the column values and returns your row type.
  3. Destructor — a function that takes your row type and returns the column values as an array.

The parser uses column-index-based reading (not column names), which is both faster and catches schema mismatches at parse time rather than silently returning wrong data.

Nullable Columns

Use .opt() to wrap a type for nullable columns:

RowParser<Person> personParser = RowParsers.of(
PgTypes.int4,
PgTypes.text,
PgTypes.timestamptz.opt(), // Optional<OffsetDateTime>
Person::new,
person -> new Object[]{person.id(), person.name(), person.createdAt()}
);

Composing Parsers

Row parsers compose for joins. Left join gives you Optional on the right side:

RowParser<And<ProductRow, Optional<CategoryRow>>> joined =
ProductRow.rowParser.leftJoined(CategoryRow.rowParser);