Skip to main content

Casting

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

  • 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.

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);
}