Skip to Content
DocsAckAdvancedCodecs

Codecs

Your API sends a timestamp as an ISO string, but your code wants a DateTime. A codec handles that round-trip: it validates the incoming string, decodes it into the Dart type you actually use, and encodes it back when you send data out.

final when = Ack.datetime(); // CodecSchema<String, DateTime> final dt = when.parse('2026-01-01T00:00:00Z'); // decode: String -> DateTime final iso = when.encode(dt); // encode: DateTime -> String

Ack calls the wire shape the boundary (JSON-safe primitives like String or int) and the Dart value the runtime (a DateTime, Uri, Duration, or your own type). Parsing decodes boundary → runtime; encoding goes the other way.

Transform vs. codec

HelperDirectionReturns
schema.transform<R>(fn)one-way (parse only)CodecSchema<Boundary, R>
schema.codec<R>(decode: ..., encode: ...)bidirectionalCodecSchema<Boundary, R>

Use transform when you only ever decode. Encoding a transformed schema fails with a SchemaEncodeError (a one-way transform has no encoder). Use codec when you need a reversible encode path.

Built-in codecs

Ack ships codecs for the most common boundary conversions:

FactoryBoundary ↔ RuntimeRuntime invariant
Ack.date()ISO YYYY-MM-DD StringDateTimelocal midnight (no time-of-day)
Ack.datetime()ISO 8601 StringDateTimemust be UTC; leap-second strings are rejected
Ack.uri()StringUriabsolute URI (scheme + host)
Ack.duration()milliseconds intDurationwhole milliseconds
Ack.enumCodec(values)enum-name String ↔ enum valueone of values
final schema = Ack.object({ 'startsAt': Ack.datetime(), 'website': Ack.uri(), 'timeout': Ack.duration(), }); final event = schema.parse({ 'startsAt': '2026-01-01T09:00:00Z', 'website': 'https://example.com', 'timeout': 5000, }); // event['startsAt'] is a DateTime (UTC) // event['website'] is a Uri // event['timeout'] is a Duration (5 seconds)

Each built-in enforces a runtime invariant on encode. Ack.datetime() requires a UTC DateTime; convert local values with .toUtc() before encoding. It also rejects RFC leap-second strings before decoding because Dart normalizes :60. Use Ack.string().datetime() when leap-second text must be validated and preserved.

Custom codecs

Create a codec from any input schema with Ack.codec(...), providing a decode function (boundary → runtime) and an encode function (runtime → boundary):

final csv = Ack.codec<String, String, List<String>>( input: Ack.string(), decode: (s) => s.split(','), encode: (list) => list.join(','), ); csv.parse('a,b,c'); // ['a', 'b', 'c'] csv.encode(['a', 'b', 'c']); // 'a,b,c'

The three type arguments are the boundary type, the input schema’s runtime type, and the final runtime type. The optional output schema validates the decoded value (defaults to a type check on the runtime type).

Call .codec<R>(...) on an existing schema to add a reversible conversion:

final trimmed = Ack.string().codec<String>( decode: (s) => s.trim(), encode: (s) => s, );

Encoding back to the boundary

Use encode (throws on failure) or safeEncode (returns a SchemaResult):

final result = Ack.datetime().safeEncode(DateTime.utc(2026)); if (result.isOk) { print(result.getOrThrow()); // 2026-01-01T00:00:00.000Z }

Errors surface as typed SchemaError values:

  • SchemaTransformError — a decode function threw during parsing.
  • SchemaEncodeError — encoding failed (for example, a one-way transform, or a runtime invariant violation such as a non-UTC DateTime for Ack.datetime()).

Codecs and JSON Schema

A codec exports the JSON Schema of its boundary (input) schema — that is the shape that crosses the wire. For example, Ack.datetime() exports as string with format: date-time. The runtime type and decode/encode logic are not part of the exported schema. See JSON Serialization.

Next steps

Last updated on