Null-aware operator with Maps - dart

I had the same problem with lists, now it is Map.
What I would like to do
The following syntax is not Dart, as in it does not compile:
map?[key] ?? otherValue
If my map was not a Map but a List, it would look like Günter pointed out here:
list?.elementAt(index) ?? otherValue
What I am searching for
I understand that map?[key] is not valid syntax and therefore I am searching for something like elementAt, which works for lists, for maps.
map?.valueFor(key) ?? otherValue
valueOf
That does obviously not yet exist. The problem has solutions and valueOf might be a good one as well.

This works:
(map ?? const {})[key] ?? otherValue;
Because the key will fallback to accessing an empty Map, which will always return null.

I use this function for null-safe nested map access:
// Returns value of map[p1][p2]...[pn]
// where path = [p1, p2, ..., pn]
//
// example: mapGet(map, ["foo", 9, 'c'])
dynamic mapGet(Map map, List path) {
assert(path.length > 0);
var m = map ?? const {};
for (int i = 0; i < path.length - 1; i++) {
m = m[path[i]] ?? const {};
}
return m[path.last];
}

Something like this might work as an extension function:
extension MapExtensions<T, K> on Map<T, K> {
K getOrNull(T key) {
if (this == null || !this.containsKey(key)) {
return null;
} else {
return this[key];
}
}
K getOrElse(T key, K fallback) {
return this.getOrNull(key) ?? fallback;
}
}
Note: I haven't tested this, but should work.

Yet another syntax I used a few times:
map?.containsKey(key) ?? false ? map[key] : otherValue
Although functionally equivalent to the one proposed by #Günter Zöchbauer it is a bit more cumbersome.
Anyway, since it might look clearer in a few circumstances, it is worth to mention.

Or
map != null ? map[key]: otherValue;
which checks to see if the map is null before attempting to access the variable, which is the same as Günter's answer but checks first.

Based on Arturs answer, I think we should improve it and discuss alternative implementations.
Extension on non-null Map<K,V>
In extensions on non-nullable types this is never null, so we should not check against it. (see bottom for more information)
I use K for the key type and V for the value type
make all parameters and return values nullable.
extension MapExt<K, V> on Map<K, V> {
V? getOrNull(K? key) {
if (!containsKey(key) || key == null) {
return null;
} else {
return this[key];
}
}
V? getOrElse(K? key, V? fallback) {
return getOrNull(key) ?? fallback;
}
}
Null-safe usage would then look like this:
myMap?.getOrNull(someKey)
Note that it is required to put a ? here because the extensions is implemented on non-null maps, which is consistent with the rest of the nullsafe-apis.
(Not really an ) alternative: implement extension on nullable Map<K,V>?
It is possible to implement the extension on nullable maps (i.e. Map<K,V>?) which then would require to check this for null:
extension MapExt<K, V> on Map<K, V>? {
V? getOrNull(K? key) {
if (this == null || !this!.containsKey(key) || key == null) {
return null;
} else {
return this![key];
}
}
V? getOrElse(K? key, V? fallback) {
return getOrNull(key) ?? fallback;
}
}
This implementation allows accessing the extension methods on a potential null map directly without ?:
Map<int,int>? = ...
myMap.getOrNull(someKey); // no compiler error
I think this is not consistent with the rest of the null-safe operators. I would suggest not using it, but preferring the first variant, which reveals that the map might be null.

I have written my own version based on the answers above. It is used mostly for the JSON object handling.
dynamic mapOrListGet(dynamic map, List path) {
assert(path.length > 0);
assert(map is List || map is Map);
var m = map ?? const {};
var firstEl = path.removeAt(0);
var result = (m is List)
? (firstEl < m.length ? m.elementAt(firstEl) : null)
: m[firstEl];
if (path.length == 0 || result == null) {
return result;
} else {
return mapOrListGet(result, path);
}
}
And here is how it works.

Related

Dart best practice to return null from RangeError (index)

What is the best practice to return null for:
Unhandled exception: RangeError (index): Invalid value: Not in inclusive range 0..2
My code:
late final int? element;
try {
element = l[index];
} catch(e) {
element = null;
}
Looking for a shorter, one-liner solution.
Something like:
final element = l[index] ?? null;
The best practice for errors is to not throw them.
In this case, I'd do:
final T? element = (index >= 0 && index < l.length) ? l[index] : null;
No need for late, not throwing or catching errors.
If an error does get thrown, it's probably a real error in the program.
If you catch and ignore errors that you expect, you risk also catching errors you didn't expect, and hiding real problems. That's the real reason catching errors as part of "normal control flow" is frowned upon, not just because less efficient. Which it also is.
And it's why you should always throw (and document) precise types of exceptions from an API, so that the user can do precise catches.
Hope this answer can satisfy you. :)
final int? element = (index == null || index.clamp(0,l.length - 1) != index) ? null : l[index];
you can use extension method :
myextension.dart :
extension NullableList<T> on List<T> {
T? nullable(int index){
T? element;
try {
element = this[index];
} catch(_) {
}
return element;
}
}
use it :
homepage.dart :
List<int> l = [1];
int? result = l.nullable(3);
print(result); //--> null
print(l.nullable(0)); //--> 1

dart nullability checking method [duplicate]

This question already has answers here:
"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety
(3 answers)
Closed 12 months ago.
I have migrated my Dart code to NNBD / Null Safety. Some of it looks like this:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
This causes two analysis errors. For _a += 'a';:
An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.
For Bar() {:
Non-nullable instance field '_a' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
In both cases I have already done exactly what the error suggests! What's up with that?
I'm using Dart 2.12.0-133.2.beta (Tue Dec 15).
Edit: I found this page which says:
The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.
But that doesn't make sense to me - there's only one possible flow control path from if (_a != null) to _a += 'a'; in this case - there's no async code and Dart is single-threaded - so it doesn't matter that _a isn't local.
And the error message for Bar() explicitly states the possibility of initialising the field in the constructor.
The problem is that class fields can be overridden even if it is marked as final. The following example illustrates the problem:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
#override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
The B class overrides the text final field so it returns a value the first time it is asked but returns null after this. You cannot write your A class in such a way that you can prevent this form of overrides from being allowed.
So we cannot change the return value of getText from String? to String even if it looks like we checks the text field for null before returning it.
An expression whose value can be 'null' must be null-checked before it can be dereferenced. Try checking that the value isn't 'null' before dereferencing it.
It seems like this really does only work for local variables. This code has no errors:
class Foo {
String? _a;
void foo() {
final a = _a;
if (a != null) {
a += 'a';
_a = a;
}
}
}
It kind of sucks though. My code is now filled with code that just copies class members to local variables and back again. :-/
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
Ah so it turns out a "field initializer" is actually like this:
class Bar {
Bar() : _a = 'a';
String _a;
}
There are few ways to deal with this situation. I've given a detailed answer here so I'm only writing the solutions from it:
Use local variable (Recommended)
void foo() {
var a = this.a; // <-- Local variable
if (a != null) {
a += 'a';
this.a = a;
}
}
Use ??
void foo() {
var a = (this.a ?? '') + 'a';
this.a = a;
}
Use Bang operator (!)
You should only use this solution when you're 100% sure that the variable (a) is not null at the time you're using it.
void foo() {
a = a! + 'a'; // <-- Bang operator
}
To answer your second question:
Non-nullable fields should always be initialized. There are generally three ways of initializing them:
In the declaration:
class Bar {
String a = 'a';
}
In the initializing formal
class Bar {
String a;
Bar({required this.a});
}
In the initializer list:
class Bar {
String a;
Bar(String b) : a = b;
}
You can create your classes in null-safety like this
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1, this.docCam2, this.docBarcode, this.docSignature});
JobDoc.fromJson(Map<String, dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}

Safe way to access List index

I'm newbee to Dart.
I have troubles to find an easy to read way to "safely" access a List element at any index
final List<String> myList = <String>[]
myList.add("something")
// ...
String myGetter(int index) {
// "heavy" way
if (index < myList.length) {
return myList[index]
}
return null;
}
If I go with regular [index] or elementAt(index) and index is out of boundaries, it throws a RandeError
Is there a method that returns null when the index cannot be reached?
Sorry if double posted, but I try to find the info without any success, + not sure if there is an (un)official slack / discord to ask this kind of "easy" questions
Dart lists do not allow invalid indices. There is no built-in way to get a null when trying. Not in the platform libraries.
You can create your own helper function (like you already do):
T? tryGet<T>(List<T> list, int index) =>
index < 0 || index >= list.length ? null : list[index];
(Remember to check for negative indices too).
As suggested, you can also add it as an extension method:
extension ListGetExtension<T> on List<T> {
T? tryGet(int index) =>
index < 0 || index >= this.length ? null : this[index];
}
which may make it more pleasant to work with.
(I recommend against doing something bad and then catching the error, at least when you can easily check up-front whether it's bad or not).
You can defined an extension method to catch the RangeError and return null:
void main() {
print([1, 2].get(3)); // display null
}
extension SafeLookup<E> on List<E> {
E get(int index) {
try {
return this[index];
} on RangeError {
return null;
}
}
}
You can try this
void main() {
List<int> teste = [1, 2, 3, 4];
print(teste.get(1));
}
extension ListExtension<E> on List<E> {
dynamic get(int value) {
return this.contains(value) ? value : null;
}
}
According to the documentation:
throws a RangeError if index is out of bounds.
So you can use the try-catch block:
String myGetter(int index) {
try {
return myList[index];
}
on RangeError {
// Called when the index is out of bounds
return null;
}
}
If you want to be extra cautious I guess you could put a generic catch at the end (to catch all kinds of throws that are not RangeError), but in a simple getter like this I think that would not be necessary:
[...]
}catch (e) {
// No specified type, handles all other types of error/exceptions
return null;
}
[...]

In Dart, syntactically nice way to cast dynamic to given type or return null?

I have a dynamic x and I would like to assign x to T s if x is T, and otherwise assign null to s. Specifically, I would like to avoid having to type x twice, and to avoid creating a temporary. (For example, I don't want to have to write String s = map['key'] is String ? map['key'] : null; over and over, because I will have many such expressions.) I don't want there to be any possibility of a runtime error.
The following works:
class Cast<T> {
T f(x) {
if (x is T) {
return x;
} else {
return null;
}
}
}
// ...
dynamic x = something();
String s = Cast<String>().f(x);
Is there a syntactically nicer way to do this?
Dart 2 has generic functions which allows
T? cast<T>(x) => x is T ? x : null;
dynamic x = something();
String s = cast<String>(x);
you can also use
var /* or final */ s = cast<String>(x);
and get String inferred for s
I use the following utility function, which allows for an optional fallback value and error logging.
T tryCast<T>(dynamic x, {T fallback}){
try{
return (x as T);
}
on CastError catch(e){
print('CastError when trying to cast $x to $T!');
return fallback;
}
}
var x = something();
String s = tryCast(x, fallback: 'nothing');
Just use the as keyword
final tweet = tweets[index] as Tweet;
I'm using those with Dart null safety (Dart SDK >= 2.12):
T? castOrNull<T>(dynamic x) => x is T ? x : null;
T castOrFallback<T>(dynamic x, T fallback) => x is T ? x : fallback;
A combination of both prior two posts, without the logging.
Fallback defaults to null when not provided.
T cast<T>(dynamic x, {T fallback}) => x is T ? x : fallback;
This hidden gem was provided by one of Dart-Lang's maintainers:
extension AsExtension on Object? {
X as<X>() => this as X;
X? asOrNull<X>() {
var self = this;
return self is X ? self : null;
}
}
extension AsSubtypeExtension<X> on X {
Y asSubtype<Y extends X>() => this as Y;
}
extension AsNotNullExtension<X> on X? {
X asNotNull() => this as X;
}
// example
void main() {
num? n = 1 as dynamic;
n.as<int>().isEven;
n.asSubtype<int>().isEven; // `n.asSubtype<String>()` is an error.
n.asNotNull().floor();
n.asOrNull<int>()?.isEven; // Corresponds to `(n as? int)?.isEven`.
}
NOTE: If your object is of type dynamic, you have to cast it Object? first. The explanation for this can be found here: first one by Erik, a dart maintainer #Google and the second by a community member. Basically it boils down to dart not calling extension methods on receives of one of the following three types: dynamic, Never, or void as stated here.
CastError is deprecated, Instead use TypeError.
With null safety, you can try the below snippet. Where fallback is optional/nullable.
T? tryCast<T>(dynamic value, {T? fallback}) {
try {
return (value as T);
} on TypeError catch (_) {
return fallback;
}
}
Or without fallback -
T? tryCast<T>(dynamic value) {
try {
return (value as T);
} on TypeError catch (_) {
return null;
}
}
Usage -
final val = tryCast<String>(1) ?? "";

Conditional member access for map children?

Lets say you are trying to access deeply nested children in a map and you are not able to expect their parents to be there. Example:
Map awesomeMap = {
"this":{
"is":{
"sometimes":"not here"
}
}
}
Map notAwesomeMap = {
"this":{
"haha":{
"we":"switched"
}
}
}
When I go to access notAwesomeMap['this']['is']['sometimes'] it will return an error because ['this']['is'] is null, and you cannot look for the value ['sometimes'] of null.
So that's fine, but I was hoping to be able to use conditional member access operators...
notAwesomeMap['this']?.['is']?.['sometimes']
but that doesn't work...
Short of wrapping everything in a try block, is there a good way to handle these situations?
Edit: I tried playing around with this and I didn't find anything really illuminating, but maybe this gives someone an idea
void main() {
Map nestedMap = {
'this':{
'is':{
'sometimes':'here'
}
}
};
final mapResult = nestedMap['this'];
print(mapResult); //returns {is: {sometimes: here}}
final nullResult = nestedMap['this']['is an'];
print(nullResult); // returns null
final nullifiedResult = nullify(nestedMap['this']['is an']['error']);
print(nullifiedResult); // returns error, but is this possible another way?
final errorResult = nestedMap['this']['is an']['error'];
print(errorResult); // returns error
}
nullify(object){
try {
final result = object;
return result;
}
catch (e) {
return null;
}
}
One way would be
final result = (((nestedMap ?? const {})['this'] ?? const {})['is an'] ?? const {})['error'];
See also Null-aware operator with Maps
You could write a simple function to help do what you want:
R lookup<R, K>(Map<K, dynamic> map, Iterable<K> keys, [R defaultTo]);
Example usage:
final result = lookup(inputMap, ['this', 'is', 'something']);
Example implementation:
https://dartpad.dartlang.org/1a937b2d8cdde68e6d6f14d216e4c291
void main() {
var nestedMap = {
'this':{
'is':{
'sometimes':'here'
}
}
};
print(lookup(nestedMap, ['this']));
print(lookup(nestedMap, ['this', 'is']));
print(lookup(nestedMap, ['this', 'is', 'sometimes']));
print(lookup(nestedMap, ['this', 'is', 'error']));
// Bail out on null:
print(lookup(nestedMap, ['error'], 'Default Value'));
}
R lookup<R, K>(Map<K, dynamic> map, Iterable<K> keys, [R defaultTo]) {
dynamic current = map;
for (final key in keys) {
if (current is Map<K, dynamic>) {
current = current[key];
} else {
return defaultTo;
}
}
return current as R;
}
I like #matanlurey's approach, but have made two changes:
Drop the defaultTo since you can still use ?? which is more readable.
Swallow type-cast errors.
R lookup <R, K>(Map<K, dynamic> map, Iterable<K> keys) {
dynamic current = map;
for (final key in keys) {
if (current is Map<K, dynamic>) {
current = current[key];
}
}
try{
return current as R;
} catch(e){
// do nothing
}
}
Usage is similar
String someValue = lookup(nestedMap, ['some', 'value']) ?? 'Default Value';

Resources