Skip to Content

Dart schemas for every boundary

Trust the boundary.Keep the types.

Define the boundary once. Validate unknown data, decode it into the models your application uses, and encode those models safely for the next boundary.

  • Runtime-first validation
  • Bidirectional codecs
  • Optional code generation
DARTuser_codec.dart
parse() ↔ encode()
final userCodec = Ack.object({
  'name': Ack.string().minLength(2),
  'email': Ack.string().email(),
  'createdAt': Ack.datetime(),
}).codec<User>(
  decode: User.fromMap,
  encode: (user) => user.toMap(),
);

// User
final user = userCodec.parse(wireData);
// JSON-safe
final json = userCodec.encode(user);
ONE SCHEMA, BOTH DIRECTIONS

From boundary data to User—and back.

Validation and transformation stay in one readable contract. Nested codecs handle field values while the outer codec constructs the model your application understands.

01 / DEFINE

Describe the boundary and the model.

Compose field schemas for the wire shape, then map the validated result into User. Built-in codecs such as Ack.datetime() keep rich values inside your application.

Explore schemas
Boundary fields + model mapping
final boundary = Ack.object({
  'createdAt': Ack.datetime(),
});

final userCodec = boundary.codec<User>(
  decode: User.fromMap,
  encode: (user) => user.toMap(),
);
02 / VALIDATE + DECODE

Parse boundary data into a User.

userCodec.parse() checks every field, decodes the ISO timestamp, and calls User.fromMap. Use safeParse() when you want structured failures instead of an exception.

Boundary map → validated User
final user = userCodec.parse(wireData);
print(user.createdAt); // UTC DateTime
03 / ENCODE

Send the model back safely.

userCodec.encode(user) runs the outer model mapping and every nested field codec in reverse. The resulting map is ready for JSON serialization.

Serialize with codecs
User model → JSON-safe map
final json = userCodec.encode(user);
print(json['createdAt']); // ISO String
ONE SCHEMA, MORE REACH

Go beyond validation without duplicating the contract.

Use the schema you already trust for structured AI, provider-specific output, generated types, and reversible conversions at every application boundary.

Firebase AI / Gemini

Guide generation. Then verify it.

The shipped Firebase AI adapter turns your Ack schema into responseJsonSchema. After generation, the same schema remains the authoritative runtime validator and codec boundary.

Explore structured output
structured_output.dart · guide → verify
final responseSchema = Ack.object({
  'answer': Ack.string(),
  'sources': Ack.list(Ack.uri()),
  'generatedAt': Ack.datetime(),
});

final config = GenerationConfig(
  responseJsonSchema:
    responseSchema.toFirebaseAiResponseJsonSchema(),
);

final output = responseSchema.parse(
  jsonDecode(response.text!),
);
PROVIDER ADAPTERS

One model. Provider-specific output.

AckSchemaModel is the target-independent adapter boundary. Use the built-in toJsonSchema() path, the Firebase AI adapter, or author a focused adapter for OpenAI and other provider SDKs.

Build a provider adapter
Canonical model
AckSchemaModel
Ships with
Firebase AI · JSON Schema
Extend
Your provider adapter
TYPE-SAFE CODE GENERATION

Generate types, not duplicate models.

Annotate the schema you already trust. Ack generates a lightweight wrapper with typed getters and parse helpers—without changing the runtime schema.

Generate typed schemas
user_schema.dart · optional generator
@AckType()
final userSchema = Ack.object(...);

final ada = UserType.parse(json);
print(ada.email); // String
BUILT-IN + CUSTOM CODECS

Compose rich values through objects and lists.

Built-in field codecs decode common boundary formats inside Ack.object() and Ack.list(). Add Ack.codec(...) for your own runtime type; each layer reverses together on encode.

Meet codecs
Ack.date()
String ↔ DateTime
Ack.datetime()
ISO String ↔ UTC DateTime
Ack.uri()
String ↔ Uri
Ack.duration()
milliseconds int ↔ Duration
Ack.enumCodec(values)
name String ↔ enum value
Ack.codec(...)
Boundary value ↔ your type
START WITH ONE BOUNDARY

Make unknown data explicit.

Add Ack, describe the shape you expect, and keep the rest of your Dart code focused on trusted values.

dart pub add ack
Read installation