diff --git a/packages/CHANGELOG.ejson.md b/packages/CHANGELOG.ejson.md index 77a6da4bb..03e24fd6d 100644 --- a/packages/CHANGELOG.ejson.md +++ b/packages/CHANGELOG.ejson.md @@ -2,9 +2,6 @@ - `fromEJson` now accepts a `defaultValue` argument that is returned if `null` is passed as `ejson`. -- `fromEJson` now accepts a `allowCustom` argument that can be used to specify - the custom decoders are allowed to be used. Defaults to `null`, which specify that - currently set value on the stack should be used. At top level the default is `true`. - `register` takes an optional `superTypes` argument to specify the super types of `T` if needed. diff --git a/packages/ejson/lib/src/decoding.dart b/packages/ejson/lib/src/decoding.dart index 0ab7c5572..d551381c2 100644 --- a/packages/ejson/lib/src/decoding.dart +++ b/packages/ejson/lib/src/decoding.dart @@ -65,25 +65,19 @@ final _decoders = CombinedMapView([customDecoders, commonDecoders]); /// /// Throws [InvalidEJson] if [ejson] is not valid for [T]. /// Throws [MissingDecoder] if no decoder is registered for [T]. -T fromEJson(EJsonValue ejson, {bool? allowCustom, T? defaultValue}) { - final oldAllowCustom = _allowCustom; - _allowCustom = allowCustom ?? _allowCustom; - try { - final type = T; - final nullable = type.isNullable; - if (!nullable && ejson == null && defaultValue != null) return defaultValue; - final decoder = nullable ? _decodeNullable : _decoders[type.base]; - if (decoder == null) { - throw MissingDecoder._(ejson, type); - } - final args = nullable ? [type.nonNull] : type.args; - if (args.isEmpty) { - return decoder(ejson) as T; // minor optimization - } - return decoder.callWith(typeArguments: args, parameters: [ejson]) as T; - } finally { - _allowCustom = oldAllowCustom; +T fromEJson(EJsonValue ejson, {T? defaultValue}) { + final type = T; + final nullable = type.isNullable; + if (ejson == null && defaultValue != null) return defaultValue; + final decoder = nullable ? _decodeNullable : _decoders[type.base]; + if (decoder == null) { + throw MissingDecoder._(ejson, type); } + final args = nullable ? [type.nonNull] : type.args; + if (args.isEmpty) { + return decoder(ejson) as T; // minor optimization + } + return decoder.callWith(typeArguments: args, parameters: [ejson]) as T; } /// Parses [source] to [EJsonValue] and convert to type [T]. @@ -116,37 +110,23 @@ dynamic _decodeAny(EJsonValue ejson) { {'\$oid': _} => _decodeObjectId(ejson), {'\$binary': {'base64': _, 'subType': '04'}} => _decodeUuid(ejson), {'\$binary': _} => _decodeBinary(ejson), - List _ => _decodeArray(ejson), - Set _ => _decodeSet(ejson), - Map _ => _tryDecodeCustomIfAllowed(ejson) ?? _decodeDocument(ejson), // other maps goes last!! + List _ => _decodeArray(ejson), + Set _ => _decodeSet(ejson), + Map _ => _decodeDocument(ejson), // other maps goes last!! _ => raiseInvalidEJson(ejson), }; } -bool _allowCustom = true; -dynamic _tryDecodeCustomIfAllowed(EJsonValue ejson) { - if (_allowCustom) { - for (final decoder in customDecoders.values) { - try { - return decoder(ejson); - } catch (_) { - // ignore - } - } - } - return null; -} - List _decodeArray(EJsonValue ejson) { return switch (ejson) { - Iterable i => i.map((ejson) => fromEJson(ejson)).toList(), + Iterable i => i.map((ejson) => fromEJson(ejson)).toList(), _ => raiseInvalidEJson(ejson), }; } Set _decodeSet(EJsonValue ejson) { return switch (ejson) { - Iterable i => i.map((ejson) => fromEJson(ejson)).toSet(), + Iterable i => i.map((ejson) => fromEJson(ejson)).toSet(), _ => raiseInvalidEJson(ejson), }; } @@ -167,20 +147,23 @@ DateTime _decodeDate(EJsonValue ejson) { } DBRef _decodeDBRef(EJsonValue ejson) { - return switch (ejson) { - {'\$ref': String collection, '\$id': EJsonValue id} => DBRef(collection, fromEJson(id)), - _ => raiseInvalidEJson(ejson), - }; + switch (ejson) { + case {'\$ref': String collection, '\$id': EJsonValue id}: + KeyT key = fromEJson(id); + return DBRef.new.callWith(parameters: [collection, key], typeArguments: [key.runtimeType]) as DBRef; + default: + return raiseInvalidEJson(ejson); + } } Defined _decodeDefined(EJsonValue ejson) { if (ejson case {'\$undefined': 1}) return raiseInvalidEJson(ejson); - return Defined(fromEJson(ejson)); + return Defined(fromEJson(ejson)); } Map _decodeDocument(EJsonValue ejson) { return switch (ejson) { - Map m => m.map((key, value) => MapEntry(key as K, fromEJson(value))), + Map m => m.map((key, value) => MapEntry(key as K, fromEJson(value))), _ => raiseInvalidEJson(ejson), }; } @@ -263,14 +246,14 @@ Symbol _decodeSymbol(EJsonValue ejson) { Undefined _decodeUndefined(EJsonValue ejson) { return switch (ejson) { - {'\$undefined': 1} => Undefined(), + {'\$undefined': 1} => Undefined(), _ => raiseInvalidEJson(ejson), }; } UndefinedOr _decodeUndefinedOr(EJsonValue ejson) { return switch (ejson) { - {'\$undefined': 1} => Undefined(), + {'\$undefined': 1} => Undefined(), _ => _decodeDefined(ejson), }; } diff --git a/packages/ejson/lib/src/types.dart b/packages/ejson/lib/src/types.dart index a816cb067..37f080927 100644 --- a/packages/ejson/lib/src/types.dart +++ b/packages/ejson/lib/src/types.dart @@ -31,6 +31,8 @@ enum EJsonType { /// and [MinKey](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-MinKey) enum BsonKey { min, max } +/// See [DBRef](https://github.com/mongodb/specifications/blob/master/source/dbref.md) +/// This is not technically a BSON type, but a common convention. final class DBRef { // Do we need to support the database name? final String collection; diff --git a/packages/ejson/test/ejson_test.dart b/packages/ejson/test/ejson_test.dart index 1251b0ad8..ee3a18633 100644 --- a/packages/ejson/test/ejson_test.dart +++ b/packages/ejson/test/ejson_test.dart @@ -10,9 +10,41 @@ import 'package:ejson/ejson.dart'; import 'package:objectid/objectid.dart'; import 'package:sane_uuid/uuid.dart'; import 'package:test/test.dart'; +import 'package:type_plus/type_plus.dart'; import 'person.dart'; +bool _canDecodeAny([Type? type]) { + commonDecoders; // ensure common types has been registered; + type ??= T; + if (type.isNullable) return _canDecodeAny(type.base); + if ([ + dynamic, + Null, + Object, + bool, + double, + int, + num, + String, + DateTime, + BsonKey, + Symbol, + ObjectId, + Uuid, + Uint8List, + ].contains(type)) return true; + if ([ + List, + Set, + Map, + DBRef, + Undefined, + UndefinedOr, + ].contains(type.base)) return type.args.every(_canDecodeAny); + return false; +} + void _testCase(T value, EJsonValue expected) { test('encode from $value of type $T', () { expect(toEJson(value), expected); @@ -46,7 +78,7 @@ void _testCase(T value, EJsonValue expected) { expect(() => fromEJson(expected), returnsNormally); }); - if (value is! Defined && value is! DBRef) { + if (_canDecodeAny()) { test('roundtrip $value of type $T as dynamic', () { // no here, so dynamic expect(fromEJson(toEJson(value)), value); diff --git a/packages/ejson_generator/test/ctor_test.dart b/packages/ejson_generator/test/ctor_test.dart index e97641993..a7ab3a0eb 100644 --- a/packages/ejson_generator/test/ctor_test.dart +++ b/packages/ejson_generator/test/ctor_test.dart @@ -85,13 +85,6 @@ void _testCase(T value, EJsonValue expected) { test('roundtrip $expected as $T', () { expect(toEJson(fromEJson(expected)), expected); }); - - test('roundtrip $expected of type $T as dynamic', () { - // no here, so dynamic - final decoded = fromEJson(expected); - expect(decoded, isA()); - expect(toEJson(decoded), expected); - }); } void main() { diff --git a/packages/realm_dart/lib/src/handles/native/init.dart b/packages/realm_dart/lib/src/handles/native/init.dart index 2dec585cf..dbebc737e 100644 --- a/packages/realm_dart/lib/src/handles/native/init.dart +++ b/packages/realm_dart/lib/src/handles/native/init.dart @@ -158,7 +158,7 @@ EJsonValue encodeRealmValue(RealmValue value) { } RealmValue decodeRealmValue(EJsonValue ejson) { - Object? decoded = fromEJson(ejson, allowCustom: false); + final decoded = fromEJson(ejson); if (decoded is DBRef) { final t = TypePlus.fromId(decoded.collection); final o = RealmObjectBase.createObject(t, null);