Hi, first of all, thank you for dart_mappable — it has become one of the most ergonomic codegen libraries in the Dart ecosystem.
I would like to request support for Dart extension types.
Extension types are now a first-class language feature in Dart and are intended to provide a zero-cost, compile-time abstraction over an underlying representation type. They are especially relevant for value objects, domain-specific wrappers, and JS interop style APIs. ([dart.dev][1])
At the moment, dart_mappable already supports many advanced language and modeling scenarios, but I could not find obvious support or an existing dedicated issue for extension types in the current public issue list and package docs/changelog snippets I checked. ([GitHub][2])
Motivation
Extension types are a very natural fit for serialization/mapping because they are often used to model:
- strongly typed IDs
- validated scalar wrappers
- domain-specific units
- lightweight wrappers around primitives or collection-like types
- transport-safe representations without heap-allocation overhead
Today, users likely need to manually bridge these types using hooks/converters or fall back to wrapper classes. Native support would make these models feel much more seamless.
Example use cases
1. Strongly typed identifiers
extension type UserId(String value) {
@override
String toString() => value;
}
@MappableClass()
class User with UserMappable {
final UserId id;
final String name;
const User({
required this.id,
required this.name,
});
}
Expected behavior:
- encode
UserId as its representation type (String)
- decode
String back into UserId
Desired JSON:
{
"id": "user_123",
"name": "Duong"
}
2. Domain value objects over primitives
extension type EmailAddress(String value) {
bool get isValid => value.contains('@');
}
This is a common pattern for domain modeling. It would be ideal if dart_mappable treated this similarly to a scalar value object.
3. Numeric/unit wrappers
extension type Meters(double value) {
double toKm() => value / 1000;
}
Useful for API models where transport uses a primitive but the domain wants stronger semantics.
4. Transparent wrappers that implement the representation type
extension type UserId(String value) implements String {}
For transparent extension types, it may be possible to treat them even closer to the representation type during mapper resolution.
5. Nested use inside collections / generics
@MappableClass()
class Team with TeamMappable {
final List<UserId> memberIds;
const Team(this.memberIds);
}
Support here would be especially valuable, because this is where manual conversion becomes repetitive.
Possible behavior model
A reasonable first version could treat extension types as scalar-like wrappers over their representation type.
For an extension type like:
extension type UserId(String value) {}
the generated mapper could conceptually behave like:
- encode:
UserId -> String
- decode:
String -> UserId
That would align with how extension types are intended to act as a static abstraction over an existing representation type. ([dart.dev][1])
Potential implementation directions
Option A — Treat extension types as mappable value wrappers automatically
The builder detects extension type X(T representation) and generates a mapper equivalent to a custom scalar mapper.
Conceptually:
class UserIdMapper extends SimpleMapper1<UserId, String> {
const UserIdMapper();
@override
UserId decode(String value) => UserId(value);
@override
String encode(UserId self) => self.value;
}
Pros:
- most ergonomic for users
- works naturally in fields, lists, maps, nested generics
- matches the common “strong typedef/value object” use case
Cons:
- builder must introspect extension type declarations correctly
- access to the representation field/member may need care depending on syntax and visibility rules
Option B — Add an annotation specifically for extension types
Something like:
@MappableExtensionType()
extension type UserId(String value) {}
Pros:
- explicit opt-in
- easier migration story
- less risk of surprising behavior
Cons:
- slightly more API surface
- less “it just works”
Option C — Support via generated mapper hook only
Allow or document a standard way to annotate/register an extension type so dart_mappable generates only the bridging mapper and then reuses it transitively in classes.
Pros:
- narrower scope
- easier initial rollout
Cons:
- weaker DX than first-class support
- users still need extra boilerplate for a very common pattern
Suggested decoding/encoding rules
A possible set of rules:
-
If a field type is an extension type over T, encode it exactly as T.
-
Decode from T into the extension type constructor.
-
Collections and generics should recurse normally:
List<UserId> ↔ List<String>
Map<String, UserId> ↔ Map<String, String>
-
Nullable variants should work naturally:
-
Errors should be surfaced using the same decode error model as other scalar conversions.
Edge cases worth considering
1. Multiple constructors / custom factories
Some extension types may define extra constructors or factories. It may be best to use the primary representation constructor by default.
2. Private/internal representation details
If codegen cannot safely access the representation member, support may need to be limited to public/simple forms first.
3. Non-trivial extension types
Some extension types are not just “value wrappers” and expose a constrained or altered interface. In those cases, mapping by raw representation may still be correct, but the rules should be explicit.
4. Transparent vs non-transparent extension types
Since extension types can either implement the representation type or remain distinct from it, mapper resolution should probably be based on the declared extension type itself, not only assignability. ([dart.dev][1])
5. JS interop-oriented extension types
Some extension types are intended for dart:js_interop and may not be meaningful for JSON serialization. It may be worth documenting that only representation-backed, serializable extension types are supported. Dart’s own docs highlight JS interop as a major use case for extension types. ([dart.dev][3])
Why this would be valuable
dart_mappable already positions itself as supporting advanced use cases like generics, inheritance, and richer data-class workflows. Extension type support feels like a natural next step for modern Dart domain modeling. ([Dart packages][4])
This would let users model APIs more safely without sacrificing performance or falling back to wrapper classes just for serialization.
Minimal desired outcome
Even a limited first version would already be very useful if it supported:
- extension types over primitive representation types
- automatic encode/decode by representation type
- usage inside annotated classes and collections
- nullable support
For example:
extension type UserId(String value) {}
@MappableClass()
class User with UserMappable {
final UserId id;
const User(this.id);
}
with generated mapping equivalent to:
UserMapper.fromMap({'id': 'abc'}) == User(UserId('abc'));
User(UserId('abc')).toMap() == {'id': 'abc'};
Thanks for considering this feature.
Hi, first of all, thank you for
dart_mappable— it has become one of the most ergonomic codegen libraries in the Dart ecosystem.I would like to request support for Dart extension types.
Extension types are now a first-class language feature in Dart and are intended to provide a zero-cost, compile-time abstraction over an underlying representation type. They are especially relevant for value objects, domain-specific wrappers, and JS interop style APIs. ([dart.dev][1])
At the moment,
dart_mappablealready supports many advanced language and modeling scenarios, but I could not find obvious support or an existing dedicated issue for extension types in the current public issue list and package docs/changelog snippets I checked. ([GitHub][2])Motivation
Extension types are a very natural fit for serialization/mapping because they are often used to model:
Today, users likely need to manually bridge these types using hooks/converters or fall back to wrapper classes. Native support would make these models feel much more seamless.
Example use cases
1. Strongly typed identifiers
Expected behavior:
UserIdas its representation type (String)Stringback intoUserIdDesired JSON:
{ "id": "user_123", "name": "Duong" }2. Domain value objects over primitives
This is a common pattern for domain modeling. It would be ideal if
dart_mappabletreated this similarly to a scalar value object.3. Numeric/unit wrappers
Useful for API models where transport uses a primitive but the domain wants stronger semantics.
4. Transparent wrappers that implement the representation type
For transparent extension types, it may be possible to treat them even closer to the representation type during mapper resolution.
5. Nested use inside collections / generics
Support here would be especially valuable, because this is where manual conversion becomes repetitive.
Possible behavior model
A reasonable first version could treat extension types as scalar-like wrappers over their representation type.
For an extension type like:
the generated mapper could conceptually behave like:
UserId -> StringString -> UserIdThat would align with how extension types are intended to act as a static abstraction over an existing representation type. ([dart.dev][1])
Potential implementation directions
Option A — Treat extension types as mappable value wrappers automatically
The builder detects
extension type X(T representation)and generates a mapper equivalent to a custom scalar mapper.Conceptually:
Pros:
Cons:
Option B — Add an annotation specifically for extension types
Something like:
Pros:
Cons:
Option C — Support via generated mapper hook only
Allow or document a standard way to annotate/register an extension type so
dart_mappablegenerates only the bridging mapper and then reuses it transitively in classes.Pros:
Cons:
Suggested decoding/encoding rules
A possible set of rules:
If a field type is an extension type over
T, encode it exactly asT.Decode from
Tinto the extension type constructor.Collections and generics should recurse normally:
List<UserId>↔List<String>Map<String, UserId>↔Map<String, String>Nullable variants should work naturally:
UserId?↔String?Errors should be surfaced using the same decode error model as other scalar conversions.
Edge cases worth considering
1. Multiple constructors / custom factories
Some extension types may define extra constructors or factories. It may be best to use the primary representation constructor by default.
2. Private/internal representation details
If codegen cannot safely access the representation member, support may need to be limited to public/simple forms first.
3. Non-trivial extension types
Some extension types are not just “value wrappers” and expose a constrained or altered interface. In those cases, mapping by raw representation may still be correct, but the rules should be explicit.
4. Transparent vs non-transparent extension types
Since extension types can either implement the representation type or remain distinct from it, mapper resolution should probably be based on the declared extension type itself, not only assignability. ([dart.dev][1])
5. JS interop-oriented extension types
Some extension types are intended for
dart:js_interopand may not be meaningful for JSON serialization. It may be worth documenting that only representation-backed, serializable extension types are supported. Dart’s own docs highlight JS interop as a major use case for extension types. ([dart.dev][3])Why this would be valuable
dart_mappablealready positions itself as supporting advanced use cases like generics, inheritance, and richer data-class workflows. Extension type support feels like a natural next step for modern Dart domain modeling. ([Dart packages][4])This would let users model APIs more safely without sacrificing performance or falling back to wrapper classes just for serialization.
Minimal desired outcome
Even a limited first version would already be very useful if it supported:
For example:
with generated mapping equivalent to:
Thanks for considering this feature.