Records and patterns

Dart · Practical · 14 min

Records and patterns are Dart 3's biggest additions and make JSON handling and state matching concise and safe.

A record is an anonymous, immutable, fixed-size aggregate: (1, 'a') or with named fields (name: 'Ana', age: 31). Its type is written the same way: (int, String) or ({String name, int age}). Positional fields are read with $1, $2; named ones by name. Records have structural == and hashCode, so they're great for returning several values:

(double, double) minMax(List<double> xs) => (xs.reduce(min), xs.reduce(max));
final (lo, hi) = minMax(data);

Patterns describe the shape of a value and destructure it. They appear in:

  • Variable declarations: final (lo, hi) = ...;, var {'name': name} = json;.
  • switch statements and expressions.
  • if (value case pattern) — match and bind in a condition.
  • for loops: for (final (i, x) in list.indexed).

Pattern kinds include constants, relational (> 0), logical (||, &&), records, lists ([first, ...rest]), maps ({'id': int id}), objects (Point(x: 0, :var y)), wildcards _, casts and null checks (var x?).

Map and list patterns check types as they bind, which makes validating untyped JSON clean: if (json case {'name': String name, 'age': int age}) ... only matches when both keys exist with the right types. A when clause adds a guard: case (var a, var b) when a > b:.

Example

({int ok, int failed}) summarize(List<int> codes) {
  var ok = 0, failed = 0;
  for (final c in codes) {
    if (c case >= 200 && < 300) ok++; else failed++;
  }
  return (ok: ok, failed: failed);
}

String describe(Object? json) => switch (json) {
      {'type': 'user', 'name': String name, 'age': int age} when age >= 18 =>
        'adult $name',
      {'type': 'user', 'name': String name} => 'user $name',
      [int first, ...] => 'list starting with $first',
      null => 'nothing',
      _ => 'unknown',
    };

void main() {
  final (:ok, :failed) = summarize([200, 404, 201, 500]);
  print('$ok ok, $failed failed');                               // 2 ok, 2 failed
  print(describe({'type': 'user', 'name': 'Ana', 'age': 31}));   // adult Ana
  print(describe({'type': 'user', 'name': 'Bo'}));               // user Bo
  print(describe([7, 8]));                                       // list starting with 7
  print((1, 'a') == (1, 'a'));                                   // true
}

Remember

  • Records (a, b) / (name: x) bundle values with structural equality
  • Patterns destructure in declarations, switch, if-case and for
  • Map/list patterns type-check while binding — ideal for JSON

Watch out for

  • Matching JSON numbers with int when the server may send 1.0; use num and convert.

Check yourself

  1. Does `{'id': int id}` match the map `{'id': 7, 'extra': true}`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dart.dev/language/patterns

Records and patterns · Dart · Topolearn