json_sentinel 0.1.0 copy "json_sentinel: ^0.1.0" to clipboard
json_sentinel: ^0.1.0 copied to clipboard

Lightweight runtime JSON key and type validation for Dart. No code generation required.

json_sentinel #

CI pub.dev

Lightweight runtime JSON key and type validation for Dart — no code generation required.

Validates a Map<String, dynamic> against an expected schema before you deserialise it, catching malformed API responses early with a single, readable log entry per failure.

Features #

  • Checks key existence and value types in one call
  • Nullable fields, union types ([int, double]), and optional fields all supported
  • Strict mode flags unexpected keys not declared in your schema
  • Single log entry per call listing every problem as a bullet — nothing hidden by an early return
  • Structured json_preview and context passed as extras for searchable Sentry/Crashlytics context
  • Configurable escalate flag per call — control whether a failure is a breadcrumb or a capture
  • Returns JsonValidationResult with isValid + programmatic errors list
  • silence() for pure programmatic use with no log output
  • Optional verbose: true for debug-mode traces via dart:developer
  • Zero runtime dependencies — pure Dart, works in Flutter, server, and CLI

Getting started #

dependencies:
  json_sentinel: ^0.1.0

Usage #

1. Wire in your logger once at startup #

JsonSentinel.configure(
  (message, {error, stackTrace, extras, escalate}) {
    // escalate: true → full capture; false → breadcrumb
    if (escalate == true) {
      Sentry.captureMessage(message, hint: Hint.withMap(extras ?? {}));
    } else {
      Sentry.addBreadcrumb(Breadcrumb(message: message));
    }
  },
  verbose: true, // optional: dart:developer traces in debug mode
);

If configure() has not been called, failures fall back to dart:developer (visible in DevTools Logging; suppressed in release builds). Always call configure() or silence() in production code. Use JsonSentinel.logger to check whether a logger is already registered.

No logging needed?

Use silence() when you only want programmatic access to result.errors:

JsonSentinel.silence(); // installs a no-op logger — no output of any kind

silence() and configure() are mutually exclusive — call one or the other, never both.

2. Validate in tryFromJson #

static OrderResponse? tryFromJson(Map<String, dynamic> json) {
  final result = JsonSentinel.validate(
    json: json,
    expectedTypes: {
      'orderId':   [int],
      'depotCode': [String],
      'litres':    [int, double],   // union — API may return either
      'notes':     [String, null],  // nullable
    },
    optional: {'notes'},            // absent is fine; type-checked if present
    context: 'OrderResponse',
    escalate: true,
  );
  if (!result.isValid) return null;

  return OrderResponse(
    orderId:   json['orderId'] as int,
    depotCode: json['depotCode'] as String,
    litres:    (json['litres'] as num).toDouble(),
    notes:     json['notes'] as String?,
  );
}

Nested JSON #

Validate the outer shape at each level; let child models validate their own fields. Failures carry the child's own context string, so log entries are always pinpointed.

// Parent: validates top-level keys as List/Map only.
static PaginatedResponse? tryFromJson(Map<String, dynamic> json) {
  final result = JsonSentinel.validate(
    json: json,
    expectedTypes: {
      'data':  [List],
      'links': [Map],
      'meta':  [Map],
    },
    context: 'PaginatedResponse',
    escalate: true,
  );
  if (!result.isValid) return null;

  final meta = MetaModel.tryFromJson(
    Map<String, dynamic>.from(json['meta'] as Map),
  );
  if (meta == null) return null;
  // ...
}

// Child: validates its own schema, including nullable fields.
static MetaModel? tryFromJson(Map<String, dynamic> json) {
  final result = JsonSentinel.validate(
    json: json,
    expectedTypes: {
      'current_page': [int],
      'from':         [null, int],  // null when page is empty
      'last_page':    [int],
      'links':        [List],
      'path':         [String],
      'per_page':     [int],
      'to':           [null, int],  // null when page is empty
      'total':        [int],
    },
    context: 'MetaModel',
    escalate: true,
  );
  if (!result.isValid) return null;
  // ...
}

Optional fields #

Keys listed in optional are skipped when absent but still type-checked when present:

JsonSentinel.validate(
  json: json,
  expectedTypes: {'id': [int], 'nickname': [String]},
  optional: {'nickname'},
);

Strict mode #

Reject keys present in the JSON but not declared in your schema:

JsonSentinel.validate(
  json: json,
  expectedTypes: {'id': [int]},
  strict: true,
);

Programmatic error access #

final result = JsonSentinel.validate(json: json, expectedTypes: schema);
if (!result.isValid) {
  for (final error in result.errors) {
    print(error);
  }
}

Example log output #

[OrderResponse] JSON validation failed (2 errors):
  • Key 'orderId' has invalid type. Expected: int; Actual: String.
  • Missing required key 'depotCode'.

Testing #

configure() and silence() both assert in debug mode if called twice. Call JsonSentinel.resetLoggerForTesting() in tearDown to allow re-configuration across test cases:

setUp(() {
  JsonSentinel.configure((message, {error, stackTrace, extras, escalate}) {
    logs.add(message);
  });
});
tearDown(JsonSentinel.resetLoggerForTesting);

Schema reference #

Type list value Meaning
[int] Required, must be int
[String] Required, must be String
[bool] Required, must be bool
[double] Required, must be double
[num] Required, int or double
[int, double] Required, int or double (explicit union)
[Map] Required, any Map
[List] Required, any List
[String, null] Required, String or null
[null, int] Required, null or int
null or [] Required key, type not checked

Add the key to optional to make presence itself optional.

Additional information #

4
likes
0
points
198
downloads

Publisher

unverified uploader

Weekly Downloads

Lightweight runtime JSON key and type validation for Dart. No code generation required.

Repository (GitHub)
View/report issues

Topics

#json #validation #schema-validation

License

unknown (license)

More

Packages that depend on json_sentinel