-
Notifications
You must be signed in to change notification settings - Fork 214
Optional Entries in Map Patterns #2496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
maybe put return switch (json) {
case {
"type": "location",
"latlon": [lat as num, lon as num],
"name"?: name as String?,
"visits"?: visits as int?,
} {
...
}
};
|
I can see the use-case in destructuring, but it's not working very well with pattern matching. A pattern match will either succeed, and bind variables, or reject, and not. What we have here is more like an optional declaration, which ... I guess could make sense, since it can be initialized to case int i = 0 | String s = "": print("$i: $s"); With the current rules, the variables on each side of an Since the pattern it's irrefutable, you don't need it as part of the match, and you can just do: var name = json["name"] as String?; in the body of the switch. For a plain destructuring assignment, it would make a kind of sense to allow: var {"type": var type as String,
"latlon": [var lat as num, var lon as num],
"name"?: var name = null as String?,
"visits"? : var visits = 0 as int?
} = json; (Not sure if this brings us closer to parameter lists with optional parameters, or if it diverges in a different direction.) |
I'm definitely not sold on default values for patterns. That starts to get really hard for me to follow. Optional entries in map patterns is a pretty interesting idea, though. I think what we'd say is that the matched value type of the optional entry's subpattern is
In other words, the entry itself always matches, but the value sent to the subpattern may be var map = <String, String>{};
var {'x': a} = map; // Throws.
var {'x'?: b} = map; // Fine. `b` has type `String?` and contains `null`. It's an interesting idea. I suspect we shouldn't put it in the initial release because I want to see how much user demand there is for it first. We already use |
At this point, I'd probably just go with; switch (json) {
case {"id": int id, "name": String? name}:
var (bool animated, bool managed, bool available, ...) =
(json["animated"] ?? false, json["managed"] ?? false, json["available"] ?? false, ...);
...
} We don't allow switch (json) {
case {"id": int id, "name": String? name} &&
Map(["animated"]: bool? animated, ["managed"]: bool? managed, ["available"]: bool? available, ...):
...
...
} Default values do make a kind of sense. We have Nullability, the gift that keeps giving. :) |
I started converting models over to pattern matching destructuring for JSON results and I personally expected for Maps with a missing key to be set to null when using nullable syntax. After getting the "Bad state: Pattern matching error" back, the first thing I tried was adding a question mark after the string literal but before the colon like @munificent suggested in his previous post. Attempt 1"myKeyValue": String? myVariable Attempt 2"myKeyValue"?: String? myVariable |
For JSON models with optional keys we have to fall back on |
Consider the following example: Future<Map<String, String>> nameService(int id) async => switch (id) {
1 => {'first': 'Mike', 'last': 'Jones'},
2 => {'first': 'Kevin', 'last': 'Smith', 'middle': 'Jacob'},
3 => {'first': 'Mary', 'last': 'Davis', 'title': 'Dr'},
_ => throw Exception('unknown id'),
};
void main() async {
final name = await nameService(1);
final {
'first': String first,
'last': String last,
'middle': String? middle,
'title': String? title,
} = name;
print('$title $first $middle $last');
} Since we can not specify that
In order to get it working you have to rewrite to not use map patterns: Future<Map<String, String>> nameService(int id) async => switch (id) {
1 => {'first': 'Mike', 'last': 'Jones'},
2 => {'first': 'Kevin', 'last': 'Smith', 'middle': 'Jacob'},
3 => {'first': 'Mary', 'last': 'Davis', 'title': 'Dr'},
_ => throw Exception('unknown id'),
};
void main() async {
final name = await nameService(1);
final String first = name['first']!;
final String last = name['last']!;
final String? middle = name['middle'];
final String? title = name['title'];
print('$title $first $middle $last');
} Having a syntax for optional keys would allow us to express the following: final {
'first': String first,
'last': String last,
'middle'?: String? middle,
'title'?: String? title,
} = name; |
I'd prefer the question mark at the front, so it's immediately clear without needing to find the end of the key, which may be long. also nicer to have it lined up. |
This is still a feature we could consider doing, but note that the upcoming null-aware elements feature does sit on some of the potential syntactic space we might want to use. The |
IMO without this feature, I would recommend to people to simply never use map patterns in an irrefutable context like a variable destructuring assignment. You inevitably run into cases where you cannot express it properly, and then you have to rewrite it. You are better off not using the feature to begin with to avoid rewriting it later. |
Currently the specification for patterns states that a map that does not contain a key will not match. Unfortunately this means that patterns aren't able to be used to destructure JSON with optional fields.
Say we have the following schema:
{"type": "location", "latlon": [number, number], "name": string?, "visits": int (defaults to 0 if unspecified)}
Matching this is not possible due to the optional fields:
Perhaps a
?
suffix could be used on the map key to indicate that it should be optional?The text was updated successfully, but these errors were encountered: