Skip to content

Commit 5810903

Browse files
authored
Add mutable collection serializers: List, Map, Set. (#1360)
1 parent abc3dda commit 5810903

File tree

8 files changed

+686
-0
lines changed

8 files changed

+686
-0
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
# 8.10.1
4+
5+
- Add mutable collection serializers: `List`, `Set`, `Map`. These let you
6+
easily serialize mutable collections of immutable value types; it's still
7+
recommended to avoid mutable collections inside value types, as they break
8+
hashing, comparison and caching.
9+
310
# 8.10.0
411

512
- Stop generating unnecessary `new` keywords.

built_value/lib/serializer.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import 'package:built_value/src/duration_serializer.dart';
1010
import 'package:built_value/src/int32_serializer.dart';
1111
import 'package:built_value/src/int64_serializer.dart';
1212
import 'package:built_value/src/json_object_serializer.dart';
13+
import 'package:built_value/src/list_serializer.dart';
14+
import 'package:built_value/src/map_serializer.dart';
1315
import 'package:built_value/src/num_serializer.dart';
16+
import 'package:built_value/src/set_serializer.dart';
1417
import 'package:built_value/src/uint8_list_serializer.dart';
1518
import 'package:built_value/src/uri_serializer.dart';
1619

@@ -59,6 +62,9 @@ abstract class Serializers {
5962
return (SerializersBuilder()
6063
..add(BigIntSerializer())
6164
..add(BoolSerializer())
65+
..add(ListSerializer())
66+
..add(MapSerializer())
67+
..add(SetSerializer())
6268
..add(BuiltListSerializer())
6369
..add(BuiltListMultimapSerializer())
6470
..add(BuiltMapSerializer())
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2025, Google Inc. Please see the AUTHORS file for details.
2+
// All rights reserved. Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
import 'package:built_collection/built_collection.dart';
6+
import 'package:built_value/serializer.dart';
7+
8+
class ListSerializer implements StructuredSerializer<List> {
9+
final bool structured = true;
10+
@override
11+
final Iterable<Type> types = BuiltList<Type>([List, <Object>[].runtimeType]);
12+
@override
13+
final String wireName = 'List';
14+
15+
@override
16+
Iterable<Object?> serialize(Serializers serializers, List list,
17+
{FullType specifiedType = FullType.unspecified}) {
18+
var isUnderspecified =
19+
specifiedType.isUnspecified || specifiedType.parameters.isEmpty;
20+
if (!isUnderspecified) serializers.expectBuilder(specifiedType);
21+
22+
var elementType = specifiedType.parameters.isEmpty
23+
? FullType.unspecified
24+
: specifiedType.parameters[0];
25+
26+
return list
27+
.map((item) => serializers.serialize(item, specifiedType: elementType));
28+
}
29+
30+
@override
31+
List deserialize(Serializers serializers, Iterable serialized,
32+
{FullType specifiedType = FullType.unspecified}) {
33+
var isUnderspecified =
34+
specifiedType.isUnspecified || specifiedType.parameters.isEmpty;
35+
36+
var elementType = specifiedType.parameters.isEmpty
37+
? FullType.unspecified
38+
: specifiedType.parameters[0];
39+
40+
var result = isUnderspecified
41+
? <Object>[]
42+
: serializers.newBuilder(specifiedType) as List;
43+
44+
for (final item in serialized) {
45+
result.add(serializers.deserialize(item, specifiedType: elementType));
46+
}
47+
return result;
48+
}
49+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2025, Google Inc. Please see the AUTHORS file for details.
2+
// All rights reserved. Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
import 'package:built_collection/built_collection.dart';
6+
import 'package:built_value/serializer.dart';
7+
8+
class MapSerializer implements StructuredSerializer<Map> {
9+
final bool structured = true;
10+
@override
11+
final Iterable<Type> types =
12+
BuiltList<Type>([Map, <Object, Object>{}.runtimeType]);
13+
@override
14+
final String wireName = 'Map';
15+
16+
@override
17+
Iterable<Object?> serialize(Serializers serializers, Map Map,
18+
{FullType specifiedType = FullType.unspecified}) {
19+
var isUnderspecified =
20+
specifiedType.isUnspecified || specifiedType.parameters.isEmpty;
21+
if (!isUnderspecified) serializers.expectBuilder(specifiedType);
22+
23+
var keyType = specifiedType.parameters.isEmpty
24+
? FullType.unspecified
25+
: specifiedType.parameters[0];
26+
var valueType = specifiedType.parameters.isEmpty
27+
? FullType.unspecified
28+
: specifiedType.parameters[1];
29+
30+
var result = <Object?>[];
31+
for (var key in Map.keys) {
32+
result.add(serializers.serialize(key, specifiedType: keyType));
33+
final value = Map[key];
34+
result.add(serializers.serialize(value, specifiedType: valueType));
35+
}
36+
return result;
37+
}
38+
39+
@override
40+
Map deserialize(Serializers serializers, Iterable serialized,
41+
{FullType specifiedType = FullType.unspecified}) {
42+
var isUnderspecified =
43+
specifiedType.isUnspecified || specifiedType.parameters.isEmpty;
44+
45+
var keyType = specifiedType.parameters.isEmpty
46+
? FullType.unspecified
47+
: specifiedType.parameters[0];
48+
var valueType = specifiedType.parameters.isEmpty
49+
? FullType.unspecified
50+
: specifiedType.parameters[1];
51+
52+
var result = isUnderspecified
53+
? <Object, Object>{}
54+
: serializers.newBuilder(specifiedType) as Map;
55+
56+
if (serialized.length % 2 == 1) {
57+
throw ArgumentError('odd length');
58+
}
59+
60+
for (var i = 0; i != serialized.length; i += 2) {
61+
final key = serializers.deserialize(serialized.elementAt(i),
62+
specifiedType: keyType);
63+
final value = serializers.deserialize(serialized.elementAt(i + 1),
64+
specifiedType: valueType);
65+
result[key] = value;
66+
}
67+
68+
return result;
69+
}
70+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2025, Google Inc. Please see the AUTHORS file for details.
2+
// All rights reserved. Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
import 'package:built_collection/built_collection.dart';
6+
import 'package:built_value/serializer.dart';
7+
8+
class SetSerializer implements StructuredSerializer<Set> {
9+
final bool structured = true;
10+
@override
11+
final Iterable<Type> types = BuiltSet<Type>([Set, <Object>{}.runtimeType]);
12+
@override
13+
final String wireName = 'Set';
14+
15+
@override
16+
Iterable<Object?> serialize(Serializers serializers, Set set,
17+
{FullType specifiedType = FullType.unspecified}) {
18+
var isUnderspecified =
19+
specifiedType.isUnspecified || specifiedType.parameters.isEmpty;
20+
if (!isUnderspecified) serializers.expectBuilder(specifiedType);
21+
22+
var elementType = specifiedType.parameters.isEmpty
23+
? FullType.unspecified
24+
: specifiedType.parameters[0];
25+
26+
return set
27+
.map((item) => serializers.serialize(item, specifiedType: elementType));
28+
}
29+
30+
@override
31+
Set deserialize(Serializers serializers, Iterable serialized,
32+
{FullType specifiedType = FullType.unspecified}) {
33+
var isUnderspecified =
34+
specifiedType.isUnspecified || specifiedType.parameters.isEmpty;
35+
36+
var elementType = specifiedType.parameters.isEmpty
37+
? FullType.unspecified
38+
: specifiedType.parameters[0];
39+
40+
var result = isUnderspecified
41+
? <Object>{}
42+
: serializers.newBuilder(specifiedType) as Set;
43+
44+
for (final item in serialized) {
45+
result.add(serializers.deserialize(item, specifiedType: elementType));
46+
}
47+
return result;
48+
}
49+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) 2025, Google Inc. Please see the AUTHORS file for details.
2+
// All rights reserved. Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
7+
import 'package:built_value/serializer.dart';
8+
import 'package:test/test.dart';
9+
10+
void main() {
11+
group('List with known specifiedType but missing builder', () {
12+
var data = <int>[1, 2, 3];
13+
var specifiedType = const FullType(List, [FullType(int)]);
14+
var serializers = Serializers();
15+
var serialized = json.decode(json.encode([1, 2, 3])) as Object;
16+
17+
test('serialize throws', () {
18+
expect(() => serializers.serialize(data, specifiedType: specifiedType),
19+
throwsA(const TypeMatcher<StateError>()));
20+
});
21+
22+
test('deserialize throws', () {
23+
expect(
24+
() =>
25+
serializers.deserialize(serialized, specifiedType: specifiedType),
26+
throwsA(const TypeMatcher<DeserializationError>()));
27+
});
28+
});
29+
30+
group('List with known specifiedType and correct builder', () {
31+
var data = <int>[1, 2, 3];
32+
var specifiedType = const FullType(List, [FullType(int)]);
33+
var serializers = (Serializers().toBuilder()
34+
..addBuilderFactory(specifiedType, () => <int>[]))
35+
.build();
36+
var serialized = json.decode(json.encode([1, 2, 3])) as Object;
37+
38+
test('can be serialized', () {
39+
expect(serializers.serialize(data, specifiedType: specifiedType),
40+
serialized);
41+
});
42+
43+
test('can be deserialized', () {
44+
expect(serializers.deserialize(serialized, specifiedType: specifiedType),
45+
data);
46+
});
47+
48+
test('keeps generic type when deserialized', () {
49+
expect(
50+
serializers
51+
.deserialize(serialized, specifiedType: specifiedType)
52+
.runtimeType,
53+
<int>[].runtimeType);
54+
});
55+
});
56+
57+
group('List nested with known specifiedType and correct builders', () {
58+
var data = <List<int>>[
59+
[1, 2, 3],
60+
[4, 5, 6],
61+
[7, 8, 9]
62+
];
63+
var specifiedType = const FullType(List, [
64+
FullType(List, [FullType(int)])
65+
]);
66+
var serializers = (Serializers().toBuilder()
67+
..addBuilderFactory(specifiedType, () => <List<int>>[])
68+
..addBuilderFactory(
69+
const FullType(List, [FullType(int)]), () => <int>[]))
70+
.build();
71+
var serialized = json.decode(json.encode([
72+
[1, 2, 3],
73+
[4, 5, 6],
74+
[7, 8, 9]
75+
])) as Object;
76+
77+
test('can be serialized', () {
78+
expect(serializers.serialize(data, specifiedType: specifiedType),
79+
serialized);
80+
});
81+
82+
test('can be deserialized', () {
83+
expect(serializers.deserialize(serialized, specifiedType: specifiedType),
84+
data);
85+
});
86+
});
87+
88+
group('List with unknown specifiedType and no builders', () {
89+
var data = <int>[1, 2, 3];
90+
var specifiedType = FullType.unspecified;
91+
var serializers = Serializers();
92+
var serialized = json.decode(json.encode([
93+
'List',
94+
['int', 1],
95+
['int', 2],
96+
['int', 3]
97+
])) as Object;
98+
99+
test('can be serialized', () {
100+
expect(serializers.serialize(data, specifiedType: specifiedType),
101+
serialized);
102+
});
103+
104+
test('can be deserialized', () {
105+
expect(serializers.deserialize(serialized, specifiedType: specifiedType),
106+
data);
107+
});
108+
});
109+
}

0 commit comments

Comments
 (0)