Skip to content

Commit 3c53d2b

Browse files
committed
feat!: remove the usage of iterable kinds for serialization
The system was only kept in after adding tree converters for estimated slight performance benefits which turned out to be minimal though for most use-cases. This additional mechanism made serialization less transparent and more error prone with no direct benefit which is way I decided to remove it in favour of a unified approach. BREAKING CHANGES: Most of the functionality still is available with minimal alterations as type trees include all of the required information, iterable conversion methods that don't make use of type tree will no longer return casted iterable variants though. Also converters and extensions directly building on top of structure definitions may need to update.
1 parent 99890b2 commit 3c53d2b

File tree

10 files changed

+102
-129
lines changed

10 files changed

+102
-129
lines changed

packages/dogs/lib/src/converter.dart

+6-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ abstract class DogConverter<T> extends TypeCapture<T> {
3737
/// Contains structure information about the type being serialized and provides
3838
/// an [OperationMode] mapping for all supported operation modes.
3939
const DogConverter(
40-
{this.struct, this.isAssociated = true, this.keepIterables = false});
40+
{this.struct, this.isAssociated = true, @Deprecated(
41+
"This parameter has been removed in favor of the shift towards tree based converters. "
42+
"This value is effectively a no-op, please remove it.") this.keepIterables = false});
4143

4244
/// Returns the operation mode for the given [opmodeType] or null if the
4345
/// converter does not support the given operation mode by default.
@@ -93,7 +95,9 @@ class SerializableLibrary {
9395
}
9496

9597
/// Marks a class or enum as serializable.
96-
const serializable = Serializable();
98+
const serializable = Structure(
99+
serializable: true
100+
);
97101

98102
@internal
99103
class LinkSerializer {

packages/dogs/lib/src/engine.dart

+27-7
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class DogEngine with MetadataMixin {
3232
if (instanceOverride != null) return instanceOverride!;
3333

3434
if (_instance == null) {
35-
throw DogException("No valid DogEngine instance available. Did you forget to await initialiseDogs() in your main?");
35+
throw DogException(
36+
"No valid DogEngine instance available. Did you forget to await initialiseDogs() in your main?");
3637
}
3738
return _instance!;
3839
}
@@ -357,6 +358,10 @@ class DogEngine with MetadataMixin {
357358
/// If [allowPolymorphic] is true, the returned converter may contain
358359
/// polymorphic converters if any type tree terminals are not concrete.
359360
DogConverter getTreeConverter(TypeTree tree, [bool allowPolymorphic = true]) {
361+
if (!tree.isQualified) {
362+
throw DogException("TypeTree '$tree' must be qualified");
363+
}
364+
360365
final cachedConverter =
361366
_runtimeTreeConverterCache[tree.qualified.typeArgument];
362367
if (cachedConverter != null) return cachedConverter;
@@ -441,9 +446,15 @@ class DogEngine with MetadataMixin {
441446
/// [serialType].
442447
dynamic convertIterableToNative(
443448
dynamic value, Type serialType, IterableKind kind) {
444-
return _nativeSerialization
445-
.forType(serialType, this)
446-
.serializeIterable(value, this, kind);
449+
if (kind == IterableKind.none) {
450+
return convertObjectToNative(value, serialType);
451+
} else if (value is! Iterable) {
452+
throw DogException(
453+
"Cannot convert non-iterable value to iterable of type $serialType");
454+
}
455+
return value.map((e) {
456+
return convertObjectToNative(e, serialType);
457+
}).toList();
447458
}
448459

449460
/// Converts the [value], which can be either a [Iterable] or instance of
@@ -456,9 +467,18 @@ class DogEngine with MetadataMixin {
456467
/// will result in an exception.
457468
dynamic convertIterableFromNative(
458469
dynamic value, Type serialType, IterableKind kind) {
459-
return _nativeSerialization
460-
.forType(serialType, this)
461-
.deserializeIterable(value, this, kind);
470+
if (kind == IterableKind.none) {
471+
return convertObjectFromNative(value, serialType);
472+
} else if (value is! Iterable) {
473+
throw DogException(
474+
"Cannot convert non-iterable value to iterable of type $serialType");
475+
}
476+
return adjustIterable(
477+
value.map((e) {
478+
return convertObjectFromNative(e, serialType);
479+
}),
480+
kind,
481+
);
462482
}
463483
}
464484

packages/dogs/lib/src/opmodes/modes/native.dart

-32
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,6 @@ abstract class NativeSerializerMode<T> implements OperationMode<T> {
3434
/// instances from them.
3535
bool get canSerializeNull => false;
3636

37-
/// Converts the [value], which can be either a [Iterable] or instance of
38-
/// the type associated type, depending on the [IterableKind],
39-
/// to its native representation using the converter associated with
40-
/// [serialType].
41-
dynamic serializeIterable(
42-
dynamic value, DogEngine engine, IterableKind kind) {
43-
if (kind == IterableKind.none) {
44-
return serialize(value, engine);
45-
} else {
46-
if (value is! Iterable) throw Exception("value is not iterable");
47-
return value.map((e) => serialize(e, engine)).toList();
48-
}
49-
}
50-
51-
/// Converts the [value], which can be either a [Iterable] or instance of
52-
/// the associated type, depending on the [IterableKind],
53-
/// to its native representation using the converter associated with
54-
/// [serialType].
55-
///
56-
/// If the value is a [Iterable] implementation, it will converted to the
57-
/// desired [IterableKind]. Trying to convert singular values to an [Iterable]
58-
/// will result in an exception.
59-
dynamic deserializeIterable(
60-
dynamic value, DogEngine engine, IterableKind kind) {
61-
if (kind == IterableKind.none) {
62-
return deserialize(value, engine);
63-
} else {
64-
if (value is! Iterable) throw Exception("value is not iterable");
65-
return adjustIterable(value.map((e) => deserialize(e, engine)), kind);
66-
}
67-
}
68-
6937
/// Creates a new [NativeSerializerMode] instance using the provided
7038
/// [serializer] and [deserializer] functions without needing to create a
7139
/// new class.

packages/dogs/lib/src/structure/field.dart

+55-42
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,9 @@ class DogStructureField extends RetainedAnnotationHolder
2626
/// Declared type of the structure.
2727
final QualifiedTypeTree type;
2828

29-
/// Serial type of the structure.
30-
/// If the declared type is [Iterable], [List] or [Set], the type argument
31-
/// of the iterable will be the serial type.
32-
final TypeCapture serial;
33-
3429
/// Optional converter type override.
3530
final Type? converterType;
3631

37-
/// Type of the iterable, if this field is iterable.
38-
final IterableKind iterableKind;
39-
4032
/// The name of the field.
4133
final String name;
4234

@@ -49,18 +41,39 @@ class DogStructureField extends RetainedAnnotationHolder
4941
@override
5042
final List<RetainedAnnotation> annotations;
5143

44+
/// The kind of iterable this field is.
45+
///
46+
/// NOTE: Originally, this was a custom field that has since been removed and
47+
/// replaced with this getter for compatibility reasons.
48+
IterableKind get iterableKind {
49+
if (type.arguments.length == 1) {
50+
final base = type.base.typeArgument;
51+
if (base == List) {
52+
return IterableKind.list;
53+
} else if (base == Set) {
54+
return IterableKind.set;
55+
}
56+
}
57+
return IterableKind.none;
58+
}
59+
60+
/// Returns the serial type of this field.
61+
///
62+
/// NOTE: Originally, this was a custom field that has since been removed and
63+
/// replaced with this getter for compatibility reasons.
64+
TypeCapture get serial {
65+
if (iterableKind == IterableKind.none) {
66+
return type.qualified;
67+
} else {
68+
return type.arguments[0].qualified;
69+
}
70+
}
71+
5272
/// Creates a new [DogStructureField].
5373
///
5474
/// See also: https://dogs.helight.dev/advanced/structures
55-
const DogStructureField(
56-
this.type,
57-
this.serial,
58-
this.converterType,
59-
this.iterableKind,
60-
this.name,
61-
this.optional,
62-
this.structure,
63-
this.annotations);
75+
const DogStructureField(this.type, this.converterType, this.name,
76+
this.optional, this.structure, this.annotations);
6477

6578
@override
6679
String toString() {
@@ -70,80 +83,80 @@ class DogStructureField extends RetainedAnnotationHolder
7083
/// Creates a synthetic [String] field.
7184
factory DogStructureField.string(String name,
7285
{bool optional = false,
73-
IterableKind iterable = IterableKind.none,
74-
Type? converterType,
75-
List<RetainedAnnotation> annotations = const []}) {
86+
IterableKind iterable = IterableKind.none,
87+
Type? converterType,
88+
List<RetainedAnnotation> annotations = const []}) {
7689
var type = QualifiedTypeTree.terminal<String>();
7790
if (iterable == IterableKind.list) {
7891
type = QualifiedTypeTree.list<String>();
7992
} else if (iterable == IterableKind.set) {
8093
type = QualifiedTypeTree.set<String>();
8194
}
82-
return DogStructureField(type, TypeToken<String>(), converterType, iterable,
83-
name, optional, false, annotations);
95+
return DogStructureField(
96+
type, converterType, name, optional, false, annotations);
8497
}
8598

8699
/// Creates a synthetic [int] field.
87100
factory DogStructureField.int(String name,
88101
{bool optional = false,
89-
IterableKind iterable = IterableKind.none,
90-
Type? converterType,
91-
List<RetainedAnnotation> annotations = const []}) {
102+
IterableKind iterable = IterableKind.none,
103+
Type? converterType,
104+
List<RetainedAnnotation> annotations = const []}) {
92105
var type = QualifiedTypeTree.terminal<int>();
93106
if (iterable == IterableKind.list) {
94107
type = QualifiedTypeTree.list<int>();
95108
} else if (iterable == IterableKind.set) {
96109
type = QualifiedTypeTree.set<int>();
97110
}
98-
return DogStructureField(type, TypeToken<int>(), converterType, iterable,
99-
name, optional, false, annotations);
111+
return DogStructureField(
112+
type, converterType, name, optional, false, annotations);
100113
}
101114

102115
/// Creates a synthetic [double] field.
103116
factory DogStructureField.double(String name,
104117
{bool optional = false,
105-
IterableKind iterable = IterableKind.none,
106-
Type? converterType,
107-
List<RetainedAnnotation> annotations = const []}) {
118+
IterableKind iterable = IterableKind.none,
119+
Type? converterType,
120+
List<RetainedAnnotation> annotations = const []}) {
108121
var type = QualifiedTypeTree.terminal<double>();
109122
if (iterable == IterableKind.list) {
110123
type = QualifiedTypeTree.list<double>();
111124
} else if (iterable == IterableKind.set) {
112125
type = QualifiedTypeTree.set<double>();
113126
}
114-
return DogStructureField(type, TypeToken<double>(), converterType, iterable,
115-
name, optional, false, annotations);
127+
return DogStructureField(
128+
type, converterType, name, optional, false, annotations);
116129
}
117130

118131
/// Creates a synthetic [bool] field.
119132
factory DogStructureField.bool(String name,
120133
{bool optional = false,
121-
IterableKind iterable = IterableKind.none,
122-
Type? converterType,
123-
List<RetainedAnnotation> annotations = const []}) {
134+
IterableKind iterable = IterableKind.none,
135+
Type? converterType,
136+
List<RetainedAnnotation> annotations = const []}) {
124137
var type = QualifiedTypeTree.terminal<bool>();
125138
if (iterable == IterableKind.list) {
126139
type = QualifiedTypeTree.list<bool>();
127140
} else if (iterable == IterableKind.set) {
128141
type = QualifiedTypeTree.set<bool>();
129142
}
130-
return DogStructureField(type, TypeToken<bool>(), converterType, iterable,
131-
name, optional, false, annotations);
143+
return DogStructureField(
144+
type, converterType, name, optional, false, annotations);
132145
}
133146

134147
/// Creates a synthetic field for a terminal serial type.
135148
static DogStructureField create<TYPE>(String name,
136149
{bool optional = false,
137-
IterableKind iterable = IterableKind.none,
138-
Type? converterType,
139-
List<RetainedAnnotation> annotations = const []}) {
150+
IterableKind iterable = IterableKind.none,
151+
Type? converterType,
152+
List<RetainedAnnotation> annotations = const []}) {
140153
var type = QualifiedTypeTree.terminal<TYPE>();
141154
if (iterable == IterableKind.list) {
142155
type = QualifiedTypeTree.list<TYPE>();
143156
} else if (iterable == IterableKind.set) {
144157
type = QualifiedTypeTree.set<TYPE>();
145158
}
146-
return DogStructureField(type, TypeToken<TYPE>(), converterType, iterable,
147-
name, optional, false, annotations);
159+
return DogStructureField(
160+
type, converterType, name, optional, false, annotations);
148161
}
149162
}

packages/dogs/lib/src/structure/harbinger.dart

-7
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,6 @@ class StructureHarbinger<T> {
6161
engine.findAssociatedConverter(field.type.typeArgument);
6262
if (directConverter != null) return directConverter;
6363

64-
if (field.iterableKind != IterableKind.none) {
65-
// Try resolving using the serial type argument (i.E. the first type argument)
66-
final serialConverter =
67-
engine.findAssociatedConverter(field.serial.typeArgument);
68-
if (serialConverter != null) return serialConverter;
69-
}
70-
7164
// Resolve using tree converter
7265
return engine.getTreeConverter(field.type, isPolymorphicField(field));
7366
}

packages/dogs/lib/src/structure/native.dart

+7-28
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,7 @@ class NativeStructureFieldContext {
8383
}
8484
}
8585

86-
if (keepIterables) {
87-
return nativeSerializerMode!.serialize(value, engine);
88-
} else {
89-
return nativeSerializerMode!
90-
.serializeIterable(value, engine, field.iterableKind);
91-
}
86+
return nativeSerializerMode!.serialize(value, engine);
9287
}
9388

9489
/// Decodes [value] like [StructureFieldNativeDeserializerFunc] would for this field.
@@ -139,9 +134,7 @@ class StructureNativeSerialization<T> extends NativeSerializerMode<T>
139134
final fieldName = field.name;
140135
final isOptional = field.optional;
141136
final proxy = structure.proxy;
142-
final iterableKind = field.iterableKind;
143137
final fieldType = field.type;
144-
final serialType = field.serial;
145138
final fieldSerializerHooks =
146139
field.annotationsOf<FieldSerializationHook>().toList();
147140
final NativeStructureFieldContext snFieldContext;
@@ -194,19 +187,16 @@ class StructureNativeSerialization<T> extends NativeSerializerMode<T>
194187
if (mapValue == null) {
195188
if (isOptional) {
196189
args.add(null);
197-
} else if (iterableKind != IterableKind.none) {
198-
args.add(adjustWithCoercion([], iterableKind, serialType,
199-
engine.codec.primitiveCoercion, fieldName));
200190
} else {
201191
args.add(engine.codec.primitiveCoercion
202-
.coerce(serialType, null, fieldName));
192+
.coerce(fieldType, null, fieldName));
203193
}
204194
} else {
205195
if (fieldType.isAssignable(mapValue)) {
206196
args.add(mapValue);
207197
} else {
208-
args.add(adjustWithCoercion(mapValue, iterableKind,
209-
serialType, engine.codec.primitiveCoercion, fieldName));
198+
if (fieldType.isAssignable(mapValue)) return fieldType;
199+
return engine.codec.primitiveCoercion.coerce(fieldType, fieldType, fieldName);
210200
}
211201
}
212202
} on DogFieldSerializerException {
@@ -241,11 +231,8 @@ class StructureNativeSerialization<T> extends NativeSerializerMode<T>
241231
final fieldValue = proxy.getField(v, i);
242232
if (fieldValue == null) {
243233
map[fieldName] = null;
244-
} else if (isKeepIterables) {
245-
map[fieldName] = operation.serialize(fieldValue, engine);
246234
} else {
247-
map[fieldName] = operation.serializeIterable(
248-
fieldValue, engine, iterableKind);
235+
map[fieldName] = operation.serialize(fieldValue, engine);
249236
}
250237
for (var hook in fieldSerializerHooks) {
251238
hook.postFieldSerialization(
@@ -277,22 +264,14 @@ class StructureNativeSerialization<T> extends NativeSerializerMode<T>
277264
if (mapValue == null) {
278265
if (isOptional) {
279266
args.add(null);
280-
} else if (iterableKind != IterableKind.none) {
281-
// TODO: Evaluate if this behavior is what the user wants
282-
args.add(adjustIterable([], iterableKind));
283267
} else if (operation.canSerializeNull) {
284268
args.add(operation.deserialize(null, engine));
285269
} else {
286270
throw DogException(
287-
"Expected a value of serial type ${field.serial.typeArgument} at ${field.name} but got $mapValue");
271+
"Expected a value at ${field.name} but got $mapValue");
288272
}
289273
} else {
290-
if (isKeepIterables) {
291-
args.add(operation.deserialize(mapValue, engine));
292-
} else {
293-
args.add(operation.deserializeIterable(
294-
mapValue, engine, iterableKind));
295-
}
274+
args.add(operation.deserialize(mapValue, engine));
296275
}
297276
} on DogFieldSerializerException {
298277
rethrow;

0 commit comments

Comments
 (0)