├── .gitignore
├── .idea
├── .gitignore
├── fson.iml
├── libraries
│ ├── Dart_Packages.xml
│ └── Dart_SDK.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── .vscode
└── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── example
└── main.dart
├── lib
├── dson_adapter.dart
└── src
│ ├── dson.dart
│ ├── errors
│ └── dson_exception.dart
│ ├── extensions
│ └── iterable_extension.dart
│ └── param.dart
├── pubspec.yaml
└── test
└── src
├── dson_base_test.dart
└── extensions
└── iterable_extension_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Files and directories created by pub.
2 | .dart_tool/
3 | .packages
4 |
5 | # Conventional directory for build outputs.
6 | build/
7 |
8 | # Omit committing pubspec.lock for library packages; see
9 | # https://dart.dev/guides/libraries/private-files#pubspeclock.
10 | pubspec.lock
11 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/fson.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Dart_Packages.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
--------------------------------------------------------------------------------
/.idea/libraries/Dart_SDK.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "fson",
9 | "request": "launch",
10 | "type": "dart"
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.2.1 - 2023-004-11
2 |
3 | - Add new error types :
4 | [ParamUnknown, ParamNullNotAllowed, ParamInvalidType]
5 | - Turn public the [FunctionParam]
6 | - Updates for new sdk version
7 |
8 | ## 1.2.0+2 - 2023-004-11
9 |
10 | - Added Aliases propertie;
11 | - Fixed String List
12 | - Added Dart Docs
13 | - Fix Lints
14 |
15 | ## 1.0.0
16 |
17 | - Initial version.
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 | License Copyright: Flutterando.
3 | License License: Flutterando.
4 | License Contact: Flutterando.
5 | SPDX short identifier: MIT
6 | Further resources...
7 |
8 | Begin license text.
9 | Copyright 2023 Flutterando
10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13 | End license text.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DSON
2 |
3 | Convert JSON to Dart Class withless code generate(build_runner).
4 |
5 | ## A simple Object
6 |
7 | ```dart
8 | class Person {
9 | final int id;
10 | final String name;
11 | final int age;
12 |
13 | Person({
14 | required this.id,
15 | required this.name,
16 | required this.age,
17 | });
18 | }
19 | ```
20 |
21 | Convert json to Object:
22 |
23 | ```dart
24 | main(){
25 | final jsonMap = {
26 | 'id': 1,
27 | 'name': 'Joshua Clak',
28 | 'age': 3,
29 | };
30 |
31 | Person person = dson.fromJson(jsonMap, Person.new);
32 |
33 | print(person.id);
34 | print(person.name);
35 | print(person.age);
36 | }
37 |
38 |
39 | ```
40 |
41 | ## A complex object:
42 |
43 | For complex objects it is necessary to declare the constructor in the `inner` property;
44 |
45 | ```dart
46 | main(){
47 | final jsonMap = {
48 | 'id': 1,
49 | 'name': 'MyHome',
50 | 'owner': {
51 | 'id': 1,
52 | 'name': 'Joshua Clak',
53 | 'age': 3,
54 | },
55 | };
56 |
57 | Person person = dson.fromJson(
58 | jsonMap,
59 | Person.new,
60 | inner: {
61 | 'owner': Person.new,
62 | }
63 | );
64 |
65 | print(person);
66 | }
67 |
68 | ```
69 |
70 | ## A complex object with List:
71 |
72 | For work with a list, it is necessary to declare the constructor in the `inner` property and declare
73 | the list resolver in the `resolvers` property.
74 |
75 | ```dart
76 | main(){
77 | final jsonMap = {
78 | 'id': 1,
79 | 'name': 'MyHome',
80 | 'owner': {
81 | 'id': 1,
82 | 'name': 'Joshua Clak',
83 | 'age': 3,
84 | },
85 | 'parents': [
86 | {
87 | 'id': 2,
88 | 'name': 'Kepper Vidal',
89 | 'age': 25,
90 | },
91 | {
92 | 'id': 3,
93 | 'name': 'Douglas Bisserra',
94 | 'age': 23,
95 | },
96 | ],
97 | };
98 |
99 | Home home = dson.fromJson(
100 | // json Map or List
101 | jsonMap,
102 | // Main constructor
103 | Home.new,
104 | // external types
105 | inner: {
106 | 'owner': Person.new,
107 | 'parents': ListParam(Person.new),
108 | },
109 | );
110 |
111 | print(home);
112 | }
113 |
114 | ```
115 |
116 | DSON Have `ListParam` and `SetParam` for collection.
117 |
118 | ## When API replace Param Name (Set aliases):
119 |
120 | You need to declare within the aliases map the object type that has changed in the key, and in the value, a map with the old key as the key and the new key as the value.
121 |
122 | ```dart
123 | main(){
124 | final jsonMap = {
125 | 'id': 1,
126 | 'name': 'MyHome',
127 | 'master': {
128 | 'key': 1,
129 | 'name': 'Joshua Clak',
130 | 'age': 3,
131 | },
132 | 'parents': [
133 | {
134 | 'key': 2,
135 | 'name': 'Kepper Vidal',
136 | 'age': 25,
137 | },
138 | {
139 | 'key': 3,
140 | 'name': 'Douglas Bisserra',
141 | 'age': 23,
142 | },
143 | ],
144 | };
145 |
146 | Home home = dson.fromJson(
147 | // json Map or List
148 | jsonMap,
149 | // Main constructor
150 | Home.new,
151 | // external types
152 | inner: {
153 | 'owner': Person.new,
154 | 'parents': ListParam(Person.new),
155 | },
156 | // Param names Object <-> Param name in API
157 | aliases: {
158 | Home: {'owner': 'master'},
159 | Person: {'id': 'key'}
160 | }
161 | );
162 |
163 | print(home);
164 | }
165 |
166 | ```
167 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the static analysis results for your project (errors,
2 | # warnings, and lints).
3 | #
4 | # This enables the 'recommended' set of lints from `package:lints`.
5 | # This set helps identify many issues that may lead to problems when running
6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic
7 | # style and format.
8 | #
9 | # If you want a smaller set of lints you can change this to specify
10 | # 'package:lints/core.yaml'. These are just the most critical lints
11 | # (the recommended set includes the core lints).
12 | # The core lints are also what is used by pub.dev for scoring packages.
13 |
14 | include: package:flutterando_analysis/dart_package.yaml
15 |
16 | # Uncomment the following section to specify additional rules.
17 |
18 | # linter:
19 | # rules:
20 | # - camel_case_types
21 |
22 | # analyzer:
23 | # exclude:
24 | # - path/to/excluded/files/**
25 |
26 | # For more information about the core and recommended set of lints, see
27 | # https://dart.dev/go/core-lints
28 |
29 | # For additional information about configuring this file, see
30 | # https://dart.dev/guides/language/analysis-options
31 |
--------------------------------------------------------------------------------
/example/main.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_print
2 |
3 | import 'package:dson_adapter/dson_adapter.dart';
4 |
5 | void main() {
6 | final jsondata = {
7 | 'id': 1,
8 | 'name': 'Jacob',
9 | 'age': 1,
10 | };
11 |
12 | final person = const DSON().fromJson(jsondata, Person.new);
13 | print(person.age);
14 | }
15 |
16 | class Person {
17 | final int id;
18 | final String? name;
19 | final int age;
20 |
21 | Person({
22 | required this.id,
23 | this.name,
24 | this.age = 20,
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/lib/dson_adapter.dart:
--------------------------------------------------------------------------------
1 | library dson_adapter;
2 |
3 | export 'src/dson.dart';
4 | export 'src/errors/dson_exception.dart';
5 | export 'src/param.dart';
6 |
--------------------------------------------------------------------------------
/lib/src/dson.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_catching_errors
2 |
3 | import '../dson_adapter.dart';
4 |
5 | /// Function to transform the value of an object based on its key
6 | typedef ResolverCallback = Object Function(String key, dynamic value);
7 |
8 | /// Convert JSON to Dart Class withless code generate(build_runner)
9 | class DSON {
10 | /// Convert JSON to Dart Class withless code generate(build_runner)
11 | const DSON();
12 |
13 | ///
14 | /// For complex objects it is necessary to declare the constructor in
15 | /// the [inner] property and declare the list resolver in the [resolvers]
16 | /// property.
17 | ///
18 | /// The [aliases] parameter can be used to create alias to specify the name
19 | /// of a field when it is deserialized.
20 | ///
21 | /// For example:
22 | /// ```dart
23 | /// Home home = dson.fromJson(
24 | /// // json Map or List
25 | /// jsonMap,
26 | /// // Main constructor
27 | /// Home.new,
28 | /// // external types
29 | /// inner: {
30 | /// 'owner': Person.new,
31 | /// 'parents': ListParam(Person.new),
32 | /// },
33 | /// // Param names Object <-> Param name in API
34 | /// aliases: {
35 | /// Home: {'owner': 'master'},
36 | /// Person: {'id': 'key'}
37 | /// }
38 | /// );
39 | /// ```
40 |
41 | ///
42 | /// For more information, see the
43 | /// [documentation](https://pub.dev/documentation/dson_adapter/latest/).
44 | T fromJson(
45 | dynamic map,
46 | Function mainConstructor, {
47 | Map inner = const {},
48 | List resolvers = const [],
49 | Map> aliases = const {},
50 | }) {
51 | final mainConstructorNamed = mainConstructor.runtimeType.toString();
52 | final aliasesWithTypeInString =
53 | aliases.map((key, value) => MapEntry(key.toString(), value));
54 | final hasOnlyNamedParams =
55 | RegExp(r'\(\{(.+)\}\)').firstMatch(mainConstructorNamed);
56 | final parentClass = mainConstructorNamed.split(' => ').last;
57 | if (hasOnlyNamedParams == null) {
58 | throw ParamsNotAllowed('$parentClass must have named params only!');
59 | }
60 |
61 | final regExp = _namedParamsRegExMatch(parentClass, mainConstructorNamed);
62 | final functionParams =
63 | _parseFunctionParams(regExp, aliasesWithTypeInString[parentClass]);
64 |
65 | try {
66 | final mapEntryParams = functionParams
67 | .map(
68 | (functionParam) {
69 | dynamic value;
70 |
71 | final hasSubscriptOperator =
72 | map is Map || map is List || map is Set;
73 |
74 | if (!hasSubscriptOperator) {
75 | throw ParamInvalidType.notIterable(
76 | functionParam: functionParam,
77 | receivedType: map.runtimeType.toString(),
78 | parentClass: parentClass,
79 | stackTrace: StackTrace.current,
80 | );
81 | }
82 |
83 | final workflow = map[functionParam.aliasOrName];
84 |
85 | if (workflow is Map || workflow is List || workflow is Set) {
86 | final innerParam = inner[functionParam.name];
87 |
88 | if (innerParam is IParam) {
89 | value = innerParam.call(
90 | this,
91 | workflow,
92 | inner,
93 | resolvers,
94 | aliases,
95 | );
96 | } else if (innerParam is Function) {
97 | value = fromJson(
98 | workflow,
99 | innerParam,
100 | resolvers: resolvers,
101 | aliases: aliases,
102 | );
103 | } else {
104 | value = workflow;
105 | }
106 | } else {
107 | value = workflow;
108 | }
109 |
110 | value = resolvers.fold(
111 | value,
112 | (previousValue, element) =>
113 | element(functionParam.name, previousValue),
114 | );
115 |
116 | if (value == null) {
117 | if (!functionParam.isRequired) return null;
118 | if (!functionParam.isNullable) {
119 | throw ParamNullNotAllowed(
120 | functionParam: functionParam,
121 | parentClass: parentClass,
122 | stackTrace: StackTrace.current,
123 | );
124 | }
125 |
126 | final entry = MapEntry(Symbol(functionParam.name), null);
127 | return entry;
128 | }
129 |
130 | final entry = MapEntry(Symbol(functionParam.name), value);
131 | return entry;
132 | },
133 | )
134 | .where((entry) => entry != null)
135 | .cast>()
136 | .toList();
137 |
138 | final namedParams = {}..addEntries(mapEntryParams);
139 |
140 | return Function.apply(mainConstructor, [], namedParams);
141 | } on TypeError catch (error, stackTrace) {
142 | throw ParamInvalidType.typeError(
143 | error: error,
144 | stackTrace: stackTrace,
145 | functionParams: functionParams,
146 | parentClass: parentClass,
147 | );
148 | }
149 | }
150 |
151 | RegExpMatch _namedParamsRegExMatch(
152 | String parentClass,
153 | String mainConstructorNamed,
154 | ) {
155 | final result = RegExp(r'\(\{(.+)\}\)').firstMatch(mainConstructorNamed);
156 |
157 | if (result == null) {
158 | throw ParamsNotAllowed('$parentClass must have named params only!');
159 | }
160 |
161 | return result;
162 | }
163 |
164 | Iterable _parseFunctionParams(
165 | RegExpMatch regExp,
166 | Map? aliases,
167 | ) {
168 | return regExp.group(1)!.split(',').map((e) => e.trim()).map(
169 | (element) => FunctionParam.fromString(element)
170 | .copyWith(alias: aliases?[element.split(' ').last]),
171 | );
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/lib/src/errors/dson_exception.dart:
--------------------------------------------------------------------------------
1 | import '../../dson_adapter.dart';
2 | import '../extensions/iterable_extension.dart';
3 |
4 | /// Exception from DSON
5 | class DSONException implements Exception {
6 | /// Message for exception
7 | final String message;
8 |
9 | /// stackTrace for exception
10 | final StackTrace? stackTrace;
11 |
12 | /// Exception from DSON
13 | DSONException(this.message, [this.stackTrace]);
14 |
15 | String get _className => '[$DSONException]';
16 |
17 | @override
18 | String toString() {
19 | var message = '$_className: ${this.message}';
20 | if (stackTrace != null) {
21 | message = '$message\n\n$stackTrace';
22 | }
23 |
24 | return message;
25 | }
26 | }
27 |
28 | /// Called when params is not allowed
29 | class ParamsNotAllowed extends DSONException {
30 | /// Called when params is not allowed
31 | ParamsNotAllowed(super.message, [super.stackTrace]);
32 |
33 | @override
34 | String get _className => '[$ParamsNotAllowed]';
35 | }
36 |
37 | /// Called when param is unknown and the library is not able to handle it
38 | class ParamUnknown extends DSONException {
39 | /// The name of the class that contains the param
40 | final String? parentClass;
41 |
42 | /// Param name
43 | final String? paramName;
44 |
45 | /// Called when param is unknown and the library is not able to handle it
46 | ParamUnknown({
47 | this.parentClass,
48 | this.paramName,
49 | StackTrace? stackTrace,
50 | }) : super(
51 | "Unknown error while trying parse parameter '$paramName' on class"
52 | " '$parentClass'",
53 | stackTrace,
54 | );
55 |
56 | @override
57 | String get _className => '[$ParamUnknown]';
58 | }
59 |
60 | /// Called when value is null, but params is required and non-nullable
61 | class ParamNullNotAllowed extends DSONException {
62 | /// the representation of the param
63 | final FunctionParam functionParam;
64 |
65 | /// The name of the class that contains the param
66 | final String parentClass;
67 |
68 | /// Called when value is null, but params is required and non-nullable
69 | ParamNullNotAllowed({
70 | required this.functionParam,
71 | StackTrace? stackTrace,
72 | required this.parentClass,
73 | }) : super(
74 | "Param '${functionParam.name}' from $parentClass"
75 | '({required $functionParam})'
76 | "${functionParam.alias != null ? " with alias"
77 | " '${functionParam.alias}'," : ''}"
78 | ' is required and non-nullable, but the value is null or some alias'
79 | ' is missing.',
80 | stackTrace,
81 | );
82 |
83 | @override
84 | String get _className => '[$ParamNullNotAllowed]';
85 | }
86 |
87 | /// Called when params is not the correct type
88 | class ParamInvalidType extends DSONException {
89 | /// the representation of the param
90 | final FunctionParam functionParam;
91 |
92 | /// The type of param received in json
93 | final String receivedType;
94 |
95 | /// The name of the class that contains the param
96 | final String parentClass;
97 |
98 | /// Called when params is not the correct type
99 | ParamInvalidType(
100 | super.message,
101 | super.stackTrace, {
102 | required this.receivedType,
103 | required this.functionParam,
104 | required this.parentClass,
105 | });
106 |
107 | @override
108 | String get _className => '[$ParamInvalidType]';
109 |
110 | /// Called when params is not the correct type
111 | factory ParamInvalidType.typeError({
112 | required Error error,
113 | required String parentClass,
114 | required Iterable functionParams,
115 | StackTrace? stackTrace,
116 | }) {
117 | final typeErrorAsString = error.toString();
118 |
119 | final errorSplitted = typeErrorAsString.split("'");
120 | final receivedType = errorSplitted[1];
121 | final paramName = errorSplitted[5];
122 |
123 | final functionParam = functionParams.firstWhereOrNull(
124 | (element) => element.name == paramName,
125 | );
126 |
127 | if (functionParam == null) {
128 | throw ParamUnknown(
129 | stackTrace: stackTrace,
130 | parentClass: parentClass,
131 | paramName: paramName,
132 | );
133 | }
134 |
135 | return ParamInvalidType(
136 | "Type '$receivedType' is not a subtype of type '${functionParam.type}' of"
137 | " '$parentClass({${functionParam.isRequired ? 'required ' : ''}"
138 | "$functionParam})'${functionParam.alias != null ? " with alias '"
139 | "${functionParam.alias}'." : '.'}",
140 | stackTrace,
141 | receivedType: receivedType,
142 | functionParam: functionParam,
143 | parentClass: parentClass,
144 | );
145 | }
146 |
147 | /// This is called when the value expected should have a
148 | /// subscritor operator ([]), but the incoming value in json
149 | /// is not iterable (e.g.: not a List, Set or Map)
150 | factory ParamInvalidType.notIterable({
151 | required String receivedType,
152 | required FunctionParam functionParam,
153 | required String parentClass,
154 | required StackTrace? stackTrace,
155 | }) {
156 | return ParamInvalidType(
157 | "Type not iterable '$receivedType' is not a subtype of type"
158 | " '$parentClass'${functionParam.alias != null ? " with alias '"
159 | "${functionParam.alias}'." : '.'}",
160 | stackTrace,
161 | receivedType: receivedType,
162 | functionParam: functionParam,
163 | parentClass: parentClass,
164 | );
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/lib/src/extensions/iterable_extension.dart:
--------------------------------------------------------------------------------
1 | /// Extensions that apply to all iterables.
2 | extension IterableExtension on Iterable {
3 | /// The first element satisfying [test], or `null` if there are none.
4 | T? firstWhereOrNull(bool Function(T element) test) {
5 | for (final element in this) {
6 | if (test(element)) return element;
7 | }
8 | return null;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/src/param.dart:
--------------------------------------------------------------------------------
1 | import '../dson_adapter.dart';
2 |
3 | /// Used in "inner" propetier.
4 | /// IParam represents complex transform, for example: ListParam, SetParam
5 | abstract class IParam {
6 | /// execute transform
7 | T call(
8 | DSON dson,
9 | dynamic map,
10 | Map inner,
11 | List