dart_mappable

Improved json serialization and data classes with full support for generics, inheritance, customization and more.

MIT License

Stars
143

Bot releases are hidden (Show)

dart_mappable - v4.0.0 Latest Release

Published by schultek about 1 year ago

Changelog

  • Require sdk: >=3.0.0.

  • Added support for Records.

    • Fields of a class can now be any record type.

    • You can annotate toplevel record typedefs:

        @MappableRecord()
        typedef Coordinates = ({double latitude, double longitude});
      

Documentation

Record Fields

dart_mappable supports record fields out of the box.
When you define a class, any field using a record type will be automatically included in the serialization.

@MappableClass()
class Location with LocationMappable {
  
  const Location({
    required this.name,
    required this.coordinates, 
  });

  final (String, String) name;
  final ({double lat, double lng}) coordinates;
}

An instance of this class would be encoded to:

{
  "name": {
    "$1": "John",
    "$2": "Doe"
  },
  "coordinates": {
    "lat": 123,
    "lng": 456
  }
}

Record Aliases

Additionally, you can create record type aliases using annotated typedefs:

@MappableRecord()
typedef Coordinates = ({double lat, double lng});

When using this type for a field of another class, it will be serialized as expected.

This also allows you to serialize records using the standalone generated mapper class as well as the generated extension methods:

void main() {
  // Using the generated mapper class.
  Coordinates coords  = CoordinatesMapper.fromJson('{"lat": 123, "lng": 456}');
  
  // Using the generated extension methods.
  String json = coords.toJson();
}

Renaming Record Properties

You can also annotate individual fields of a record alias to change the key or add a hook:

@MappableRecord()
typedef FullName = (@MappableField(key: 'firstName') String, @MappableField(hook: MyHook()) String);

This will only work on annotated record type aliases, not inline record fields.

Note: Since typedefs are just an alias for a type, you cannot define two aliases for the same type
with different @MappableField() modifiers. Only one will be used for serialization.

dart_mappable - v4.0.0-dev.0

Published by schultek over 1 year ago

Changelog

  • Require sdk: >=3.0.0.

  • Added support for Records.

    • Fields of a class can now be any record type.

    • You can annotate toplevel record typedefs:

        @MappableRecord()
        typedef Coordinates = ({double latitude, double longitude});
      

Documentation

When you define a class, any record field will be automatically included in the serialization.

@MappableClass()
class Location with PersonMappable {
  
  const Position({
    required this.name,
    required this.coordinates, 
  });

  final (String, String) name;
  final ({double lat, double lng}) coordinates;
}

An instance of this class would be encoded to:

{
  "name": {
    "$1": "John",
    "$2": "Doe"
  },
  "coordinates": {
    "lat": 123,
    "lng": 456
  }
}

Record Aliases

Additionally, you can create record type aliases using annotated typedefs:

@MappableRecord()
typedef Coordinates = ({double lat, double lng});

This allows you to serialize records using the standalone generated mapper class as well as the generated extension methods:

void main() {
  // Using the generated mapper class.
  var coords  = CoordinatesMapper.fromJson('{"lat": 123, "lng": 456}');
  
  // Using the generated extension methods.
  var json = coords.toJson();
}

You can also annotate individual fields of a record to e.g. change the key:

@MappableRecord()
typedef FullName = (@MappableField(key: 'firstName') String, String);

This would serialize to:

{"firstName": "John", "$2": "Doe"}

Note: Since typedefs are just an alias for a type, you cannot define two aliases for the same type
with different @MappableField() modifiers. Only one will be used for serialization.

dart_mappable - v3.1.1

Published by schultek over 1 year ago

Changelog

  • The builder now respects basic initializer expressions of a constructor. This makes it
    possible to do field renaming or assigning to private fields without requiring an additional
    getter matching the parameter.

    The following is now supported out of the box:

    class MyClass {
      MyClass(int value, {String? name}) 
          // Effectively renaming 'value' to 'data'.
        : data = value,
          // Assigning to a private field + having a null fallback.
          _name = name ?? 'Unnamed';
    
      final int value;
      final String _name;
    
  • Fixed encoding of a map now returns a Map<String, dynamic> where possible
    (instead of a Map<dynamic, dynamic>)

  • Fixed supporting expressions in @MappableClass.includeCustomMappers.

  • Fixed error when using non-literal values in @MappableValue().

  • Fixed generic decoding when using generic superclass.

  • Fixed unknown-type bug for serialized non-constructor fields.

  • Fixed bug with undetermined includeSubClasses.

dart_mappable - v3.0.0

Published by schultek over 1 year ago

Changelog

  • Breaking: Generated mappers no longer have a .container property. This was removed in favor
    of the new MapperContainer.globals container.

    // Instead of this:
    var value = MyClassMapper.container.fromValue(...);
    // Do this:
    var value = MapperContainer.globals.fromValue(...);
    

    Mapper initialization and usage is now simplified to the following:

    1. When used explicitly (e.g. through MyClassMapper.fromMap or myClass.toMap()) no additional
      initialization is needed.
    2. When used implicitly (through a generic type e.g. MapperContainer.globals.fromMap<MyClass>()) the
      mapper needs to be initialized once before being used with MyClassMapper.ensureInitialized().
  • Breaking: Changed internal mapper implementation which causes any custom mapper to break.

    • Removed MapperElementBase class.
    • Added MappingContext being passed to mapper methods.

    See docs on how to use custom mappers in v3.

  • Breaking: Removed @MappableLib.createCombinedContainer in favor of @MappableLib.generateInitializerForScope.

    Instead of generating a new container, v3 generates an initialization function for all mappers. Use it early on in your
    application:

    @MappableLib(generateInitializerForScope: InitializerScope.package)
    library main;
    
    import 'main.init.dart';
    
    void main() {
      initializeMappers();
      ...
    }
    
  • Breaking: Improved support and features for .copyWith.

    • Copy-With now supports classes that implement multiple interfaces.
    • Renamed .copyWith.apply() method to .copyWith.$update().
    • Added .copyWith.$merge() and .copyWith.$delta().

    You can now use .copyWith with either an existing instance using .$merge or a map of values using .$delta.

    @MappableClass()
    class A with AMappable {
      A(this.a, this.b);
      
      int? a;
      int? b;
    }
    
    void main() {
      var a = A(1, null);
      
      var c = a.copyWith.$merge(A(null, 2));
      assert(c == A(1, 2));
    
      var d = a.copyWith.$delta({'b': 2});
      assert(d == A(1, 2));
    }
    
  • Breaking: Removed CheckTypesHook in favor of discriminator functions.

    You can now use a custom predicate function as the discriminatorValue of a class. This function can check
    whether the encoded value should be decoded to this subclass and return a boolean.

    @MappableClass()
    abstract class A with AMappable {
      A();
    }
    
    @MappableClass(discriminatorValue: B.checkType)
    class B extends A with BMappable {
      B();
      
      /// checks if [value] should be decoded to [B]
      static bool checkType(value) {
        return value is Map && value['isB'] == true;
      }
    }
    
    @MappableClass(discriminatorValue: C.checkType)
    class C extends A with CMappable {
      C();
      
      /// checks if [value] should be decoded to [C]
      static bool checkType(value) {
        return value is Map && value['isWhat'] == 'C';
      }
    }
    
  • Added support for serializing fields that are not part of the constructor
    when annotated with @MappableField().

  • Added EncodingOptions to toValue method.

  • Added support for third-party models by using annotated typedefs.

  • Added renameMethods to build options.

  • Improved performance of generated encoding and decoding methods.

For a detailed migration guide, see this issue.

dart_mappable - v2.0.0

Published by schultek almost 2 years ago

  • Mappers are now generated for each file containing annotated classes. This removes the
    need to specify entry points in the build.yaml.

    This is now similar to how packages like json_serializable or freezed generate code.

    • Generated files are now part files and need to be included as such.
    • All annotated classes must now use their respective <MyClass>Mappable mixin.
    • Instead of one global Mapper each class has its own <ClassName>Mapper.
      • A new global container that includes all models can now be generated using
        @MappableLib(createCombinedContainer: true).
    • Mappers can be linked together to enable working with multiple classes.
    • Removed @CustomMapper annotation in favor of includeCustomMappers property on @MappableClass().

    For a detailed migration guide, see this issue.

  • Documentation is now separated from the README using the official pub.dev documentation topics.
    Find the new documentation here

  • Improvements in performance and support for generics and inheritance.

  • Added the [CheckTypesHook] to allow for custom discriminator checks on subclasses in a
    polymorphic class structure.

  • CopyWith is now more powerful and also works for generic or polymorphic classes, while being
    completely type-safe.

    When called on a superclass, the concrete subtype will be retained through a
    .copyWith call, which also respects generics:

      // with `class A` and `class B<T> extends A`
      A a = B<int>(); // static type A, dynamic type B<int>
    
      // signature will be `A copyWith()`, so static type A
      A a2 = a.copyWith(); 
    
      // this will still resolve to a dynamic type of B<int>
      assert(a2 is B<int>);
    

    For more checkout the docs
    or example