@@ -19,6 +19,73 @@ extension NullableMapChecks<K, V> on Subject<Map<K, V>?> {
19
19
}
20
20
}
21
21
22
+ /// Convert [object] to a pure JSON-like value.
23
+ ///
24
+ /// The result is similar to `jsonDecode(jsonEncode(object))` , but without
25
+ /// passing through a serialized form.
26
+ ///
27
+ /// All JSON atoms (numbers, booleans, null, and strings) are used directly.
28
+ /// All JSON containers (lists, and maps with string keys) are copied
29
+ /// as their elements are converted recursively.
30
+ /// For any other value, a dynamic call `.toJson()` is made and
31
+ /// should return either a JSON atom or a JSON container.
32
+ Object ? deepToJson (Object ? object) {
33
+ // Implementation is based on the recursion underlying [jsonEncode],
34
+ // at [_JsonStringifier.writeObject] in the stdlib's convert/json.dart .
35
+ // (We leave out the cycle-checking, for simplicity / out of laziness.)
36
+
37
+ var (result, success) = _deeplyConvertShallowJsonValue (object);
38
+ if (success) return result;
39
+
40
+ final Object ? shallowlyConverted;
41
+ try {
42
+ shallowlyConverted = (object as dynamic ).toJson ();
43
+ } catch (e) {
44
+ throw JsonUnsupportedObjectError (object, cause: e);
45
+ }
46
+
47
+ (result, success) = _deeplyConvertShallowJsonValue (shallowlyConverted);
48
+ if (success) return result;
49
+ throw JsonUnsupportedObjectError (object);
50
+ }
51
+
52
+ (Object ? result, bool success) _deeplyConvertShallowJsonValue (Object ? object) {
53
+ final Object ? result;
54
+ switch (object) {
55
+ case null || bool () || String () || num ():
56
+ result = object;
57
+ case List ():
58
+ result = object.map ((x) => deepToJson (x)).toList ();
59
+ case Map () when object.keys.every ((k) => k is String ):
60
+ result = object.map ((k, v) => MapEntry (k, deepToJson (v)));
61
+ default :
62
+ return (null , false );
63
+ }
64
+ return (result, true );
65
+ }
66
+
67
+ extension JsonChecks on Subject <Object ?> {
68
+ /// Expects that the value is deeply equal to [expected] ,
69
+ /// after calling [deepToJson] on both.
70
+ ///
71
+ /// Deep equality is computed by [MapChecks.deepEquals]
72
+ /// or [IterableChecks.deepEquals] .
73
+ void jsonEquals (Object ? expected) {
74
+ final expectedJson = deepToJson (expected);
75
+ final actualJson = has ((e) => deepToJson (e), 'deepToJson' );
76
+ switch (expectedJson) {
77
+ case null || bool () || String () || num ():
78
+ return actualJson.equals (expectedJson);
79
+ case List ():
80
+ return actualJson.isA <List >().deepEquals (expectedJson);
81
+ case Map ():
82
+ return actualJson.isA <Map >().deepEquals (expectedJson);
83
+ case _:
84
+ assert (false );
85
+ }
86
+ }
87
+ }
88
+
22
89
extension UriChecks on Subject <Uri > {
23
90
Subject <String > get asString => has ((u) => u.toString (), 'toString' ); // TODO(checks): what's a good convention for this?
24
91
0 commit comments