Skip to main content

Casting

Casting lets you map a model field to a codec key (a string) so Ormed can consistently:

Prerequisites

What You’ll Learn

  • Built-in cast keys and when to use each
  • How field-level and model-level casts interact
  • How casting affects read, write, and serialization pipelines

Step Outcome

By the end of this page, you should be able to:

  • Choose the correct cast key for each field type
  • Understand where casts run in the model lifecycle
  • Add custom cast handlers/codecs safely

Ormed applies cast mappings consistently to:

  • Normalize values when assigning (fill, setAttribute)
  • Decode values when hydrating from queries
  • Encode values when persisting mutations
  • Serialize values when calling toArray() / toJson()

You can define casts:

  • Per-model with @OrmModel(casts: {...})
  • Per-field with @OrmField(cast: '...')

Built-in cast keys

These keys are available out of the box:

Cast keyDart type to useStored asNotes
dateDateTime / DateTime?Date (driver-dependent)Normalizes to a date-only DateTime.
datetimeDateTime / DateTime?DateTime or ISO-8601 string (driver-dependent)Supports CarbonInterface for input. Decodes to DateTime.
timestampint / int?Unix timestamp (seconds)Converts from DateTime, CarbonInterface, or numeric values.
decimalDecimal / Decimal?StringUses package:decimal.
bool / booleanbool / bool?BooleanAccepts numeric and string inputs.
int / integerint / int?IntegerAccepts numeric and string inputs.
double / float / realdouble / double?FloatAccepts numeric and string inputs.
stringString / String?StringCoerces values to strings.
enumMyEnum / MyEnum?Enum nameRequires @OrmField(cast: 'enum').
encryptedString / String?Encrypted stringRequires a registered encrypter.
jsonMap<String, Object?> / Map<String, Object?>?JSON stringFor JSON objects
objectMap<String, Object?> / Map<String, Object?>?JSON stringAlias for json
arrayList<Object?> / List<Object?>?JSON stringFor JSON arrays
Enum + encrypted casts

enum casts require @OrmField(cast: 'enum') on an enum-typed field. Encrypted casts require an encrypter to be registered via dataSource.codecRegistry.registerEncrypter(...) (recommended) or ValueCodecRegistry.instance.registerEncrypter(...) before decoding/encoding. Encrypted values are decrypted in model attributes and serialization; encryption is applied only when persisting changes.

Decimal scale

Use decimal:2 (or any decimal:<scale>) to format decimal values with a fixed scale on write. Decoding always returns a Decimal.

Use built-in keys first; add custom handlers only when behavior cannot be expressed with existing codecs.

When casts run

Ormed applies casts at multiple lifecycle points:

  • Assign: fill, forceFill, setAttribute on tracked models.
  • Hydrate: query results → model attributes.
  • Persist: building insert/update payloads for the driver.
  • Serialize: toArray() / toJson() output.

Custom cast handlers receive context.operation so you can branch per stage:

class MaskOnSerializeCastHandler extends AttributeCastHandler {
const MaskOnSerializeCastHandler();


Object? encode(Object? value, AttributeCastContext context) {
final text = value?.toString();
if (context.operation == CastOperation.serialize) {
return text == null ? null : '***';
}
return text;
}


Object? decode(Object? value, AttributeCastContext context) {
return value?.toString();
}
}

Notes for encrypted casts:

  • Assign uses the raw value (no decryption), so you can pass plaintext in fill.
  • Serialize does not re-encrypt values (so APIs get plaintext).
  • Persist encrypts before writing to the database.

Defining casts

(
table: 'settings',
casts: {
// Stored as JSON string, read as Map<String, Object?>
'metadata': 'json',
// Stored/read as DateTime (or parsed from ISO-8601 string)
'createdAt': 'datetime',
},
)
class Settings extends Model<Settings> {
const Settings({required this.id, this.metadata, this.createdAt});

(isPrimaryKey: true, autoIncrement: true)
final int id;

final Map<String, Object?>? metadata;
final DateTime? createdAt;
}

Custom cast handlers

For context-aware casts (like formatters or per-field logic), register an AttributeCastHandler and reference it by key from your model metadata.

class UppercaseCastHandler extends AttributeCastHandler {
const UppercaseCastHandler();


Object? encode(Object? value, AttributeCastContext context) {
final text = value?.toString();
return text?.toUpperCase();
}


Object? decode(Object? value, AttributeCastContext context) {
return value?.toString();
}
}
void registerCastHandlers(DataSource dataSource) {
dataSource.codecRegistry.registerCastHandler(
key: 'upper',
handler: const UppercaseCastHandler(),
);
}

Register handlers on the data source registry (or before DataSource creation) so the active connection can see them.

Enum casts

Enum casts store values by name and hydrate back to enum instances when possible.

enum AccountStatus { active, disabled }

(table: 'accounts')
class Account extends Model<Account> {
const Account({required this.id, required this.status, required this.secret});

(isPrimaryKey: true)
final int id;

(cast: 'enum')
final AccountStatus status;

(cast: 'encrypted')
final String secret;
}

Notes:

  • Stored values are enum names (active, disabled, etc).
  • Hydration accepts enum names or numeric indexes when values are available.
  • If the field metadata doesn’t include enum values, Ormed leaves the raw value.

Encrypted casts

Encrypted casts require a ValueEncrypter registration. Register per data source to avoid leaking secrets across connections.

class ExampleEncrypter extends ValueEncrypter {
const ExampleEncrypter();


String encrypt(String value) => base64.encode(utf8.encode(value));


String decrypt(String value) => utf8.decode(base64.decode(value));
}

void registerEncrypter(DataSource dataSource) {
dataSource.codecRegistry.registerEncrypter(const ExampleEncrypter());
}

Encrypted casts can wrap other cast keys by using arguments, e.g. encrypted:json or encrypted:decimal:2 (see below).

Cast arguments

Some casts accept arguments via key:arg:

(
table: 'invoices',
casts: {'amount': 'decimal:2', 'metadata': 'encrypted:json'},
)
class Invoice extends Model<Invoice> {
const Invoice({required this.id, this.amount, this.metadata});

(isPrimaryKey: true)
final int id;

final Decimal? amount;
final Map<String, Object?>? metadata;
}

Examples:

  • decimal:2 formats writes with a fixed scale.
  • encrypted:json JSON-encodes values before encrypting.

Precedence

When multiple options are present, Ormed resolves the codec in this order:

  1. @OrmField(codec: SomeCodecType) (explicit codec type)
  2. @OrmField(cast: 'someKey')
  3. @OrmModel(casts: {'fieldName': 'someKey'})
  4. Default based on the field Dart type (e.g. DateTime)

Custom cast keys (custom codecs)

You can define your own cast keys by registering a codec under that key, then referencing the key from casts / cast.

class UriCodec extends ValueCodec<Uri> {
const UriCodec();


Object? encode(Uri? value) => value?.toString();


Uri? decode(Object? value) =>
value == null ? null : Uri.parse(value as String);
}

Read This Next

Verify Casting Behavior

Add tests for:

  1. Assignment normalization (fill / setAttribute).
  2. Query hydration back into typed fields.
  3. Serialization output (toArray / toJson).