rejectUnknownKeys function

void rejectUnknownKeys(
  1. String context,
  2. Map<String, dynamic> json,
  3. Set<String> known
)

Rejects a decoded payload carrying a key this API does not define.

Option and configuration fields all have defaults, so an absent key is legitimate and must fall back rather than throw. A key that is present but misspelled is not: it would silently take the default and drop the value the integrator supplied.

The two platforms disagree here, so this is a deliberate choice rather than a contract being mirrored. Android rejects the same payload — its JsonSerializer sets ignoreUnknownKeys = false — while iOS ignores it, because the option structs use the compiler-synthesised Decodable. Rejecting matches Android and, more importantly, reports the mistake here, naming the class and the key, rather than letting it surface as an opaque kotlinx failure on Android and as silently wrong behaviour on iOS.

The accepted key sets are written by hand: Dart has no reflection outside dart:mirrors, which is unavailable under Flutter's AOT compilation, so a decoder cannot ask a class for its fields. step_builders_test.dart decodes every strict class from its own toJson output, so a field added without extending the set fails there instead of rejecting a valid key at runtime.

Deliberately not re-exported from steps.dart: it is internal to the step decoders and is not part of the public API.

Implementation

void rejectUnknownKeys(
  String context,
  Map<String, dynamic> json,
  Set<String> known,
) {
  final unknown = json.keys.toSet().difference(known);
  if (unknown.isEmpty) {
    return;
  }
  throw FormatException(
    '$context: unknown key(s) ${(unknown.toList()..sort()).join(', ')}. '
    'Expected any of: ${(known.toList()..sort()).join(', ')}.',
  );
}