Skip to main content

Casting

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

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

:::note 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. :::

:::note 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).