├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── build.yaml ├── doc └── proposal │ ├── feature-any-property.md │ ├── feature-cast.md │ ├── feature-custom-transformer.md │ ├── feature-dartson-parser-setup.md │ ├── feature-type-identity-replacement.md │ └── feature-type-replacement.md ├── example ├── lib │ ├── example.dart │ ├── serializer.dart │ ├── serializer.g.dart │ └── src │ │ ├── my_class.dart │ │ └── sub_class.dart └── pubspec.yaml ├── lib ├── builder.dart ├── dartson.dart ├── src │ ├── annotations.dart │ ├── exceptions.dart │ ├── generator │ │ ├── entity_generator.dart │ │ ├── entity_type_helper.dart │ │ ├── field_context.dart │ │ ├── generator.dart │ │ ├── generator_settings.dart │ │ ├── identifier.dart │ │ ├── serializer_generator.dart │ │ ├── transformer_generator.dart │ │ └── utils.dart │ ├── serializer.dart │ └── type_transformer.dart └── transformers │ └── date_time.dart ├── pubspec.yaml ├── test ├── dartson_test.dart ├── generator_test.dart ├── src │ ├── my_class.dart │ ├── my_impl.dart │ ├── serializer.dart │ ├── serializer.g.dart │ ├── sub_class.dart │ └── usage.dart ├── test_all.dart ├── test_file_utils.dart └── test_lib_utils.dart └── tool └── travis.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.js.* 3 | .settings 4 | packages 5 | .project 6 | pubspec.lock 7 | *.DS_Store 8 | *.iml 9 | .idea 10 | doc-viewer/ 11 | test/tmp/ 12 | .pub 13 | .packages -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | dart: 3 | - stable 4 | - dev 5 | script: ./tool/travis.sh 6 | sudo: false 7 | cache: 8 | directories: 9 | - $HOME/.dart_tool -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0-alpha+4 (09/26/2018) 4 | 5 | - Support `pkg:build` 1.X 6 | 7 | ## 1.0.0-alpha+3 (09/20/2018) 8 | 9 | - Add support for final properties 10 | - Add support to skip private properties 11 | - Add support for constructor arguments 12 | - Add support for getters 13 | 14 | **Breaking changes** 15 | - `DateTimeParser` is deprecated. The default is now the `json_serializable` implementation and it's no longer necessary 16 | to register a transformer at all. 17 | - Generator no longer create `_Dartson$impl` class instance and instead calls `Dartson` constructor directly 18 | 19 | ## 1.0.0-alpha+2 (09/17/2018) 20 | 21 | - Add replacement functionality 22 | - Add `encodeList` and `decodeList` for de/serializing lists directly 23 | - Fix #51 by adding latest version constrain 24 | 25 | ## 1.0.0-alpha+1 (09/10/2018) 26 | 27 | - Add `extend` method to `Dartson` to extend a serializer 28 | - Fix bug where `codec` is always set to null 29 | 30 | **Breaking changes** 31 | 32 | - Codec usage is now different, please see `README.md` 33 | - `DartsonEntityNotExistsException` renamed to `UnknownEntityException` 34 | - `NoDeserializeMethodOnTypeTransformer` renamed to `MissingDecodeMethodException` 35 | - `NoSerializeMethodOnTypeTransformer` renamed to `MissingEncodeMethodException` 36 | 37 | ## 1.0.0-alpha (09/06/2018) 38 | - Support dart 2.0 with `build_runner` 39 | - Add enum support (thanks to `json_serializable`) 40 | 41 | **Breaking changes** 42 | 43 | - `@Entity` is deprecated and ignored 44 | - See `README.md` for how to use dartson `1.0.0` 45 | - Reflection implementation is currently not supported 46 | (still under evaluation if it will be supported in the future) 47 | 48 | ## 0.2.6 (10/05/2015) 49 | - Bump dependency versions in pubspec 50 | 51 | ## 0.2.4 (7/04/2015) 52 | - Support for "double" types 53 | - Update of source_spans dependency 54 | 55 | ## 0.2.3 (5/25/2015) 56 | - Updated analyzer and test dependency (thanks to @rightisleft) 57 | 58 | ## 0.2.1 (2/10/2015) 59 | - Changed the default DateTime serialized format to ISO 8601 with Z notation. 60 | 61 | ## 0.2.0 (1/29/2015) 62 | - Added a transformer that generates static serialization rules and does not use mirrors. 63 | - Breaking changes: 64 | - You now have to instantiate a Dartson instance instead of relying on global functions. 65 | - You now add custom transformer to the Dartson instance instead of adding them globally. 66 | 67 | ## 0.1.6 (6/05/2014) 68 | - Added dartson.fill function 69 | - Compatible to dart 1.4+ 70 | 71 | ## 0.1.5 (3/14/2014) 72 | - Added dartson.TypeTransformer see [README.md](./README.md) 73 | - Added package:dartson/default_transformers.dart 74 | 75 | ## 0.1.4 76 | - Fixed @MirrorsUsed annotations 77 | - Added map and mapList functions to use an already parsed map 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Eric Schneller (eric@schnellers.name) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dartson 2 | [![Pub Version](https://img.shields.io/pub/v/dartson.svg)](https://pub.dartlang.org/packages/dartson) 3 | [![Build Status](https://travis-ci.org/eredo/dartson.svg?branch=master)](https://travis-ci.org/eredo/dartson) 4 | [![Coverage Status](https://coveralls.io/repos/github/eredo/dartson/badge.svg)](https://coveralls.io/github/eredo/dartson) 5 | 6 | **Dartson 1.0.0 is currently in alpha. The public API might be subject to change. For further details of 7 | potential breaks and a roadmap take a look at [project 1.0.0](https://github.com/eredo/dartson/projects/1).** 8 | 9 | Dartson is a dart library which converts Dart Objects into their JSON representation. It helps you keep your code clean 10 | of `fromJSON` and `toJSON` functions by providing a builder which generates the serialization methods. 11 | 12 | ## Usage 13 | 14 | Add the following lines to your `pubspec.yaml` in order to use dartson: 15 | 16 | ``` 17 | dependencies: 18 | dartson: ^1.0.0-alpha+4 19 | 20 | dev_dependencies: 21 | build_runner: ^0.10.0 22 | ``` 23 | 24 | Dartson is using a central serializer instead of serializers for each object, therefore create a 25 | central file which refers the objects that need to be serialized: 26 | 27 | ```dart 28 | import 'package:dartson/dartson.dart'; 29 | import 'package:some_dependency/some_class.dart'; 30 | 31 | import 'my_class.dart'; 32 | 33 | @Serializer( 34 | entities: [ 35 | MyClass, 36 | SomeClass, 37 | ], 38 | ) 39 | final Dartson> serializer = _serializer$dartson; 40 | ``` 41 | 42 | Dartson encodes and decodes into a serializable Map (`Map`) by default. In order to 43 | encode and decode into a json string (in previous versions done by using `Dartson.JSON`) directly, call the 44 | `useCodec` method on the generated `Dartson` instance, which creates a new instance using the provided codec. 45 | 46 | ```dart 47 | import 'dart:convert'; 48 | 49 | import 'package:dartson/dartson.dart'; 50 | import 'package:some_dependency/some_class.dart'; 51 | 52 | import 'my_class.dart'; 53 | 54 | @Serializer( 55 | entities: [ 56 | MyClass, 57 | SomeClass, 58 | ], 59 | ) 60 | final Dartson serializer = _serializer$dartson.useCodec(json); 61 | ``` 62 | 63 | ### Private properties 64 | 65 | It's not possible to encode / decode private properties. To set private properties, expose these within the constructor 66 | and provide a getter for encoding the entity. 67 | 68 | ### Encoding / decoding lists 69 | 70 | As of dartson `>1.0.0` there are specific `encodeList` and `decodeList` methods. Because of type restrictions 71 | `encodeList` returns an `Object` and `decodeList` expects an `Object`. This should not cause any further actions when 72 | using `json` codec, however when working with the default serializer without any Codec, than a cast to 73 | `List>` might be necessary when using the `encodeList` result. 74 | 75 | ```dart 76 | main() { 77 | final result = serializer.encodeList([ 78 | MyClass()..name = 'test1', 79 | MyClass()..name = 'test2', 80 | ]) as List>; 81 | 82 | expect(result, allOf(isList, hasLength(2))); 83 | expect(result[0]['name'], 'test1'); 84 | expect(result[1]['name'], 'test2'); 85 | } 86 | ``` 87 | 88 | ### Replacing entities 89 | 90 | Sometimes entities are automatically generated and as such cannot contain any handwritten code, which could provide 91 | further logic and reduce complexity. This is where the `replacement` feature of dartson can help. 92 | 93 | Here an example of an entity called `Money` which is replaced using `MoneyImpl` for replacing the operators. 94 | 95 | ```dart 96 | import 'package:dartson/dartson.dart'; 97 | import 'package:dartson/transformers/date_time.dart'; 98 | 99 | // Imagine Money and Product couldn't be touched. 100 | class Money { 101 | double net; 102 | double gross; 103 | } 104 | 105 | class Product { 106 | Money price; 107 | String name; 108 | } 109 | 110 | 111 | class MoneyImpl extends Money { 112 | operator +(dynamic ob) { 113 | if (obj is! Money) { 114 | throw TypeError(); 115 | } 116 | 117 | net += ob.net; 118 | gross += ob.gross; 119 | } 120 | } 121 | 122 | 123 | @Serializer( 124 | entities: [ 125 | Money, 126 | Product, 127 | ], 128 | replacements: { 129 | Money: MoneyImpl, 130 | }, 131 | transformers: [ 132 | DateTimeParser, 133 | ], 134 | ) 135 | final Dartson serializer = _serializer$dartson; 136 | ``` 137 | 138 | ### Extending the serializer 139 | 140 | Dartson supports extending serializers to provide a module approach. This is necessary to support functionality 141 | like deferred loading. This also may improve build times, so when changing an entity only a part of the 142 | serializer is regenerated. 143 | 144 | **serializer_init.dart** 145 | ```dart 146 | import 'dart:convert'; 147 | 148 | import 'package:dartson/dartson.dart'; 149 | import 'package:some_dependency/some_class.dart'; 150 | 151 | import 'my_class.dart'; 152 | 153 | @Serializer( 154 | entities: [ 155 | MyClass, 156 | SomeClass, 157 | ], 158 | ) 159 | final Dartson serializer = _serializer$dartson.useCodec(json); 160 | ``` 161 | 162 | **serializer_second.dart** 163 | ```dart 164 | import 'package:dartson/dartson.dart'; 165 | 166 | import 'other_class.dart'; 167 | import 'serializer_init.dart' as fs; 168 | 169 | @Serializer( 170 | entities: [ 171 | OtherClass, 172 | ], 173 | ) 174 | final Dartson serializer = fs.serializer.extend(_serializer$dartson); 175 | ``` 176 | 177 | Notice that `extend` provides a completely new instance of `Dartson`. Also the entities provided by the 178 | serializer on which `extend` was called can be overwritten by the entities used in the serializer passed 179 | as the argument (in this case: `_serializer$dartson` entities may overwrite `fs.serializer` entities). 180 | 181 | ## Writting custom TypeTransformers 182 | Transformers are used to encode / decode none serializable types that shouldn't be treated as objects / lists 183 | (for example DateTime). 184 | 185 | ```dart 186 | 187 | /// A simple DateTime transformer which uses the toString() method. 188 | class DateTimeParser implements TypeTransformer { 189 | // Make sure to add a constant constructor, because dartson will initiate all tranformers 190 | // as constant to improve dart2js compilation. 191 | const DateTimeParser(); 192 | DateTime decode(String value) => DateTime.parse(value); 193 | String encode(DateTime value) => value.toString(); 194 | } 195 | ``` 196 | 197 | In order to use the TypeTransformer you need to register the transformer for the serializer: 198 | 199 | ```dart 200 | import 'package:dartson/dartson.dart'; 201 | import 'package:dartson/transformers/date_time.dart'; 202 | 203 | import 'my_class.dart'; 204 | 205 | @Serializer( 206 | entities: [ 207 | MyClass, 208 | ], 209 | transformers: [ 210 | DateTimeParser, 211 | ], 212 | ) 213 | final Dartson serializer = _serializer$dartson; 214 | ``` 215 | 216 | ## Roadmap for 1.0.0 alpha/beta 217 | 218 | - First alpha release evaluates and tests the reuse of `json_serializable` 219 | (refactorings during the alpha/beta will be necessary) 220 | - Additional functionality from proposals will be ported 221 | - Looking for feedback in regards of usability from users 222 | - Further benchmarking of potential bottlenecks because of single point of 223 | the builder 224 | 225 | ## Further features planned 226 | 227 | - See doc/proposal for general features 228 | - Add tool to generate serializer.dart based on `serializer.decode()` and 229 | `serializer.encode(T)` usage 230 | - Add analyzer plugin to detect potential issues of used entities which are not 231 | present in the serializer definition -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | linter: 2 | rules: 3 | - avoid_empty_else 4 | - avoid_relative_lib_imports 5 | - avoid_return_types_on_setters 6 | - avoid_types_as_parameter_names 7 | - control_flow_in_finally 8 | - no_duplicate_case_values 9 | - prefer_contains 10 | - prefer_equal_for_default_values 11 | - prefer_is_not_empty 12 | - recursive_getters 13 | - throw_in_finally 14 | - unrelated_type_equality_checks 15 | - use_rethrow_when_possible 16 | - valid_regexps -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | builders: 2 | dartson: 3 | import: "package:dartson/builder.dart" 4 | builder_factories: ["dartsonBuilder"] 5 | build_extensions: {".dart": [".dartson.g.part"]} 6 | auto_apply: dependents 7 | build_to: cache 8 | applies_builders: ["source_gen|combining_builder"] -------------------------------------------------------------------------------- /doc/proposal/feature-any-property.md: -------------------------------------------------------------------------------- 1 | # Feature proposal: `@anyProperty` 2 | 3 | This feature was requested in issue [#41](https://github.com/eredo/dartson/issues/41). 4 | 5 | ## Goal 6 | 7 | Provide a map of values which were provided in the original map during decoding but didn't have a property defined in 8 | the target object. 9 | 10 | ## Example 11 | 12 | **Note: This code is not a valid dartson implementation and only used to show the actual feature part** 13 | 14 | ```dart 15 | class ExampleObject { 16 | String name; 17 | 18 | @anyProperty 19 | Map unknownProperties; 20 | } 21 | 22 | void main() { 23 | final obj = serializer.decode('{"name":"hello","isUnknown":true}', ExampleObject); 24 | print(obj.unknownProperties); // {"isUnknown":true} 25 | } 26 | ``` -------------------------------------------------------------------------------- /doc/proposal/feature-cast.md: -------------------------------------------------------------------------------- 1 | # Feature proposal: `dartson.cast` 2 | 3 | This is a new feature which provides an easier way to work with dynamic data. 4 | 5 | ## Goal 6 | 7 | Cast entities which contain deserialized values to another entity type and back without loosing properties. Also provide 8 | a strict way of casting objects by comparing their properties with or without any exceptions. 9 | 10 | ## Example 11 | 12 | **Note: This code is not a valid dartson implementation and only used to show the actual feature part** 13 | 14 | ```dart 15 | class Account { 16 | String name; 17 | double money; 18 | } 19 | 20 | class Customer { 21 | double money; 22 | bool firstTime; 23 | 24 | bool pay(double cost) { 25 | if (money < cost) { 26 | return false; 27 | } 28 | 29 | money -= cost; 30 | return true; 31 | } 32 | } 33 | 34 | 35 | void main() { 36 | Account acc = serializer.decode('{"name":"Test account","money":10.5}', Account); 37 | final CastResult cast = serializer.cast(acc, Customer); 38 | print(cast.result.runtimeType); // Customer 39 | print(cast.unknownProperties); // {"name":"Test account"} 40 | print(cast.origin.runtimeType); // Account 41 | 42 | cast.result.pay(5.0); 43 | acc = cast.revert(); 44 | print(acc.name); // "Test account" 45 | print(acc.money); // 5.5 46 | 47 | // strict implementation 48 | serializer.cast(acc, Customer, strict: true); // throws Exception: Property "firstTime" not found 49 | 50 | // strict with exception 51 | serializer.cast(acc, Customer, strict: true, exclude: ['firstTime']); 52 | 53 | // With transformation 54 | serializer.cast(acc, Customer, transform: {'firstTime': (Account acc) => acc.name != 'test'}); 55 | 56 | // With defaults 57 | serializer.cast(acc, Customer, defaults: {'firstTime': true}); 58 | } 59 | ``` 60 | 61 | ## Questions 62 | 63 | Should cast work with immutable objects as such not modify the origin? Or should cast be able to modify the origin 64 | object when `revert` is called? -------------------------------------------------------------------------------- /doc/proposal/feature-custom-transformer.md: -------------------------------------------------------------------------------- 1 | # Feature proposal: `CustomTransformer` 2 | 3 | This feature is already provided by dartson. This proposal contains further additions 4 | and notes when this feature ships with the code generation approach including `json_serializable` 5 | support. 6 | 7 | ## Goal 8 | 9 | `CustomTransformer` provide the functionality to code the serialization and deserialization 10 | of types outside of core types. 11 | 12 | ## Example 13 | 14 | ```dart 15 | part 'example.g.dart'; 16 | 17 | class Example extends Object with _$ExampleSerializerMixin { 18 | DateTime myTime; 19 | } 20 | ``` 21 | 22 | ```dart 23 | class MyDateTransformer extends _$MyDateTransformerMixin implements CustomTransformer { 24 | String encode(DateTime date) => '${date.hour}:${date.second}'; 25 | DateTime decode(String dateStr) => new DateTime.now() 26 | ..hour = int.parse(dateStr.split(':')[0]) 27 | ..second = int.parse(dateStr.split(':')[1]); 28 | } 29 | ``` 30 | 31 | ```dart 32 | abstract class _$MyDateTransformerMixin { 33 | String get target => 'dart.core.DateTime'; 34 | } 35 | ``` 36 | 37 | ```dart 38 | void main() { 39 | var dson = new Dartson(); 40 | dson.addTransformer(new MyDateTransformer()); 41 | 42 | var obj = dson.decode('{"myTime": "14:10"}', new Example()); 43 | } 44 | ``` -------------------------------------------------------------------------------- /doc/proposal/feature-dartson-parser-setup.md: -------------------------------------------------------------------------------- 1 | # Feature proposal: `dartson.Serializer` 2 | 3 | This feature is already provided by dartson. This proposal describes how an implementation using code generation could 4 | look like. 5 | 6 | ## Goal 7 | 8 | Define a custom Serializer with defined options for certain use cases instead of having global defined serialization 9 | settings across a package. 10 | 11 | ## Example 12 | -------------------------------------------------------------------------------- /doc/proposal/feature-type-identity-replacement.md: -------------------------------------------------------------------------------- 1 | # Feature proposal: Type identity replacement 2 | 3 | This feature was somewhat supported in dartson by setting up a custom transformer which returned another type base on 4 | a property provided which identified the type that inherited the actual type of the property. 5 | 6 | ## Goal 7 | 8 | Provide a functionality to use custom implementations of a type based on a property value within the actual object. 9 | 10 | ## Example 11 | 12 | **Note: This code is not a valid dartson implementation and only used to show the actual feature part** 13 | 14 | ```dart 15 | class Company { 16 | List employee; 17 | } 18 | 19 | class User { 20 | @typeIdentifier 21 | String role; 22 | String fistName; 23 | String lastName; 24 | } 25 | 26 | @TypeIdentity('dev') 27 | class Developer extends User { 28 | List programmingLanguages; 29 | } 30 | 31 | @TypeIdentity('test') 32 | class Tester extends User { 33 | List components; 34 | } 35 | 36 | @Serializer( 37 | identityReplacement: const { 38 | User: const [Developer, Tester], 39 | }, 40 | ) 41 | final serializer = self.serializer$Serializer; 42 | 43 | void main() { 44 | final obj = serializer.decode('{"employee":[{"role": "test"}, {"role":"dev"}]}', Company); 45 | print(obj.employee[0].runtimeType); // Tester 46 | print(obj.employee[1].runtimeType); // Developer 47 | } 48 | ``` 49 | 50 | ## Reference 51 | 52 | This should also work together with [Type Replacement](./feature-type-replacement.md). 53 | 54 | ```dart 55 | class DeveloperImpl extends Developer { 56 | void writeCode(String code) => print('developer says: "This code: $code should work. Because I wrote it."'); 57 | } 58 | 59 | class TesterImpl extends Tester { 60 | void test(String code) => print('tester says: "This is broken. It\'s supposed to be \'hello world\' not \'hello world \'".'); 61 | } 62 | 63 | @Serializer( 64 | typeReplacement: const { 65 | Developer: DeveloperImpl, 66 | Tester: TesterImpl, 67 | }, 68 | identityReplacement: const { 69 | User: const [Developer, Tester], 70 | }, 71 | ) 72 | final serializer = self.serializer$Serializer; 73 | ``` -------------------------------------------------------------------------------- /doc/proposal/feature-type-replacement.md: -------------------------------------------------------------------------------- 1 | # Feature proposal: Type replacement 2 | 3 | This feature was somewhat supported in dartson by setting up a custom transformer which returned another type that 4 | inherited the actual type of the property. 5 | 6 | ## Goal 7 | 8 | Make it easy to define a custom type which is used as a replacement for another type defined in the serialization target. 9 | This is especially useful if the original object was automatically generated and the implementation contains user written 10 | code. 11 | 12 | ## Example 13 | 14 | **Note: This code is not a valid dartson implementation and only used to show the actual feature part** 15 | 16 | ```dart 17 | class ExampleObject { 18 | BaseObject myObject; 19 | } 20 | 21 | class BaseObject { 22 | String text; 23 | } 24 | 25 | class ImplementationBaseObject extends BaseObject { 26 | void append(String part) => text += part; 27 | } 28 | 29 | @Serializer( 30 | typeReplacement: const { 31 | BaseObject: ImplementationBaseObject, 32 | }, 33 | ) 34 | final serializer = self.serializer$Serializer; 35 | 36 | void main() { 37 | final obj = serializer.decode('{"myObject":{"text": "test"}}', ExampleObject); 38 | print(obj.myObject.runtimeType); // ImplementationBaseObject 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /example/lib/example.dart: -------------------------------------------------------------------------------- 1 | import 'serializer.dart'; 2 | 3 | void main() { 4 | print(serializer.encode(MyClass()..name = 'test')); 5 | print(serializer.decode('{"name":"test"}').name); 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/serializer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dartson/dartson.dart'; 4 | import 'package:dartson/transformers/date_time.dart'; 5 | 6 | import 'src/my_class.dart'; 7 | import 'src/sub_class.dart'; 8 | 9 | export 'src/my_class.dart'; 10 | export 'src/sub_class.dart'; 11 | 12 | part 'serializer.g.dart'; 13 | 14 | @Serializer( 15 | entities: [ 16 | MyClass, 17 | SubClass, 18 | ], 19 | transformers: [ 20 | DateTimeParser, 21 | ], 22 | ) 23 | final serializer = _serializer$dartson.useCodec(json); 24 | -------------------------------------------------------------------------------- /example/lib/serializer.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'serializer.dart'; 4 | 5 | // ************************************************************************** 6 | // SerializerGenerator 7 | // ************************************************************************** 8 | 9 | const _transformer0 = const DateTimeParser(); 10 | Map _MyClass$encoder(MyClass object, Dartson inst) { 11 | if (object == null) { 12 | return null; 13 | } 14 | final obj = new Map(); 15 | obj['name'] = object.name; 16 | obj['number'] = object.number; 17 | obj['boolean'] = object.hasBoolean; 18 | obj['numDouble'] = object.numDouble; 19 | obj['uri'] = object.uri?.toString(); 20 | obj['dateTime'] = _transformer0.encode(object.dateTime); 21 | obj['myEnum'] = _$MyEnumEnumMap[object.myEnum]; 22 | obj['secondEnum'] = _$SecondEnumEnumMap[object.secondEnum]; 23 | obj['subClass'] = _SubClass$encoder(object.subClass, inst); 24 | obj['subClasses'] = 25 | object.subClasses?.map((e) => _SubClass$encoder(e, inst))?.toList(); 26 | obj['complexMap'] = 27 | object.complexMap?.map((k, e) => MapEntry(k, _SubClass$encoder(e, inst))); 28 | obj['inherited'] = object.inherited; 29 | obj['inheritName'] = object.inheritedRenamed; 30 | return obj; 31 | } 32 | 33 | MyClass _MyClass$decoder(Map data, Dartson inst) { 34 | if (data == null) { 35 | return null; 36 | } 37 | final obj = new MyClass(); 38 | obj.name = data['name'] as String; 39 | obj.number = data['number'] as int; 40 | obj.hasBoolean = data['boolean'] as bool; 41 | obj.numDouble = (data['numDouble'] as num)?.toDouble(); 42 | obj.uri = data['uri'] == null ? null : Uri.parse(data['uri'] as String); 43 | obj.dateTime = _transformer0.decode(data['dateTime'] as String); 44 | obj.myEnum = _$enumDecodeNullable(_$MyEnumEnumMap, data['myEnum']); 45 | obj.secondEnum = 46 | _$enumDecodeNullable(_$SecondEnumEnumMap, data['secondEnum']); 47 | obj.subClass = _SubClass$decoder(data['subClass'], inst); 48 | obj.subClasses = (data['subClasses'] as List) 49 | ?.map((e) => _SubClass$decoder(e, inst)) 50 | ?.toList(); 51 | obj.complexMap = (data['complexMap'] as Map) 52 | ?.map((k, e) => MapEntry(k, _SubClass$decoder(e, inst))); 53 | obj.inherited = data['inherited'] as bool; 54 | obj.inheritedRenamed = data['inheritName'] as String; 55 | return obj; 56 | } 57 | 58 | const _$MyEnumEnumMap = { 59 | MyEnum.firstValue: 'firstValue', 60 | MyEnum.secondValue: 'secondValue' 61 | }; 62 | const _$SecondEnumEnumMap = { 63 | SecondEnum.has: 'has', 64 | SecondEnum.nothing: 'nothing' 65 | }; 66 | T _$enumDecode(Map enumValues, dynamic source) { 67 | if (source == null) { 68 | throw ArgumentError('A value must be provided. Supported values: ' 69 | '${enumValues.values.join(', ')}'); 70 | } 71 | return enumValues.entries 72 | .singleWhere((e) => e.value == source, 73 | orElse: () => throw ArgumentError( 74 | '`$source` is not one of the supported values: ' 75 | '${enumValues.values.join(', ')}')) 76 | .key; 77 | } 78 | 79 | T _$enumDecodeNullable(Map enumValues, dynamic source) { 80 | if (source == null) { 81 | return null; 82 | } 83 | return _$enumDecode(enumValues, source); 84 | } 85 | 86 | Map _SubClass$encoder(SubClass object, Dartson inst) { 87 | if (object == null) { 88 | return null; 89 | } 90 | final obj = new Map(); 91 | obj['name'] = object.name; 92 | obj['aliases'] = object.aliases; 93 | obj['simpleMap'] = object.simpleMap; 94 | return obj; 95 | } 96 | 97 | SubClass _SubClass$decoder(Map data, Dartson inst) { 98 | if (data == null) { 99 | return null; 100 | } 101 | final obj = new SubClass(); 102 | obj.name = data['name'] as String; 103 | obj.aliases = (data['aliases'] as List)?.map((e) => e as String)?.toList(); 104 | obj.simpleMap = (data['simpleMap'] as Map) 105 | ?.map((k, e) => MapEntry(k, (e as num)?.toDouble())); 106 | return obj; 107 | } 108 | 109 | class _Dartson$impl extends Dartson> { 110 | _Dartson$impl() 111 | : super({ 112 | MyClass: 113 | const DartsonEntity(_MyClass$encoder, _MyClass$decoder), 114 | SubClass: const DartsonEntity( 115 | _SubClass$encoder, _SubClass$decoder) 116 | }); 117 | } 118 | 119 | final _serializer$dartson = new _Dartson$impl(); 120 | -------------------------------------------------------------------------------- /example/lib/src/my_class.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartson/dartson.dart'; 2 | 3 | import 'sub_class.dart'; 4 | 5 | enum MyEnum { firstValue, secondValue } 6 | enum SecondEnum { has, nothing } 7 | 8 | class MyClass extends BaseClass { 9 | String name; 10 | int number; 11 | @Property(name: 'boolean') 12 | bool hasBoolean; 13 | @Property(ignore: true) 14 | bool ignored; 15 | double numDouble; 16 | Uri uri; 17 | DateTime dateTime; 18 | MyEnum myEnum; 19 | SecondEnum secondEnum; 20 | SubClass subClass; 21 | List subClasses; 22 | Map complexMap; 23 | } 24 | 25 | class BaseClass { 26 | bool inherited; 27 | @Property(name: 'inheritName') 28 | String inheritedRenamed; 29 | @Property(ignore: true) 30 | String inheritedIgnored; 31 | } 32 | -------------------------------------------------------------------------------- /example/lib/src/sub_class.dart: -------------------------------------------------------------------------------- 1 | class SubClass { 2 | String name; 3 | List aliases; 4 | Map simpleMap; 5 | } 6 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dartson_test 2 | 3 | dependencies: 4 | dartson: 5 | path: ../ 6 | 7 | dev_dependencies: 8 | test: ^1.2.0 9 | build_runner: ^0.10.0 -------------------------------------------------------------------------------- /lib/builder.dart: -------------------------------------------------------------------------------- 1 | library dartson.builder; 2 | 3 | import 'package:build/build.dart'; 4 | import 'package:source_gen/source_gen.dart'; 5 | 6 | import 'src/generator/generator.dart'; 7 | 8 | /// Provides a [SharedPartBuilder] for the dartson generator. See README.md for 9 | /// usage. 10 | Builder dartsonBuilder(_) => 11 | SharedPartBuilder([SerializerGenerator()], 'dartson'); 12 | -------------------------------------------------------------------------------- /lib/dartson.dart: -------------------------------------------------------------------------------- 1 | library dartson; 2 | 3 | export 'src/annotations.dart'; 4 | export 'src/exceptions.dart'; 5 | export 'src/serializer.dart'; 6 | export 'src/type_transformer.dart'; 7 | -------------------------------------------------------------------------------- /lib/src/annotations.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | /// Annotation class to mark a class as serializable. This is required 4 | /// if the dartson builder has to build an entity map for dart2js. 5 | @Deprecated('No longer necessary, as all entities need to be passed to the' 6 | ' serializer.') 7 | class Entity { 8 | const Entity(); 9 | } 10 | 11 | /// Annotation class to describe properties of a class member. 12 | class Property { 13 | final bool ignore; 14 | final String name; 15 | 16 | const Property({this.ignore = false, this.name}); 17 | } 18 | 19 | /// Defines the generation of a serializer. Assign the variable name as private 20 | /// with a suffix "$dartson" to the annotated variable. 21 | /// 22 | /// @Serializer( 23 | /// entities: [MyClass], 24 | /// replacements: {MyInterface: MyImplementation}, 25 | /// transformers: [MyCustomTransformer], 26 | /// ) 27 | /// final Dartson serializer = _serializer$dartson.useCodec(json); 28 | /// 29 | class Serializer { 30 | /// A list of entities which will be serialized by the [Serializer]. 31 | /// 32 | /// Note: All classes used within an entity must be added to this list or 33 | /// have a transformer added to the [transformers]. 34 | final List entities; 35 | 36 | /// A list of transformers which will be used to serializer classes or types 37 | /// that are not present in [entities] or can be serialized by default. 38 | /// 39 | /// All types in this list need to implement [TypeTransformer] providing both 40 | /// of the generic type definitions. Otherwise the builder will fail due to 41 | /// an unknown type. 42 | final List transformers; 43 | 44 | /// A map which defines replacements of classes, the key defines the targets 45 | /// for replacement and the value the implementations which be used instead. 46 | final Map replacements; 47 | 48 | const Serializer( 49 | {@required this.entities, this.transformers, this.replacements}); 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/exceptions.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | 3 | /// Thrown when encode or decode method is called with a dart object that 4 | /// is not available within the entities list of the serializer. 5 | class UnknownEntityException implements Exception { 6 | final Type type; 7 | 8 | UnknownEntityException(this.type); 9 | 10 | @override 11 | String toString() => 'UnknownEntityException: $type not found in the entity ' 12 | 'list of the serializer. Please make sure to add $type to the entities ' 13 | 'defined in the @Serializer annotation of your serializer.'; 14 | } 15 | 16 | /// Thrown if a [TypeTransformer] was registered to [Serializer] which doesn't 17 | /// contain an encode method. 18 | class MissingEncodeMethodException implements Exception { 19 | final String type; 20 | 21 | MissingEncodeMethodException(this.type); 22 | 23 | @override 24 | String toString() => 25 | 'MissingEncodeMethodException: There\'s no "encode" method available on ' 26 | 'TypeTransformer: $type. Please make sure to implement TypeTransformer ' 27 | 'and overwrite the interface methods.'; 28 | } 29 | 30 | /// Thrown if a [TypeTransformer] was registered to [Serializer] which doesn't 31 | /// contain an decode method. 32 | class MissingDecodeMethodException implements Exception { 33 | final String type; 34 | 35 | MissingDecodeMethodException(this.type); 36 | 37 | @override 38 | String toString() => 39 | 'MissingDecodeMethodException: There\'s no "decode" method available on ' 40 | 'TypeTransformers: $type. Please make sure to implement TypeTransformer ' 41 | 'and overwrite the interface methods.'; 42 | } 43 | 44 | /// Thrown by the generator in case a not optional argument within an entity 45 | /// constructor is marked as ignored and therefore the generator cannot fill in 46 | /// a value. 47 | class NotOptionalArgumentIgnoredException implements Exception { 48 | final Element field; 49 | final ClassElement type; 50 | 51 | NotOptionalArgumentIgnoredException(this.field, this.type); 52 | 53 | @override 54 | String toString() => 55 | 'NotOptionalArgumentIgnoredException: The field ${field.name} in ' 56 | '${type.name} is annotated with @Property(ignore:true) but is a not ' 57 | 'optional argument of the constructor.'; 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/generator/entity_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:code_builder/code_builder.dart'; 3 | import 'package:json_serializable/type_helper.dart'; 4 | import 'package:json_serializable/src/type_helpers/value_helper.dart'; 5 | import 'package:json_serializable/src/type_helpers/enum_helper.dart'; 6 | import 'package:json_serializable/src/type_helpers/iterable_helper.dart'; 7 | import 'package:json_serializable/src/type_helpers/map_helper.dart'; 8 | 9 | import '../annotations.dart'; 10 | import '../exceptions.dart'; 11 | import 'entity_type_helper.dart'; 12 | import 'field_context.dart'; 13 | import 'identifier.dart'; 14 | import 'transformer_generator.dart'; 15 | import 'utils.dart'; 16 | 17 | class EntityGenerator { 18 | final ClassElement _element; 19 | final Set _fields; 20 | final Iterable _helpers; 21 | final _fieldContexts = []; 22 | 23 | EntityGenerator(this._element, TransformerGenerator transformers, 24 | EntityTypeHelper entities) 25 | : _fields = sortedFieldSet(_element), 26 | _helpers = [ 27 | transformers, 28 | entities, 29 | ].followedBy([ 30 | ValueHelper(), 31 | UriHelper(), 32 | EnumHelper(), 33 | IterableHelper(), 34 | MapHelper(), 35 | DateTimeHelper(), 36 | ]); 37 | 38 | String build(DartEmitter emitter) => (StringBuffer() 39 | ..write(_buildEncoder().accept(emitter)) 40 | ..write(_buildDecoder().accept(emitter)) 41 | ..writeAll(_fieldContexts.expand((f) => f.members).toSet())) 42 | .toString(); 43 | 44 | Method _buildEncoder() { 45 | final obj = refer('obj'); 46 | final block = BlockBuilder() 47 | ..statements.add(Code('if (object == null) { return null; }')) 48 | ..addExpression(refer('Map', 'dart:core') 49 | .newInstance([]).assignFinal('obj')); 50 | 51 | for (var field in _fields) { 52 | final fieldProperty = propertyAnnotation( 53 | field.getter.metadata.isNotEmpty ? field.getter : field); 54 | if (field.isPrivate || fieldProperty.ignore) { 55 | continue; 56 | } 57 | 58 | final fieldContext = 59 | FieldContext(true, field.metadata, _helpers, _element, field); 60 | _fieldContexts.add(fieldContext); 61 | 62 | block.addExpression(obj 63 | .index(literalString(fieldProperty.name ?? field.displayName)) 64 | .assign(CodeExpression(Code(fieldContext.serialize( 65 | field.type, 'object.${field.displayName}'))))); 66 | } 67 | 68 | block.addExpression(obj.returned); 69 | 70 | return Method((b) => b 71 | ..name = encodeMethod(_element) 72 | ..returns = refer('Map') 73 | ..requiredParameters.addAll([ 74 | Parameter((pb) => pb 75 | ..name = 'object' 76 | ..type = refer(_element.displayName)), 77 | Parameter((pb) => pb 78 | ..name = 'inst' 79 | ..type = refer('Dartson', dartsonPackage)) 80 | ]) 81 | ..body = block.build()); 82 | } 83 | 84 | Method _buildDecoder() { 85 | final constructorParameters = []; 86 | final constructorNamedParameters = {}; 87 | final passedConstructorParameters = []; 88 | 89 | // Get constructor arguments. 90 | final constructorArguments = _element.constructors?.first?.parameters ?? []; 91 | for (var field in constructorArguments) { 92 | Property fieldProperty; 93 | if (field.isInitializingFormal) { 94 | // Fetch details from matching field. 95 | fieldProperty = propertyAnnotation( 96 | _fields.firstWhere((fe) => fe.name == field.name)); 97 | 98 | passedConstructorParameters.add(field.displayName); 99 | } else { 100 | // Check for annotations. 101 | fieldProperty = propertyAnnotation(field); 102 | } 103 | 104 | if (fieldProperty.ignore && field.isNotOptional) { 105 | throw NotOptionalArgumentIgnoredException(field, _element); 106 | } 107 | 108 | if (fieldProperty.ignore) { 109 | continue; 110 | } 111 | 112 | // TODO: Check how to handle pure property fields. 113 | final fieldContext = 114 | FieldContext(true, field.metadata, _helpers, _element, null); 115 | _fieldContexts.add(fieldContext); 116 | 117 | final expression = CodeExpression(Code(fieldContext.deserialize( 118 | field.type, 'data[\'${fieldProperty.name ?? field.displayName}\']'))); 119 | if (field.isPositional) { 120 | constructorParameters.add(expression); 121 | } else { 122 | constructorNamedParameters[field.name] = expression; 123 | } 124 | } 125 | 126 | final block = BlockBuilder() 127 | ..statements.add(Code('if (data == null) { return null; }')) 128 | ..addExpression(refer(_element.displayName) 129 | .newInstance(constructorParameters, constructorNamedParameters) 130 | .assignFinal('obj')); 131 | 132 | for (var field in _fields) { 133 | if (field.isFinal || 134 | passedConstructorParameters.contains(field.name) || 135 | field.isPrivate) { 136 | continue; 137 | } 138 | 139 | final fieldProperty = propertyAnnotation(field); 140 | if (fieldProperty.ignore) { 141 | continue; 142 | } 143 | final fieldContext = 144 | FieldContext(true, field.metadata, _helpers, _element, field); 145 | _fieldContexts.add(fieldContext); 146 | 147 | block.addExpression(refer('obj').property(field.displayName).assign( 148 | CodeExpression(Code(fieldContext.deserialize(field.type, 149 | 'data[\'${fieldProperty.name ?? field.displayName}\']'))))); 150 | } 151 | 152 | block.addExpression(refer('obj').returned); 153 | 154 | return Method((b) => b 155 | ..name = decodeMethod(_element) 156 | ..returns = refer(_element.displayName) 157 | ..requiredParameters.addAll([ 158 | Parameter((pb) => pb 159 | ..name = 'data' 160 | ..type = refer('Map')), 161 | Parameter((pb) => pb 162 | ..name = 'inst' 163 | ..type = refer('Dartson', dartsonPackage)) 164 | ]) 165 | ..body = block.build()); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /lib/src/generator/entity_type_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:analyzer/dart/element/type.dart'; 3 | import 'package:json_serializable/type_helper.dart'; 4 | 5 | import 'identifier.dart'; 6 | 7 | /// Helper which looks up if a [DartType] exists within a list of [_entities] 8 | /// and returns the code for the proper calls to encode/decode method. The list 9 | /// of entities is provided by the generator based on entities, transformers 10 | /// and replacements. 11 | class EntityTypeHelper implements TypeHelper { 12 | final Map _entities; 13 | EntityTypeHelper(this._entities); 14 | 15 | @override 16 | String deserialize( 17 | DartType targetType, String expression, TypeHelperContext context) { 18 | final target = targetType.element as ClassElement; 19 | if (!_entities.containsKey(target)) { 20 | return null; 21 | } 22 | 23 | return '${decodeMethod(_entities[target])}($expression, inst)'; 24 | } 25 | 26 | @override 27 | String serialize( 28 | DartType targetType, String expression, TypeHelperContext context) { 29 | final target = targetType.element as ClassElement; 30 | if (!_entities.containsKey(target)) { 31 | return null; 32 | } 33 | 34 | return '${encodeMethod(_entities[target])}($expression, inst)'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/generator/field_context.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:analyzer/dart/element/type.dart'; 3 | import 'package:json_annotation/src/json_serializable.dart'; 4 | import 'package:json_serializable/type_helper.dart'; 5 | import 'package:json_serializable/src/type_helper.dart'; 6 | 7 | /// Provides the serialization methods for a field using [TypeHelper]s which are 8 | /// standard [TypeHelper], entities or transformers. 9 | class FieldContext implements TypeHelperContextWithConfig { 10 | final bool nullable; 11 | final List metadata; 12 | final Iterable helpers; 13 | final List members = []; 14 | final ClassElement classElement; 15 | final FieldElement fieldElement; 16 | 17 | FieldContext(this.nullable, this.metadata, this.helpers, this.classElement, 18 | this.fieldElement); 19 | 20 | @override 21 | void addMember(String memberContent) { 22 | members.add(memberContent); 23 | } 24 | 25 | @override 26 | String deserialize(DartType fieldType, String expression) => helpers 27 | .map((h) => h.deserialize(fieldType, expression, this)) 28 | .firstWhere((r) => r != null, 29 | orElse: () => throw UnsupportedTypeError( 30 | fieldType, expression, _notSupportedTypeMessage)); 31 | 32 | @override 33 | String serialize(DartType fieldType, String expression) => helpers 34 | .map((h) => h.serialize(fieldType, expression, this)) 35 | .firstWhere((r) => r != null, 36 | orElse: () => throw UnsupportedTypeError( 37 | fieldType, expression, _notSupportedTypeMessage)); 38 | 39 | @override 40 | JsonSerializable get config => JsonSerializable( 41 | anyMap: false, 42 | checked: false, 43 | createFactory: false, 44 | createToJson: false, 45 | disallowUnrecognizedKeys: false, 46 | explicitToJson: false, 47 | includeIfNull: true, 48 | generateToJsonFunction: true, 49 | nullable: true, 50 | useWrappers: false, 51 | ); 52 | } 53 | 54 | final _notSupportedTypeMessage = 'UnsupportedTypeError: None of the provided ' 55 | '`TypeHelper` or defined `entities` and `transformers` support the defined ' 56 | 'type. Please make sure to add the type either to `entities`, define a ' 57 | 'TypeTransformer in `transformers` or define a `replacement`.'; 58 | -------------------------------------------------------------------------------- /lib/src/generator/generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:analyzer/dart/element/element.dart'; 4 | import 'package:code_builder/code_builder.dart'; 5 | import 'package:build/src/builder/build_step.dart'; 6 | import 'package:source_gen/source_gen.dart'; 7 | import 'package:dart_style/dart_style.dart'; 8 | 9 | import '../annotations.dart'; 10 | import 'entity_type_helper.dart'; 11 | import 'entity_generator.dart'; 12 | import 'generator_settings.dart'; 13 | import 'transformer_generator.dart'; 14 | import 'serializer_generator.dart'; 15 | 16 | class SerializerGenerator extends GeneratorForAnnotation { 17 | @override 18 | FutureOr generateForAnnotatedElement( 19 | Element element, ConstantReader annotation, BuildStep buildStep) { 20 | final settings = GeneratorSettings.fromConstant(annotation); 21 | final transformers = TransformerGenerator(settings.transformers); 22 | final entities = EntityTypeHelper(settings.entities); 23 | 24 | final emitter = DartEmitter(); 25 | final buffer = StringBuffer(); 26 | 27 | buffer.write(transformers.build(emitter)); 28 | buffer.writeAll(settings.entities.values 29 | .map((e) => EntityGenerator(e, transformers, entities).build(emitter))); 30 | 31 | buffer.write(DartsonGenerator(settings.entities.values.toSet(), element) 32 | .build(emitter)); 33 | 34 | return DartFormatter().format(buffer.toString()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/generator/generator_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:source_gen/source_gen.dart'; 3 | 4 | /// Helper class to fetch all [Serializer] definitions within the generator. 5 | class GeneratorSettings { 6 | final Map entities; 7 | final Map replacements; 8 | final Iterable transformers; 9 | 10 | GeneratorSettings._(this.entities, this.transformers, this.replacements); 11 | 12 | factory GeneratorSettings.fromConstant(ConstantReader reader) { 13 | final replacements = _replacerFromReader(reader, 'replacements'); 14 | 15 | final entityMap = {} 16 | ..addAll(Map.fromIterable( 17 | _fromReader(reader, 'entities'), 18 | key: (v) => v, 19 | value: (v) => v, 20 | )) 21 | ..addAll(replacements); 22 | 23 | return GeneratorSettings._( 24 | entityMap, 25 | _fromReader(reader, 'transformers'), 26 | replacements, 27 | ); 28 | } 29 | } 30 | 31 | Iterable _fromReader(ConstantReader reader, String key) { 32 | final field = reader.objectValue.getField(key); 33 | if (field.isNull) { 34 | return []; 35 | } 36 | 37 | return field 38 | .toListValue() 39 | .map((obj) => obj.toTypeValue().element as ClassElement); 40 | } 41 | 42 | Map _replacerFromReader( 43 | ConstantReader reader, String key) { 44 | final field = reader.objectValue.getField(key); 45 | if (field.isNull) { 46 | return {}; 47 | } 48 | 49 | return field.toMapValue().map((key, val) => 50 | MapEntry( 51 | key.toTypeValue().element as ClassElement, 52 | val.toTypeValue().element as ClassElement)); 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/generator/identifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | 3 | const _encodeMethodIdentifier = r'$encoder'; 4 | const _decodeMethodIdentifier = r'$decoder'; 5 | const serializerIdentifier = r'$dartson'; 6 | const implementationIdentifier = r'_Dartson$impl'; 7 | const dartsonPackage = 'package:dartson/dartson.dart'; 8 | 9 | /// Returns the encode method identifier for a specific entity [ClassElement]. 10 | String encodeMethod(ClassElement element) => 11 | '_${element.displayName}$_encodeMethodIdentifier'; 12 | 13 | /// Returns the decode method identifier for a specific entity [ClassElement]. 14 | String decodeMethod(ClassElement element) => 15 | '_${element.displayName}$_decodeMethodIdentifier'; 16 | -------------------------------------------------------------------------------- /lib/src/generator/serializer_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:code_builder/code_builder.dart'; 3 | 4 | import 'identifier.dart'; 5 | 6 | /// Generator which initiates the [Dartson] instance passing the map of 7 | /// entities and assigning it to the serializer value adding `$dartson` suffix. 8 | /// 9 | /// **Output:** 10 | /// 11 | /// final _serializer$dartson = new Dartson>({/*...*/}); 12 | /// 13 | class DartsonGenerator { 14 | final Set _objects; 15 | final Element _element; 16 | 17 | DartsonGenerator(this._objects, this._element); 18 | 19 | String build(DartEmitter emitter) => 20 | _buildDartson().accept(emitter).toString(); 21 | 22 | Spec _buildDartson() { 23 | final mapValues = {}; 24 | 25 | _objects.forEach((t) => mapValues[refer(t.displayName)] = 26 | refer('DartsonEntity', dartsonPackage).constInstance([ 27 | refer(encodeMethod(t)), 28 | refer(decodeMethod(t)), 29 | ], {}, [ 30 | refer(t.displayName) 31 | ])); 32 | 33 | final lookupMap = literalMap(mapValues, refer('Type', 'dart:core'), 34 | refer('DartsonEntity', dartsonPackage)); 35 | 36 | return refer('Dartson>', dartsonPackage) 37 | .newInstance([lookupMap]) 38 | .assignFinal('_${_element.displayName}$serializerIdentifier') 39 | .statement; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/generator/transformer_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:analyzer/dart/element/type.dart'; 3 | import 'package:code_builder/code_builder.dart'; 4 | import 'package:json_serializable/type_helper.dart'; 5 | 6 | import '../exceptions.dart'; 7 | 8 | /// Helper which looks up if a [DartType] has a [TypeTransformer] implementation 9 | /// defined and calls the encode/decode method on the [TypeTransformer]. 10 | class TransformerGenerator implements TypeHelper { 11 | final Iterable _transformers; 12 | final Map _transformerRef = {}; 13 | 14 | TransformerGenerator(this._transformers) { 15 | _transformers?.forEach((classElement) { 16 | final serialize = classElement.methods.firstWhere( 17 | (m) => m.name == 'encode', 18 | orElse: () => throw MissingEncodeMethodException(classElement.name)); 19 | final deserialize = classElement.methods.firstWhere( 20 | (m) => m.name == 'decode', 21 | orElse: () => throw MissingDecodeMethodException(classElement.name)); 22 | 23 | final targetType = deserialize.returnType; 24 | final inputType = serialize.returnType; 25 | 26 | _transformerRef[targetType] = _Transformer(targetType, inputType, 27 | '_transformer${_transformerRef.length}', classElement.displayName); 28 | }); 29 | } 30 | 31 | String build(DartEmitter emitter) { 32 | return _transformerRef.values.fold( 33 | '', 34 | (String v, _Transformer t) => 35 | v += _buildTransformer(t).accept(emitter).toString()); 36 | } 37 | 38 | Code _buildTransformer(_Transformer trans) => 39 | refer(trans.element).constInstance([]).assignConst(trans.name).statement; 40 | 41 | @override 42 | String deserialize( 43 | DartType targetType, String expression, TypeHelperContext context) { 44 | final transformer = _transformerRef[targetType]; 45 | if (transformer == null) { 46 | return null; 47 | } 48 | 49 | return '${transformer.name}.decode($expression as ' 50 | '${transformer.inputType.displayName})'; 51 | } 52 | 53 | @override 54 | String serialize( 55 | DartType targetType, String expression, TypeHelperContext context) { 56 | final transformer = _transformerRef[targetType]; 57 | if (transformer == null) { 58 | return null; 59 | } 60 | 61 | return '${transformer.name}.encode($expression)'; 62 | } 63 | } 64 | 65 | class _Transformer { 66 | final DartType targetType, inputType; 67 | final String name, element; 68 | 69 | _Transformer(this.targetType, this.inputType, this.name, this.element); 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/generator/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:analyzer/src/dart/resolver/inheritance_manager.dart'; 3 | import 'package:source_gen/source_gen.dart'; 4 | import 'package:dartson/dartson.dart'; 5 | 6 | Property propertyAnnotation(Element element) { 7 | final annotations = TypeChecker.fromRuntime(Property).annotationsOf(element); 8 | if (annotations.isEmpty) { 9 | return Property(); 10 | } 11 | 12 | final ignoreField = annotations.last.getField('ignore'); 13 | final renameField = annotations.last.getField('name'); 14 | return Property( 15 | ignore: !(ignoreField?.isNull ?? true) ? ignoreField.toBoolValue() : false, 16 | name: !(renameField?.isNull ?? true) ? renameField.toStringValue() : null, 17 | ); 18 | } 19 | 20 | // TODO: This is based on a json_serializable implementation maybe share? 21 | Set sortedFieldSet(ClassElement element) { 22 | final fieldsList = element.fields.where((e) => !e.isStatic).toList(); 23 | final manager = InheritanceManager(element.library); 24 | 25 | for (var v in manager.getMembersInheritedFromClasses(element).values) { 26 | assert(v is! FieldElement); 27 | 28 | if (_dartCoreObjectChecker.isExactly(v.enclosingElement)) { 29 | continue; 30 | } 31 | 32 | if (v is PropertyAccessorElement && v.variable is FieldElement) { 33 | fieldsList.add(v.variable as FieldElement); 34 | } 35 | } 36 | 37 | return fieldsList.toSet(); 38 | } 39 | 40 | final _dartCoreObjectChecker = const TypeChecker.fromRuntime(Object); 41 | -------------------------------------------------------------------------------- /lib/src/serializer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'annotations.dart'; 4 | import 'exceptions.dart'; 5 | 6 | /// Provides the serializer to [encode] and [decode] entities. The serializer 7 | /// encodes and decodes to respectively from Map by default. 8 | /// In order to target different target provide a [_codec] within the 9 | /// [Serializer] declaration. 10 | class Dartson { 11 | final Map _entities; 12 | 13 | /// If provided calls [encode] respectively [decode] on the codec. 14 | Codec _codec; 15 | 16 | Dartson(this._entities, {Codec codec}) : this._codec = codec; 17 | 18 | /// Encodes [data] using the encoding method generated by the builder and the 19 | /// [_codec] if present. 20 | /// Throws [UnknownEntityException] if the entity wasn't added to the 21 | /// [Serializer] declaration or provided by extending another serializer. 22 | R encode(T data) { 23 | final entity = _getEntity(T); 24 | final preData = entity.encoder(data, this); 25 | 26 | if (_codec == null) { 27 | return preData as R; 28 | } 29 | 30 | return _codec.encode(preData); 31 | } 32 | 33 | /// Encodes [data] by iterating over the items and using the encoding method 34 | /// generated by the builder and the [_codec] if present. Returns a list of 35 | /// serialized maps if no [_codec] is set. 36 | /// Throws [UnknownEntityException] if the entity wasn't added to the 37 | /// [Serializer] declaration or provided by extending another serializer. 38 | Object encodeList(List data) { 39 | final entity = _getEntity(T); 40 | final preData = data.map((i) => entity.encoder(i, this)).toList(); 41 | 42 | if (_codec == null) { 43 | return preData; 44 | } 45 | 46 | return _codec.encode(preData); 47 | } 48 | 49 | /// Decodes [data] using the [_codec] if provided and the decoding method 50 | /// generated by the builder. 51 | /// Throws [UnknownEntityException] if the entity wasn't added to the 52 | /// [Serializer] declaration or provided by extending another serializer. 53 | T decode(R data) { 54 | final entity = _getEntity(T); 55 | 56 | Map prepData; 57 | if (_codec != null) { 58 | prepData = _codec.decode(data); 59 | } else { 60 | prepData = data as Map; 61 | } 62 | 63 | return entity.decoder(prepData, this); 64 | } 65 | 66 | /// Decodes [list] using the [_codec] if provided and the decoding method 67 | /// generated by the builder. 68 | /// 69 | /// Throws [UnknownEntityException] if the entity wasn't added to the 70 | /// [Serializer] declaration or provided by extending another serializer. 71 | /// 72 | /// Throws [TypeError] if passed data is not a list after deserialization or 73 | /// if it's not a list in the first place and no converter is defined. 74 | List decodeList(Object data) { 75 | List prepData; 76 | final entity = _getEntity(T); 77 | 78 | if (_codec != null) { 79 | prepData = _codec.decode(data); 80 | if (prepData is! List) { 81 | throw TypeError(); 82 | } 83 | } else { 84 | if (data is! List) { 85 | throw TypeError(); 86 | } 87 | 88 | prepData = data as List; 89 | } 90 | 91 | if (prepData.isEmpty) { 92 | return []; 93 | } 94 | 95 | return prepData 96 | .map((d) => entity.decoder(d as Map, this)) 97 | .toList(); 98 | } 99 | 100 | /// Takes the entities of the [instance] and adds the entities of this instance 101 | /// and use the [_codec] of this instance. 102 | Dartson extend(Dartson instance) => 103 | Dartson({}..addAll(instance._entities)..addAll(_entities), 104 | codec: _codec); 105 | 106 | /// Returns a new [Dartson] instance using the provided [codec]. 107 | Dartson useCodec(Codec codec) => 108 | Dartson({}..addAll(_entities), codec: codec); 109 | 110 | DartsonEntity _getEntity(Type type) { 111 | if (!_entities.containsKey(type)) { 112 | throw UnknownEntityException(type); 113 | } 114 | 115 | return _entities[type] as DartsonEntity; 116 | } 117 | } 118 | 119 | /// Simple container for [encoder] and [decoder] methods of an entity. This 120 | /// shouldn't be used and is generated by the builder. 121 | class DartsonEntity { 122 | /// The generated encoding function. 123 | final Map Function(T obj, Dartson inst) encoder; 124 | 125 | /// The generated decoding function. 126 | final T Function(Map data, Dartson inst) decoder; 127 | 128 | const DartsonEntity(this.encoder, this.decoder); 129 | } 130 | -------------------------------------------------------------------------------- /lib/src/type_transformer.dart: -------------------------------------------------------------------------------- 1 | /// The interface for creating new transformers. For a basic example on how to 2 | /// use this interface take a look at [DateTimeParser]. 3 | abstract class TypeTransformer { 4 | /// Receives the [value] of type [T] and returns a serializable result which 5 | /// will be passed into the JSON representation. 6 | S encode(T value); 7 | 8 | /// Takes a serialized [value] from the JSON object and transforms it into the 9 | /// correct type. 10 | T decode(S value); 11 | } 12 | -------------------------------------------------------------------------------- /lib/transformers/date_time.dart: -------------------------------------------------------------------------------- 1 | library dartson.transformers.DateTime; 2 | 3 | import 'package:dartson/dartson.dart'; 4 | 5 | /// A simple DateTime transformer which uses the toString() method. 6 | @Deprecated('A default implementation is now available using ISO-8601.') 7 | class DateTimeParser implements TypeTransformer { 8 | const DateTimeParser(); 9 | DateTime decode(String value) => 10 | value != null ? DateTime.parse(value) : value; 11 | String encode(DateTime value) => 12 | value != null ? value.toUtc().toIso8601String() : value; 13 | } 14 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dartson 2 | version: 1.0.0-alpha+4 3 | 4 | author: Eric Schneller 5 | description: Dartson is a Dart library that can be used to serialize Dart objects into a JSON string and vice versa. It uses the new builder infrastructure. 6 | homepage: https://github.com/eredo/dartson 7 | 8 | environment: 9 | sdk: '>=2.0.0-dev.65 <3.0.0' 10 | 11 | dependencies: 12 | analyzer: '>=0.32.2 <0.34.0' 13 | build: '>=0.12.6 <2.0.0' 14 | build_config: '>=0.2.6 <0.4.0' 15 | meta: ^1.1.0 16 | source_gen: ^0.9.0 17 | code_builder: ^3.1.2 18 | dart_style: ^1.1.3 19 | json_serializable: ^2.0.0 20 | json_annotation: ^2.0.0 21 | 22 | dev_dependencies: 23 | path: ^1.3.2 24 | test: ^1.2.0 25 | build_test: ^0.10.0 26 | dart_coveralls: ^0.6.0 -------------------------------------------------------------------------------- /test/dartson_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | import 'dart:convert'; 3 | 4 | import 'package:test/test.dart'; 5 | import 'package:dartson/dartson.dart'; 6 | 7 | import 'src/serializer.dart'; 8 | import 'src/my_class.dart'; 9 | import 'src/sub_class.dart'; 10 | import 'src/my_impl.dart'; 11 | 12 | void main() { 13 | final jsonSerializer = serializer.useCodec(json); 14 | 15 | group('$Dartson', () { 16 | test('should throw when encoding an unknown entity', () { 17 | expect(() => serializer.encode(UnknownEntity()), 18 | throwsA(TypeMatcher())); 19 | expect(() => serializer.encodeList([UnknownEntity()]), 20 | throwsA(TypeMatcher())); 21 | }); 22 | 23 | test('should throw when decoding an unknown entity', () { 24 | expect(() => serializer.decode({}), 25 | throwsA(TypeMatcher())); 26 | expect(() => jsonSerializer.decode('{}'), 27 | throwsA(TypeMatcher())); 28 | expect(() => serializer.decodeList([{}]), 29 | throwsA(TypeMatcher())); 30 | expect(() => jsonSerializer.decodeList('[{}]'), 31 | throwsA(TypeMatcher())); 32 | }); 33 | 34 | test('should encode the object', () { 35 | final result = serializer.encode(MyClass('test') 36 | ..name = 'hello' 37 | ..numDouble = 1.0 38 | ..subClass = (SubClass()..name = 'test')); 39 | expect(result['name'], 'hello'); 40 | expect(result['numDouble'], 1.0); 41 | expect(result['subClass']['name'], 'test'); 42 | }); 43 | 44 | test('should decode the object', () { 45 | final result = serializer.decode({ 46 | 'name': 'hello', 47 | 'numDouble': 1, 48 | 'subClass': {'name': 'hello2'} 49 | }); 50 | expect(result.name, 'hello'); 51 | expect(result.numDouble, 1.0); 52 | expect(result.subClass.name, 'hello2'); 53 | }); 54 | 55 | test('should encode lists', () { 56 | final result = serializer.encode(MyClass('test') 57 | ..subClasses = [ 58 | SubClass() 59 | ..name = '1' 60 | ..aliases = ['t1', 't12'], 61 | SubClass() 62 | ..name = '2' 63 | ..aliases = ['t2', 't22'] 64 | ]); 65 | 66 | expect(result['subClasses'][0]['name'], '1'); 67 | expect(result['subClasses'][1]['name'], '2'); 68 | expect(result['subClasses'][0]['aliases'], ['t1', 't12']); 69 | expect(result['subClasses'][1]['aliases'], ['t2', 't22']); 70 | }); 71 | 72 | test('should decode lists', () { 73 | final result = serializer.decode({ 74 | 'subClasses': [ 75 | { 76 | 'name': '1', 77 | 'aliases': ['t1', 't12'] 78 | }, 79 | { 80 | 'name': '2', 81 | 'aliases': ['t2', 't22'] 82 | }, 83 | ], 84 | }); 85 | 86 | expect(result.subClasses, hasLength(2)); 87 | expect(result.subClasses[0].name, '1'); 88 | expect(result.subClasses[1].name, '2'); 89 | expect(result.subClasses[0].aliases, ['t1', 't12']); 90 | expect(result.subClasses[1].aliases, ['t2', 't22']); 91 | }); 92 | 93 | test('should encode maps', () { 94 | final result = serializer.encode(MyClass('test') 95 | ..complexMap = { 96 | 't1': SubClass()..name = 't1', 97 | 't2': SubClass()..name = 't2', 98 | }); 99 | 100 | expect(result['complexMap'], allOf(isMap, hasLength(2))); 101 | expect(result['complexMap']['t1']['name'], 't1'); 102 | expect(result['complexMap']['t2']['name'], 't2'); 103 | }); 104 | 105 | test('should decode maps', () { 106 | final result = serializer.decode({ 107 | 'complexMap': { 108 | 't1': {'name': 't1'}, 109 | 't2': {'name': 't2'}, 110 | } 111 | }); 112 | 113 | expect(result.complexMap, allOf(isMap, hasLength(2))); 114 | expect(result.complexMap['t1'].name, 't1'); 115 | expect(result.complexMap['t2'].name, 't2'); 116 | }); 117 | 118 | test('should encode replacements', () { 119 | final result = serializer 120 | .encode(MyClass('test')..replacement = (MyImpl()..name = 'test')); 121 | expect(result['replacement']['name'], 'test'); 122 | }); 123 | 124 | test('should decode replacements', () { 125 | final result = serializer.decode({ 126 | 'replacement': {'name': 'test'} 127 | }); 128 | 129 | expect(result.replacement, TypeMatcher()); 130 | expect(result.replacement.name, 'test'); 131 | }); 132 | 133 | test('should decode lists', () { 134 | final result = serializer.decodeList([ 135 | {'name': 'test1'}, 136 | {'name': 'test2'}, 137 | ]); 138 | 139 | expect(result, allOf(isList, hasLength(2))); 140 | expect(result[0].name, 'test1'); 141 | expect(result[1].name, 'test2'); 142 | }); 143 | 144 | test('should decode serialized lists', () { 145 | final result = jsonSerializer.decodeList('''[ 146 | {"name": "test1"}, 147 | {"name": "test2"} 148 | ]'''); 149 | 150 | expect(result, allOf(isList, hasLength(2))); 151 | expect(result[0].name, 'test1'); 152 | expect(result[1].name, 'test2'); 153 | }); 154 | 155 | test('should throw error if decoding list is not of type list', () { 156 | expect(() => serializer.decodeList(''), 157 | throwsA(TypeMatcher())); 158 | expect(() => jsonSerializer.decodeList('{}'), 159 | throwsA(TypeMatcher())); 160 | }); 161 | 162 | test('should encode lists', () { 163 | final result = serializer.encodeList([ 164 | MyClass('test')..name = 'test1', 165 | MyClass('test')..name = 'test2', 166 | ]) as List>; 167 | 168 | expect(result, allOf(isList, hasLength(2))); 169 | expect(result[0]['name'], 'test1'); 170 | expect(result[1]['name'], 'test2'); 171 | }); 172 | }); 173 | } 174 | 175 | class UnknownEntity {} 176 | -------------------------------------------------------------------------------- /test/generator_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | import 'dart:io'; 3 | 4 | import 'package:test/test.dart'; 5 | import 'package:build_test/build_test.dart'; 6 | import 'package:dartson/src/generator/generator.dart'; 7 | import 'package:source_gen/source_gen.dart'; 8 | import 'package:path/path.dart' as p; 9 | 10 | import 'test_lib_utils.dart'; 11 | import 'test_file_utils.dart'; 12 | 13 | LibraryReader elLibrary; 14 | 15 | void main() async { 16 | group('SerializerGenerator', () { 17 | final generator = SerializerGenerator(); 18 | 19 | test('should detect the property', () async { 20 | final libPath = testFilePath('lib'); 21 | final testPath = testFilePath('test', 'src'); 22 | final assets = await resolveTestProject(testPath, libPath, [ 23 | p.join(libPath, 'dartson.dart'), 24 | p.join(libPath, 'transformers', 'date_time.dart'), 25 | p.join(libPath, 'src', 'annotations.dart') 26 | ]); 27 | 28 | final expected = { 29 | 'test_lib|lib/serializer.g.dart': 30 | File(p.join(testPath, 'serializer.g.dart')).readAsStringSync(), 31 | }; 32 | 33 | await testBuilder(PartBuilder([generator], '.g.dart'), assets, 34 | rootPackage: 'test_lib', outputs: expected); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /test/src/my_class.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartson/dartson.dart'; 2 | 3 | import 'sub_class.dart'; 4 | import 'my_impl.dart'; 5 | 6 | enum MyEnum { firstValue, secondValue } 7 | enum SecondEnum { has, nothing } 8 | 9 | class MyClass extends BaseClass { 10 | final String finalProp; 11 | String name; 12 | int number; 13 | @Property(name: 'boolean') 14 | bool hasBoolean; 15 | @Property(ignore: true) 16 | bool ignored; 17 | double numDouble; 18 | Uri uri; 19 | DateTime dateTime; 20 | MyEnum myEnum; 21 | SecondEnum secondEnum; 22 | SubClass subClass; 23 | List subClasses; 24 | Map complexMap; 25 | MyAbstr replacement; 26 | String _private; 27 | 28 | MyClass(this.finalProp, 29 | {this.ignored, @Property(name: 'private') String renamedPrivate}) 30 | : _private = renamedPrivate; 31 | 32 | @Property(name: 'private') 33 | String get privateGetter => _private; 34 | } 35 | 36 | class BaseClass { 37 | bool inherited; 38 | @Property(name: 'inheritName') 39 | String inheritedRenamed; 40 | @Property(ignore: true) 41 | String inheritedIgnored; 42 | } 43 | -------------------------------------------------------------------------------- /test/src/my_impl.dart: -------------------------------------------------------------------------------- 1 | abstract class MyAbstr { 2 | String get name; 3 | } 4 | 5 | class MyImpl implements MyAbstr { 6 | String name; 7 | } 8 | -------------------------------------------------------------------------------- /test/src/serializer.dart: -------------------------------------------------------------------------------- 1 | library serializer_test; 2 | 3 | import 'package:dartson/dartson.dart'; 4 | import 'package:dartson/transformers/date_time.dart'; 5 | 6 | import 'my_class.dart'; 7 | import 'sub_class.dart'; 8 | import 'my_impl.dart'; 9 | 10 | // ignore: uri_has_not_been_generated 11 | part 'serializer.g.dart'; 12 | 13 | @Serializer( 14 | entities: [ 15 | MyClass, 16 | SubClass, 17 | ], 18 | transformers: [DateTimeParser], 19 | replacements: { 20 | MyAbstr: MyImpl, 21 | }, 22 | ) 23 | final Dartson> serializer = _serializer$dartson; 24 | -------------------------------------------------------------------------------- /test/src/serializer.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of serializer_test; 4 | 5 | // ************************************************************************** 6 | // SerializerGenerator 7 | // ************************************************************************** 8 | 9 | const _transformer0 = const DateTimeParser(); 10 | Map _MyClass$encoder(MyClass object, Dartson inst) { 11 | if (object == null) { 12 | return null; 13 | } 14 | final obj = new Map(); 15 | obj['finalProp'] = object.finalProp; 16 | obj['name'] = object.name; 17 | obj['number'] = object.number; 18 | obj['boolean'] = object.hasBoolean; 19 | obj['numDouble'] = object.numDouble; 20 | obj['uri'] = object.uri?.toString(); 21 | obj['dateTime'] = _transformer0.encode(object.dateTime); 22 | obj['myEnum'] = _$MyEnumEnumMap[object.myEnum]; 23 | obj['secondEnum'] = _$SecondEnumEnumMap[object.secondEnum]; 24 | obj['subClass'] = _SubClass$encoder(object.subClass, inst); 25 | obj['subClasses'] = 26 | object.subClasses?.map((e) => _SubClass$encoder(e, inst))?.toList(); 27 | obj['complexMap'] = 28 | object.complexMap?.map((k, e) => MapEntry(k, _SubClass$encoder(e, inst))); 29 | obj['replacement'] = _MyImpl$encoder(object.replacement, inst); 30 | obj['private'] = object.privateGetter; 31 | obj['inherited'] = object.inherited; 32 | obj['inheritName'] = object.inheritedRenamed; 33 | return obj; 34 | } 35 | 36 | MyClass _MyClass$decoder(Map data, Dartson inst) { 37 | if (data == null) { 38 | return null; 39 | } 40 | final obj = new MyClass(data['finalProp'] as String, 41 | renamedPrivate: data['private'] as String); 42 | obj.name = data['name'] as String; 43 | obj.number = data['number'] as int; 44 | obj.hasBoolean = data['boolean'] as bool; 45 | obj.numDouble = (data['numDouble'] as num)?.toDouble(); 46 | obj.uri = data['uri'] == null ? null : Uri.parse(data['uri'] as String); 47 | obj.dateTime = _transformer0.decode(data['dateTime'] as String); 48 | obj.myEnum = _$enumDecodeNullable(_$MyEnumEnumMap, data['myEnum']); 49 | obj.secondEnum = 50 | _$enumDecodeNullable(_$SecondEnumEnumMap, data['secondEnum']); 51 | obj.subClass = _SubClass$decoder(data['subClass'], inst); 52 | obj.subClasses = (data['subClasses'] as List) 53 | ?.map((e) => _SubClass$decoder(e, inst)) 54 | ?.toList(); 55 | obj.complexMap = (data['complexMap'] as Map) 56 | ?.map((k, e) => MapEntry(k, _SubClass$decoder(e, inst))); 57 | obj.replacement = _MyImpl$decoder(data['replacement'], inst); 58 | obj.inherited = data['inherited'] as bool; 59 | obj.inheritedRenamed = data['inheritName'] as String; 60 | return obj; 61 | } 62 | 63 | const _$MyEnumEnumMap = { 64 | MyEnum.firstValue: 'firstValue', 65 | MyEnum.secondValue: 'secondValue' 66 | }; 67 | const _$SecondEnumEnumMap = { 68 | SecondEnum.has: 'has', 69 | SecondEnum.nothing: 'nothing' 70 | }; 71 | T _$enumDecode(Map enumValues, dynamic source) { 72 | if (source == null) { 73 | throw ArgumentError('A value must be provided. Supported values: ' 74 | '${enumValues.values.join(', ')}'); 75 | } 76 | return enumValues.entries 77 | .singleWhere((e) => e.value == source, 78 | orElse: () => throw ArgumentError( 79 | '`$source` is not one of the supported values: ' 80 | '${enumValues.values.join(', ')}')) 81 | .key; 82 | } 83 | 84 | T _$enumDecodeNullable(Map enumValues, dynamic source) { 85 | if (source == null) { 86 | return null; 87 | } 88 | return _$enumDecode(enumValues, source); 89 | } 90 | 91 | Map _SubClass$encoder(SubClass object, Dartson inst) { 92 | if (object == null) { 93 | return null; 94 | } 95 | final obj = new Map(); 96 | obj['name'] = object.name; 97 | obj['aliases'] = object.aliases; 98 | obj['simpleMap'] = object.simpleMap; 99 | return obj; 100 | } 101 | 102 | SubClass _SubClass$decoder(Map data, Dartson inst) { 103 | if (data == null) { 104 | return null; 105 | } 106 | final obj = new SubClass(); 107 | obj.name = data['name'] as String; 108 | obj.aliases = (data['aliases'] as List)?.map((e) => e as String)?.toList(); 109 | obj.simpleMap = (data['simpleMap'] as Map) 110 | ?.map((k, e) => MapEntry(k, (e as num)?.toDouble())); 111 | return obj; 112 | } 113 | 114 | Map _MyImpl$encoder(MyImpl object, Dartson inst) { 115 | if (object == null) { 116 | return null; 117 | } 118 | final obj = new Map(); 119 | obj['name'] = object.name; 120 | return obj; 121 | } 122 | 123 | MyImpl _MyImpl$decoder(Map data, Dartson inst) { 124 | if (data == null) { 125 | return null; 126 | } 127 | final obj = new MyImpl(); 128 | obj.name = data['name'] as String; 129 | return obj; 130 | } 131 | 132 | final _serializer$dartson = 133 | new Dartson>({ 134 | MyClass: const DartsonEntity(_MyClass$encoder, _MyClass$decoder), 135 | SubClass: const DartsonEntity(_SubClass$encoder, _SubClass$decoder), 136 | MyImpl: const DartsonEntity(_MyImpl$encoder, _MyImpl$decoder) 137 | }); 138 | -------------------------------------------------------------------------------- /test/src/sub_class.dart: -------------------------------------------------------------------------------- 1 | class SubClass { 2 | String name; 3 | List aliases; 4 | Map simpleMap; 5 | } 6 | -------------------------------------------------------------------------------- /test/src/usage.dart: -------------------------------------------------------------------------------- 1 | import 'serializer.dart'; 2 | import 'my_class.dart'; 3 | 4 | void usage() { 5 | final myClass = MyClass('test') 6 | ..name = 'test name' 7 | ..number = 29 8 | ..hasBoolean = true; 9 | 10 | final myObj = serializer.encode(myClass); 11 | print(myObj); 12 | 13 | final backToObj = serializer.decode(myObj); 14 | print(backToObj); 15 | } 16 | -------------------------------------------------------------------------------- /test/test_all.dart: -------------------------------------------------------------------------------- 1 | import 'dartson_test.dart' as dartson_test; 2 | import 'generator_test.dart' as generator_test; 3 | 4 | void main() { 5 | dartson_test.main(); 6 | generator_test.main(); 7 | } 8 | -------------------------------------------------------------------------------- /test/test_file_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:mirrors'; 2 | 3 | import 'package:path/path.dart' as p; 4 | 5 | // TODO: This is based on a json_serializable implementation maybe share? 6 | 7 | String testFilePath(String part1, [String part2, String part3]) => 8 | p.join(_packagePath(), part1, part2, part3); 9 | 10 | String _packagePathCache; 11 | 12 | String _packagePath() { 13 | if (_packagePathCache == null) { 14 | // Getting the location of this file – via reflection 15 | var currentFilePath = (reflect(_packagePath) as ClosureMirror) 16 | .function 17 | .location 18 | .sourceUri 19 | .path; 20 | 21 | _packagePathCache = p.normalize(p.join(p.dirname(currentFilePath), '..')); 22 | } 23 | return _packagePathCache; 24 | } 25 | -------------------------------------------------------------------------------- /test/test_lib_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:path/path.dart' as p; 5 | 6 | Future> resolveTestProject(String sourceDirectory, 7 | String libDirectory, List libFilePaths) async { 8 | var files = Directory(sourceDirectory).listSync().whereType().toList(); 9 | var fileMap = Map.fromEntries(files.map((f) => 10 | MapEntry('test_lib|lib/${p.basename(f.path)}', f.readAsStringSync()))); 11 | 12 | final libFiles = libFilePaths.map((f) => File(f)); 13 | fileMap.addAll(Map.fromEntries(libFiles.map((f) => MapEntry( 14 | 'dartson|lib/${p.relative(f.path, from: libDirectory)}', 15 | f.readAsStringSync())))); 16 | 17 | return fileMap; 18 | } 19 | -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo -e '\033[1mTASK: Dart Analyzer [analyzer]\033[22m' 6 | echo -e 'dartanalyzer --fatal-warnings .' 7 | dartanalyzer --fatal-warnings lib/ test/ 8 | 9 | echo -e '\033[1mTASK: Dart Format [dartfmt]\033[22m' 10 | echo -e 'dartfmt -n lib/ test/ example/' 11 | dartfmt -n lib/ test/ example/ 12 | 13 | echo -e '\033[1mTASK: Testing [test]\033[22m' 14 | pub run dart_coveralls report \ 15 | --retry 2 \ 16 | --exclude-test-files \ 17 | --throw-on-error \ 18 | --throw-on-connectivity-error \ 19 | --debug test/test_all.dart --------------------------------------------------------------------------------