Skip to content

Your first schema

In this tutorial you validate a sign-up payload with three fields, one of them nested, then turn the valid map into a Dart object. It continues from Your first validator.

  1. Define the schema.

    l.schema() takes a map from field name to validator. Each entry is a field. A schema is itself a validator, so a field’s validator can be another schema.

    bin/main.dart
    import 'package:luthor/luthor.dart';
    final user = l.schema({
    'email': l.string().email().required(),
    'age': l.int().min(18),
    'profile': l.schema({
    'displayName': l.string().min(2).required(),
    }).required(),
    });

    age has no .required(), so it may be missing or null. When it is present it has to be an int of at least 18.

  2. Validate a map.

    validate accepts any input and returns a ValidationResult<Map<String, Object?>?>.

    bin/main.dart
    import 'package:luthor/luthor.dart';
    final user = l.schema({
    'email': l.string().email().required(),
    'age': l.int().min(18),
    'profile': l.schema({
    'displayName': l.string().min(2).required(),
    }).required(),
    });
    void main() {
    final result = user.validate({
    'email': 'bad',
    'age': 12,
    'profile': {'displayName': 'x'},
    });
    print(result);
    }

    The failure case prints the error map: one entry per failing field, keyed by its path. The nested field appears as profile.displayName, and the messages use the field names because the schema supplies them.

    dart run bin/main.dart
    ValidationFailure(errors: {email: [email must be a valid email address], age: [age must be greater than or equal to 18], profile.displayName: [displayName must be at least 2 characters long]})
  3. Look an error up by path.

    getError takes an error path, a dot-separated chain of field names, and returns the first message at that path or null. It is available on every result, so you can call it in either branch.

    bin/main.dart
    void main() {
    final result = user.validate({
    'email': 'bad',
    'age': 12,
    'profile': {'displayName': 'x'},
    });
    switch (result) {
    case ValidationSuccess(:final data):
    print('+ $data');
    case ValidationFailure():
    print('- ${result.getError('profile.displayName')}');
    }
    }
    dart run bin/main.dart
    - displayName must be at least 2 characters long
    user.validate(input)+2-1
    1. valid: {'email': '[email protected]', 'profile': {'displayName': 'Ada'}}
    2. valid: {'email': '[email protected]', 'age': 42, 'profile': {'displayName': 'Ada'}}
    3. invalid: {'email': 'bad', 'age': 12, 'profile': {'displayName': 'x'}}email: email must be a valid email addressage: age must be greater than or equal to 18profile.displayName: displayName must be at least 2 characters long
  4. Convert valid input to a model.

    Use validateSchema with a fromJson. On success, data is the converted object. If validation fails, fromJson does not run. If fromJson throws, the result is a failure with a fromJsonFailed issue.

    bin/main.dart
    class User {
    const User({required this.email, this.age});
    final String email;
    final int? age;
    factory User.fromJson(Map<String, Object?> json) => User(
    email: json['email']! as String,
    age: json['age'] as int?,
    );
    }
    void main() {
    final result = user.validateSchema(
    {'email': '[email protected]', 'profile': {'displayName': 'Ada'}},
    fromJson: User.fromJson,
    );
    print(result);
    if (result case ValidationSuccess(:final data)) {
    print(data.email);
    }
    }
    dart run bin/main.dart
    ValidationSuccess(data: User(email: [email protected], age: null))

Writing fromJson and the schema for the same class twice is where the generator earns its place. Next: Generate a schema from a model.