validate static method
- required Map<
String, dynamic> json, - required Map<
String, List< expectedTypes,Type?> ?> - Set<
String> optional = const <String>{}, - bool strict = false,
- bool warnUnexpected = false,
- bool escalate = false,
- String context = 'UnknownModel',
- String? parentContext,
- Map<
String, bool Function(Object?)> ? validators,
Validates json against expectedTypes and returns a JsonValidationResult.
All failures are collected before logging — a single log entry is emitted per call with every problem listed as a bullet. Returns JsonValidationResult.success when all validations pass.
expectedTypes defines the schema. Keys are the JSON field names; values
are lists of allowed types. Including null in the list marks the field
nullable. A null or empty list checks key existence only.
optional lists keys that are skipped when absent from json but still
type-checked when present. Defaults to {}.
When strict is true, keys present in json but absent from
expectedTypes are reported as errors. Defaults to false.
Mutually exclusive with warnUnexpected.
When warnUnexpected is true, keys present in json but absent from
expectedTypes are logged as warnings without failing validation —
JsonValidationResult.isValid remains true. Mutually exclusive with
strict; asserted in debug mode.
validators is an optional map of per-key predicate functions run after
type validation passes for that key. Each predicate receives the field value
as Object? and must return false to fail. A failing predicate adds a
bullet error for the key. Predicates are skipped for absent optional keys
and for keys that already failed type validation.
escalate is forwarded to the logger as a hint that this failure warrants
elevated capture (e.g. a Sentry event rather than a breadcrumb).
Defaults to false. Warning logs always use escalate: false.
context identifies the model in log output. Defaults to 'UnknownModel'.
parentContext is an optional parent path prepended to context in log
output and in the logger's extras map. When provided, the effective context
becomes '$parentContext > $context' — useful when chaining validate calls
across nested models to show the full path to the failing item
(e.g. UserPage.data[2] > MetaModel).
When verbose logging is enabled via configure or silence, a
dart:developer trace is emitted on each passing call.
Example log output:
[ProductListing] JSON validation failed (2 errors):
• Key 'productId' has invalid type. Expected: int; Actual: String.
• Missing required key 'sku'.
Implementation
static JsonValidationResult validate({
required Map<String, dynamic> json,
required Map<String, List<Type?>?> expectedTypes,
Set<String> optional = const <String>{},
bool strict = false,
bool warnUnexpected = false,
bool escalate = false,
String context = 'UnknownModel',
String? parentContext,
Map<String, bool Function(Object?)>? validators,
}) {
final StackTrace stackTrace = StackTrace.current;
assert(
!(strict && warnUnexpected),
'JsonSentinel.validate: strict and warnUnexpected are mutually exclusive — '
'use strict to fail on unexpected keys, or warnUnexpected to log without failing, not both.',
);
final String effectiveContext = parentContext != null ? '$parentContext > $context' : context;
final ({List<String> errors, List<String> warnings}) core = _validateCore(
json: json,
expectedTypes: expectedTypes,
optional: optional,
strict: strict,
warnUnexpected: warnUnexpected,
validators: validators,
);
if (core.warnings.isNotEmpty) {
final String warnCount = '${core.warnings.length} unexpected key${core.warnings.length == 1 ? '' : 's'}';
final String warnBullets = core.warnings.map((String w) => ' • $w').join('\n');
_log(
'[$effectiveContext] JSON validation warning ($warnCount):\n$warnBullets',
stackTrace: stackTrace,
extras: <String, Object?>{'context': effectiveContext, 'json_preview': _jsonPreview(json)},
escalate: false,
);
if (_verbose) {
developer.log(
'[$effectiveContext] validate() warned — ${core.warnings.length} unexpected key(s).',
name: 'JsonSentinel',
);
}
}
if (core.errors.isNotEmpty) {
final String count = '${core.errors.length} error${core.errors.length == 1 ? '' : 's'}';
final String bullets = core.errors.map((String e) => ' • $e').join('\n');
_log(
'[$effectiveContext] JSON validation failed ($count):\n$bullets',
stackTrace: stackTrace,
extras: <String, Object?>{'context': effectiveContext, 'json_preview': _jsonPreview(json)},
escalate: escalate,
);
if (_verbose) {
developer.log(
'[$effectiveContext] validate() failed — ${core.errors.length} error(s).',
name: 'JsonSentinel',
);
}
return JsonValidationResult.failure(core.errors);
}
if (_verbose && core.warnings.isEmpty) {
developer.log(
'[$effectiveContext] validate() passed — ${expectedTypes.length} key(s) checked.',
name: 'JsonSentinel',
);
}
return JsonValidationResult.success;
}