validateBatch static method
- required List<
Map< jsons,String, dynamic> > - required Map<
String, List< expectedTypes,Type?> ?> - String context = 'UnknownModel',
- Set<
String> optional = const <String>{}, - bool strict = false,
- bool warnUnexpected = false,
- bool escalate = false,
- bool generatePreviews = true,
- Map<
String, bool Function(Object?)> ? validators,
Validates each item in jsons against expectedTypes and returns a BatchValidationResult.
Runs all validations before emitting a single consolidated log entry, preventing one
log event per failure when processing a list of API payloads. No log is emitted when
all items pass, including the vacuously-valid case of an empty jsons list (which
returns isValid: true with failureCount: 0 immediately).
Item indices in BatchValidationResult.failureIndices and in the log message are
zero-based offsets into jsons.
optional, strict, warnUnexpected, validators, and escalate behave
identically to validate. context identifies the model in log output.
Defaults to 'UnknownModel'.
When warnUnexpected is true, a consolidated warning log is emitted after
validation listing every item that contained unexpected keys. This is separate
from the error log and does not affect BatchValidationResult.isValid.
When generatePreviews is true (the default), the logger receives an
'item_previews' key in extras containing a truncated JSON string for each
failing item — equivalent to the 'json_preview' that validate attaches for
single-item calls. Set generatePreviews to false to skip JSON serialisation
for all failing items; useful for large high-failure batches where preview
generation would be costly.
On failure the logger receives an extras map containing:
'context'— the model name.'failure_count'— number of failing items (int).'total_count'— total items in the batch (int).'item_previews'— truncated JSON strings for each failing item, infailureIndicesorder (List<String>). Absent whengeneratePreviewsisfalse.
Example log output when 2 of 5 items fail:
[UserRecord] JSON batch validation failed (2 of 5 items failed):
Item 1 (1 error):
• Missing required key 'name'.
Item 4 (2 errors):
• Key 'role' has invalid type. Expected: String; Actual: int.
• Key 'active' cannot be null.
Implementation
static BatchValidationResult validateBatch({
required List<Map<String, dynamic>> jsons,
required Map<String, List<Type?>?> expectedTypes,
String context = 'UnknownModel',
Set<String> optional = const <String>{},
bool strict = false,
bool warnUnexpected = false,
bool escalate = false,
bool generatePreviews = true,
Map<String, bool Function(Object?)>? validators,
}) {
final StackTrace stackTrace = StackTrace.current;
assert(
!(strict && warnUnexpected),
'JsonSentinel.validateBatch: strict and warnUnexpected are mutually exclusive — '
'use strict to fail on unexpected keys, or warnUnexpected to log without failing, not both.',
);
final List<JsonValidationResult> results = <JsonValidationResult>[];
final List<List<String>> itemWarnings = <List<String>>[];
for (final Map<String, dynamic> json in jsons) {
final ({List<String> errors, List<String> warnings}) core = _validateCore(
json: json,
expectedTypes: expectedTypes,
optional: optional,
strict: strict,
warnUnexpected: warnUnexpected,
validators: validators,
);
itemWarnings.add(core.warnings);
results.add(core.errors.isEmpty ? JsonValidationResult.success : JsonValidationResult.failure(core.errors));
}
final BatchValidationResult batch = BatchValidationResult.fromResults(results);
if (batch.failureCount == 0) {
if (_verbose) {
developer.log(
'[$context] validateBatch() passed — ${jsons.length} item(s) checked.',
name: 'JsonSentinel',
);
}
} else {
final String itemsWord = jsons.length == 1 ? 'item' : 'items';
final StringBuffer buffer = StringBuffer(
'[$context] JSON batch validation failed (${batch.failureCount} of ${jsons.length} $itemsWord failed):',
);
for (final int i in batch.failureIndices) {
final List<String> errors = results[i].errors;
if (errors.isEmpty) continue;
final String errWord = errors.length == 1 ? 'error' : 'errors';
buffer.write('\n Item $i (${errors.length} $errWord):');
for (final String e in errors) {
buffer.write('\n • $e');
}
}
_log(
buffer.toString(),
stackTrace: stackTrace,
extras: <String, Object?>{
'context': context,
'failure_count': batch.failureCount,
'total_count': jsons.length,
if (generatePreviews)
'item_previews': <String>[
for (final int i in batch.failureIndices) _jsonPreview(jsons[i]),
],
},
escalate: escalate,
);
if (_verbose) {
developer.log(
'[$context] validateBatch() failed — ${batch.failureCount} of ${jsons.length} item(s) invalid.',
name: 'JsonSentinel',
);
}
}
final List<int> warningIndices = <int>[
for (int i = 0; i < itemWarnings.length; i++)
if (itemWarnings[i].isNotEmpty) i,
];
if (warningIndices.isNotEmpty) {
final String itemsWord = jsons.length == 1 ? 'item' : 'items';
final StringBuffer wb = StringBuffer(
'[$context] JSON batch validation warning (${warningIndices.length} of ${jsons.length} $itemsWord had unexpected keys):',
);
for (final int i in warningIndices) {
final List<String> ws = itemWarnings[i];
final String keyWord = ws.length == 1 ? 'key' : 'keys';
wb.write('\n Item $i (${ws.length} unexpected $keyWord):');
for (final String w in ws) {
wb.write('\n • $w');
}
}
_log(wb.toString(), stackTrace: stackTrace, extras: <String, Object?>{'context': context}, escalate: false);
if (_verbose) {
developer.log(
'[$context] validateBatch() warned — ${warningIndices.length} of ${jsons.length} item(s) had unexpected keys.',
name: 'JsonSentinel',
);
}
}
return batch;
}