Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SimpleMapperBounded1 fails to decode if TypePlus hasn't resolved the types #271

Open
LeoBound opened this issue Feb 13, 2025 · 4 comments
Open

Comments

@LeoBound
Copy link

This is an odd one I've come across recently using the generic classes.

A solution seems to be to adding all possible subclasses to TypePlus using TypePlus.add().

My software uses this for unit conversions and uses a type parameter for Weight, Length, Angle (lots of these) etc to ensure that you can't subtract incorrect units,

My classses
class Unit {}
class Length extends Unit {}
class Mass extends Unit {}
...

class UnitValue<T extends Unit> {
final T unit;
final double value;

// Supports various operations on values with units and handles conversions etc.
}

class UnitValueMapper extends SimpleMapper1Bounded<UnitValue, Unit> {
  const UnitValueMapper();

  static const _valueKey = "val";
  static const _unitKey = "unit";

  static final _unitRegistry = [
    Length.meter,
    Length.mile,
    Length.yard,
    ...etc
  ];


// Get the unit from the symbol, then cast UnitValue<Unit> to UnitValue<A> (known from a MappableClass field declaration)
  @override
  UnitValue<A> decode<A extends Unit>(value) =>
      UnitValue(_unitRegistry.where((u) => value[_unitKey] == u.symbol).single, value[_valueKey]).cast<A>();

  @override
  Json encode<A extends Unit>(UnitValue self) => {_unitKey: self.unit.symbol, _valueKey: self.value};

  @override
  Function get typeFactory => <T extends Unit>(dynamic f) => f<UnitValue<T>>();
}

There might be a simpler way to cause this to occur but the below code triggers it.
Removing TypePlus.add<Parent>() will cause all of the mappers to throw the assertion error
Failed assertion: line 51 pos 12: 'context.args.length == 1': is not true,
Removing either of the TypePlus.add<ChildX>() lines will cause it's respective mapper to throw
MapperException: Failed to encode (Container<Child1>): type 'dynamic' is not a subtype of type 'Parent' of 'A'

Minimal Example
import 'package:dart_mappable/dart_mappable.dart';
import 'package:type_plus/type_plus.dart';

class Parent {
  final String type;
  Parent(this.type);
}

class Child1 extends Parent {Child1(super.type);}
class Child2 extends Parent {Child2(super.type);}

class Container<T extends Parent> {
  Container(this.object);
  final T object;
}

class ContainerMapper extends SimpleMapper1Bounded<Container, Parent> {
  final _key = 'name';
  @override
  Container<Parent> decode<A extends Parent>(dynamic value) {
    final str = value[_key];
    return switch (str) {
      'parent' => Container(Parent(value)),
      'child1' => Container(Child1(value)),
      'child2' => Container(Child2(value)),
      _ => throw UnimplementedError()
    };
  }

  @override
  Object? encode<A extends Parent>(covariant Container<Parent> self) => {_key: self.object.type};

  @override
  Function get typeFactory => <T extends Parent>(dynamic f) => f<Container<T>>();
}

void main(List<String> arguments) {
  MapperContainer.globals.use(ContainerMapper());

  final p = Container(Parent('parent'));
  TypePlus.add<Parent>();
  final c1 = Container(Child1('child1'));
  TypePlus.add<Child1>();
  final c2 = Container(Child2('child2'));
  TypePlus.add<Child2>();

  final pJson = MapperContainer.globals.toJson(p);
  final c1Json = MapperContainer.globals.toJson(c1);
  final c2Json = MapperContainer.globals.toJson(c2);
  print(pJson);
  print(c1Json);
  print(c2Json);
}


</details>

@schultek
Copy link
Owner

I think thats expected because neither Parent nor ChildX are @MappableClass types, so dart_mappable (or in extension type_plus) cannot know about them automatically.

@LeoBound
Copy link
Author

I understand why it happens but maybe an extension method in the mapper container would be handy (something like MapperContainer.globals.registerType<T>()), along with some documentation mentioning this. It was a bit difficult to track down and having to import TypePlus just to add it feels a bit hacky.

@schultek
Copy link
Owner

There is MapperBase.addType doing exactly that.

Not sure if I ever documented that. Maybe not. Lets fix that.

@LeoBound
Copy link
Author

Ah so there is! I'll switch to using that instead of TypePlus.add() 😄
I couldn't find any docs on MapperBase.addType but I may have just missed it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants