diff --git a/README.md b/README.md index 2a2e0c5..7564ead 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ import 'my_class.dart'; final Dartson serializer = _serializer$dartson.useCodec(json); ``` +### Private properties + +It's not possible to encode / decode private properties. To set private properties, expose these within the constructor +and provide a getter for encoding the entity. ### Encoding / decoding lists diff --git a/changelog.md b/changelog.md index 1988152..59e393b 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,11 @@ ## 1.0.0-alpha+3 +- Add support for final properties +- Add support to skip private properties +- Add support for constructor arguments +- Add support for getters + **Breaking changes** - `DateTimeParser` is deprecated. The default is now the `json_serializable` implementation and it's no longer necessary to register a transformer at all. diff --git a/lib/src/generator/entity_generator.dart b/lib/src/generator/entity_generator.dart index 0c47d13..24f53dd 100644 --- a/lib/src/generator/entity_generator.dart +++ b/lib/src/generator/entity_generator.dart @@ -6,6 +6,7 @@ import 'package:json_serializable/src/type_helpers/enum_helper.dart'; import 'package:json_serializable/src/type_helpers/iterable_helper.dart'; import 'package:json_serializable/src/type_helpers/map_helper.dart'; +import '../annotations.dart'; import 'entity_type_helper.dart'; import 'field_context.dart'; import 'identifier.dart'; @@ -47,8 +48,9 @@ class EntityGenerator { .newInstance([]).assignFinal('obj')); for (var field in _fields) { - final fieldProperty = propertyAnnotation(field); - if (fieldProperty.ignore) { + final fieldProperty = propertyAnnotation( + field.getter.metadata.isNotEmpty ? field.getter : field); + if (field.isPrivate || fieldProperty.ignore) { continue; } @@ -78,12 +80,59 @@ class EntityGenerator { } Method _buildDecoder() { + final constructorParameters = []; + final constructorNamedParameters = {}; + final passedConstructorParameters = []; + + // Get constructor arguments. + final constructorArguments = _element.constructors?.first?.parameters ?? []; + for (var field in constructorArguments) { + Property fieldProperty; + if (field.isInitializingFormal) { + // Fetch details from matching field. + fieldProperty = propertyAnnotation( + _fields.firstWhere((fe) => fe.name == field.name)); + + passedConstructorParameters.add(field.displayName); + } else { + // Check for annotations. + fieldProperty = propertyAnnotation(field); + } + + if (fieldProperty.ignore && field.isNotOptional) { + // TODO: Throw proper error. + throw 'NotOptional marked as ignored'; + } + + if (fieldProperty.ignore) { + continue; + } + + final fieldContext = FieldContext(true, field.metadata, _helpers); + _fieldContexts.add(fieldContext); + + final expression = CodeExpression(Code(fieldContext.deserialize( + field.type, 'data[\'${fieldProperty.name ?? field.displayName}\']'))); + if (field.isPositional) { + constructorParameters.add(expression); + } else { + constructorNamedParameters[field.name] = expression; + } + } + final block = BlockBuilder() ..statements.add(Code('if (data == null) { return null; }')) - ..addExpression( - refer(_element.displayName).newInstance([]).assignFinal('obj')); + ..addExpression(refer(_element.displayName) + .newInstance(constructorParameters, constructorNamedParameters) + .assignFinal('obj')); for (var field in _fields) { + if (field.isFinal || + passedConstructorParameters.contains(field.name) || + field.isPrivate) { + continue; + } + final fieldProperty = propertyAnnotation(field); if (fieldProperty.ignore) { continue; diff --git a/lib/src/generator/utils.dart b/lib/src/generator/utils.dart index 797c593..1bb168a 100644 --- a/lib/src/generator/utils.dart +++ b/lib/src/generator/utils.dart @@ -3,7 +3,7 @@ import 'package:analyzer/src/dart/resolver/inheritance_manager.dart'; import 'package:source_gen/source_gen.dart'; import 'package:dartson/dartson.dart'; -Property propertyAnnotation(FieldElement element) { +Property propertyAnnotation(Element element) { final annotations = TypeChecker.fromRuntime(Property).annotationsOf(element); if (annotations.isEmpty) { return Property(); diff --git a/test/dartson_test.dart b/test/dartson_test.dart index 49eb29d..7223d79 100644 --- a/test/dartson_test.dart +++ b/test/dartson_test.dart @@ -32,7 +32,7 @@ void main() { }); test('should encode the object', () { - final result = serializer.encode(MyClass() + final result = serializer.encode(MyClass('test') ..name = 'hello' ..numDouble = 1.0 ..subClass = (SubClass()..name = 'test')); @@ -53,7 +53,7 @@ void main() { }); test('should encode lists', () { - final result = serializer.encode(MyClass() + final result = serializer.encode(MyClass('test') ..subClasses = [ SubClass() ..name = '1' @@ -91,7 +91,7 @@ void main() { }); test('should encode maps', () { - final result = serializer.encode(MyClass() + final result = serializer.encode(MyClass('test') ..complexMap = { 't1': SubClass()..name = 't1', 't2': SubClass()..name = 't2', @@ -116,8 +116,8 @@ void main() { }); test('should encode replacements', () { - final result = - serializer.encode(MyClass()..replacement = (MyImpl()..name = 'test')); + final result = serializer + .encode(MyClass('test')..replacement = (MyImpl()..name = 'test')); expect(result['replacement']['name'], 'test'); }); @@ -161,8 +161,8 @@ void main() { test('should encode lists', () { final result = serializer.encodeList([ - MyClass()..name = 'test1', - MyClass()..name = 'test2', + MyClass('test')..name = 'test1', + MyClass('test')..name = 'test2', ]) as List>; expect(result, allOf(isList, hasLength(2))); diff --git a/test/src/my_class.dart b/test/src/my_class.dart index 66867e7..7053742 100644 --- a/test/src/my_class.dart +++ b/test/src/my_class.dart @@ -7,6 +7,7 @@ enum MyEnum { firstValue, secondValue } enum SecondEnum { has, nothing } class MyClass extends BaseClass { + final String finalProp; String name; int number; @Property(name: 'boolean') @@ -22,6 +23,14 @@ class MyClass extends BaseClass { List subClasses; Map complexMap; MyAbstr replacement; + String _private; + + MyClass(this.finalProp, + {this.ignored, @Property(name: 'private') String renamedPrivate}) + : _private = renamedPrivate; + + @Property(name: 'private') + String get privateGetter => _private; } class BaseClass { diff --git a/test/src/serializer.g.dart b/test/src/serializer.g.dart index ba1bb24..2c8e785 100644 --- a/test/src/serializer.g.dart +++ b/test/src/serializer.g.dart @@ -12,6 +12,7 @@ Map _MyClass$encoder(MyClass object, Dartson inst) { return null; } final obj = new Map(); + obj['finalProp'] = object.finalProp; obj['name'] = object.name; obj['number'] = object.number; obj['boolean'] = object.hasBoolean; @@ -26,6 +27,7 @@ Map _MyClass$encoder(MyClass object, Dartson inst) { obj['complexMap'] = object.complexMap?.map((k, e) => MapEntry(k, _SubClass$encoder(e, inst))); obj['replacement'] = _MyImpl$encoder(object.replacement, inst); + obj['private'] = object.privateGetter; obj['inherited'] = object.inherited; obj['inheritName'] = object.inheritedRenamed; return obj; @@ -35,7 +37,8 @@ MyClass _MyClass$decoder(Map data, Dartson inst) { if (data == null) { return null; } - final obj = new MyClass(); + final obj = new MyClass(data['finalProp'] as String, + renamedPrivate: data['private'] as String); obj.name = data['name'] as String; obj.number = data['number'] as int; obj.hasBoolean = data['boolean'] as bool; diff --git a/test/src/usage.dart b/test/src/usage.dart index fb2cbfa..3048113 100644 --- a/test/src/usage.dart +++ b/test/src/usage.dart @@ -2,7 +2,7 @@ import 'serializer.dart'; import 'my_class.dart'; void usage() { - final myClass = MyClass() + final myClass = MyClass('test') ..name = 'test name' ..number = 29 ..hasBoolean = true;