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 key | Dart type to use | Stored as | Notes |
|---|---|---|---|
date | DateTime / DateTime? | Date (driver-dependent) | Normalizes to a date-only DateTime. |
datetime | DateTime / DateTime? | DateTime or ISO-8601 string (driver-dependent) | Supports CarbonInterface for input. Decodes to DateTime. |
timestamp | int / int? | Unix timestamp (seconds) | Converts from DateTime, CarbonInterface, or numeric values. |
decimal | Decimal / Decimal? | String | Uses package:decimal. |
bool / boolean | bool / bool? | Boolean | Accepts numeric and string inputs. |
int / integer | int / int? | Integer | Accepts numeric and string inputs. |
double / float / real | double / double? | Float | Accepts numeric and string inputs. |
string | String / String? | String | Coerces values to strings. |
enum | MyEnum / MyEnum? | Enum name | Requires @OrmField(cast: 'enum'). |
encrypted | String / String? | Encrypted string | Requires a registered encrypter. |
json | Map<String, Object?> / Map<String, Object?>? | JSON string | For JSON objects |
object | Map<String, Object?> / Map<String, Object?>? | JSON string | Alias for json |
array | List<Object?> / List<Object?>? | JSON string | For JSON arrays |
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.
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,setAttributeon 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
- Model-level casts
- Field-level 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;
}
(table: 'field_cast_settings')
class FieldCastSettings extends Model<FieldCastSettings> {
const FieldCastSettings({required this.id, this.metadata});
(isPrimaryKey: true, autoIncrement: true)
final int id;
(cast: 'json')
final Map<String, Object?>? metadata;
}
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:2formats writes with a fixed scale.encrypted:jsonJSON-encodes values before encrypting.
Precedence
When multiple options are present, Ormed resolves the codec in this order:
@OrmField(codec: SomeCodecType)(explicit codec type)@OrmField(cast: 'someKey')@OrmModel(casts: {'fieldName': 'someKey'})- 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.
- Codec
- Register
- Use in model
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);
}
DataSource buildDataSource(DriverAdapter driver) {
return DataSource(
DataSourceOptions(
driver: driver,
entities: [
SettingsOrmDefinition.definition,
FieldCastSettingsOrmDefinition.definition,
LinkOrmDefinition.definition,
],
codecs: {'uri': const UriCodec()},
),
);
}
(table: 'links', casts: {'website': 'uri'})
class Link extends Model<Link> {
const Link({required this.id, this.website});
(isPrimaryKey: true, autoIncrement: true)
final int id;
final Uri? website;
}