schemantic 0.0.1-dev.7
schemantic: ^0.0.1-dev.7 copied to clipboard
A general-purpose builder for generating type-safe data classes and JSON schemas from abstract definitions.
Schemantic #
A general-purpose Dart library for generating type-safe data classes and runtime-accessible JSON Schemas from abstract class definitions.
Features #
- Type-Safe Data Classes: Generates fully-typed Dart data classes from simple abstract definitions.
- Runtime JSON Schema: Access the standard JSON Schema for any generated type at runtime.
- Serialization: Built-in
toJsonandparsemethods. - Validation: Validate JSON data against the generated schema at runtime.
- Recursive Schemas: Easy support for recursive data structures (e.g., trees) using
$ref.
Installation #
Add schemantic and build_runner to your pubspec.yaml:
dart pub add schemantic
dart pub add dev:build_runner
Usage #
1. Define your specific Schema #
Create a Dart file (e.g., user.dart) and define your schema as an abstract class annotated with @Schematic().
import 'package:schemantic/schemantic.dart';
part 'user.schema.g.dart';
@Schematic()
abstract class UserSchema {
String get name;
int? get age;
bool get isAdmin;
}
2. Generate Code #
Run the build runner to generate the implementation:
dart run build_runner build
This will generate a user.schema.g.dart file containing:
User: The concrete data class.UserType: A utility class for parsing, schema access, and validation.
3. Use the Generated Types #
You can now use the generated User class and UserType utility:
void main() async {
// Create an instance using the generated class
final user = User.from(
name: 'Alice',
age: 30,
isAdmin: true,
);
// Serialize to JSON
print(user.toJson());
// Output: {name: Alice, age: 30, isAdmin: true}
// Parse from JSON
final parsed = UserType.parse({
'name': 'Bob',
'isAdmin': false,
});
print(parsed.name); // Bob
// Access JSON Schema at runtime
final schema = UserType.jsonSchema();
print(schema.toJson());
// Output: {type: object, properties: {name: {type: string}, ...}, required: [name, isAdmin]}
// Validate data
final validation = await schema.validate({'name': 'Charlie'}); // Missing 'isAdmin'
if (validation.isNotEmpty) {
print('Validation errors: $validation');
}
}
Advanced #
Recursive Schemas #
For recursive structures like trees, use the useRefs: true option when generating the schema. This utilizes JSON Schema $ref to handle recursion.
@Schematic()
abstract class NodeSchema {
String get id;
List<NodeSchema>? get children;
}
void main() {
// Must use useRefs: true for recursive schemas
final schema = NodeType.jsonSchema(useRefs: true);
print(schema.toJson());
// Generates schema with "$ref": "#/$defs/Node"
}
Basic & Dynamic Types #
Schemantic provides a set of basic types and helpers for creating dynamic schemas without generating code.
Primitives
stringType()intType()doubleType()boolType()voidType()
listType and mapType
You can create strongly typed Lists and Maps dynamically:
void main() {
// Define a List of Strings
final stringList = listType(stringType());
print(stringList.parse(['a', 'b'])); // ['a', 'b']
// Define a Map with String keys and Integer values
final scores = mapType(stringType(), intType());
print(scores.parse({'Alice': 100, 'Bob': 80})); // {'Alice': 100, 'Bob': 80}
// Nesting types
final matrix = listType(listType(intType()));
print(matrix.parse([[1, 2], [3, 4]])); // [[1, 2], [3, 4]]
// JSON Schema generation works as expected
print(scores.jsonSchema().toJson());
// {type: object, additionalProperties: {type: integer}}
}
## Schema Metadata
You can add a description to your generated schema using the `description` parameter in `@Schematic`:
```dart
@Schematic(description: 'Represents a user in the system')
abstract class UserSchema {
// ...
}
Enhanced Collections #
You can use listType and mapType to create collections with metadata and validation:
// A list of strings with description and size constraints.
final tags = listType(
stringType(),
description: 'A list of tags',
minItems: 1,
maxItems: 10,
uniqueItems: true,
);
// A map with integer values.
final scores = mapType(
stringType(),
intType(),
description: 'Player scores',
minProperties: 1,
);
Basic Types #
Schemantic provides factories for basic types with optional metadata:
stringType({String? description, int? minLength, ...})intType({String? description, int? minimum, ...})doubleType({String? description, double? minimum, ...})boolType({String? description})dynamicType({String? description})
Example:
final age = intType(
description: 'Age in years',
minimum: 0,
);
Customizing Fields #
You can use specialized annotations to apply JSON Schema constraints directly to your Dart fields.
@Field: Basic customization (name, description).@StringField: Constraints for strings (minLength, maxLength, pattern, format, enumValues).@IntegerField: Constraints for integers (minimum, maximum, multipleOf).@NumberField: Constraints for doubles/numbers (minimum, maximum, multipleOf).
@Schematic()
abstract class UserSchema {
// Map 'age' to 'years_old' in JSON, and add validation
@IntegerField(
name: 'years_old',
description: 'Age of the user',
minimum: 0,
maximum: 120
)
int? get age;
@StringField(
minLength: 2,
maxLength: 50,
pattern: r'^[a-zA-Z\s]+$',
enumValues: ['user', 'admin'] // Mapped to 'enum' in JSON Schema
)
String get role;
}
Validation matches the Dart type (e.g., using @StringField on an int getter will throw a build-time error).