--- name: schemantic description: Instructions and guidelines for using the Schemantic library for type-safe data classes and schemas in Dart. --- # Schemantic Schemantic is a general-purpose Dart library used for defining strongly typed data classes that automatically bind to reusable runtime JSON schemas. It is standard for the `genkit-dart` framework but works independently as well. ## Core Concepts Always use `schemantic` when strongly typed JSON parsing or programmatic schema validation is required. - Annotate your abstract classes with `@Schematic()`. - Use the `$` prefix for abstract schema class names (e.g., `abstract class $User`). - Always run `dart run build_runner build` to generate the `.g.dart` schema files. ## Basic Usage 1. **Defining a schema:** ```dart import 'package:schemantic/schemantic.dart'; part 'my_file.g.dart'; // Must match the filename @Schematic() abstract class $MyObj { String get name; $MySubObj get subObj; } @Schematic() abstract class $MySubObj { String get foo; } ``` 2. **Using the Generated Class:** The builder creates a concrete class `MyObj` (no `$`) with a factory constructor (`MyObj.fromJson`) and a regular constructor. ```dart // Creating an instance final obj = MyObj(name: 'test', subObj: MySubObj(foo: 'bar')); // Serializing to JSON print(obj.toJson()); // Parsing from JSON final parsed = MyObj.fromJson({'name': 'test', 'subObj': {'foo': 'bar'}}); ``` 3. **Accessing Schemas at Runtime:** The generated data classes have a static `$schema` field (of type `SchemanticType`) which can be used to pass the definition into functions or to extract the raw JSON schema. ```dart // Access JSON schema final schema = MyObj.$schema.jsonSchema; print(schema.toJson()); // Validate arbitrary JSON at runtime final validationErrors = await schema.validate({'invalid': 'data'}); ``` ## Primitive Schemas When a full data class is not required, Schemantic provides functions to create schemas dynamically. ```dart final ageSchema = intSchema(description: 'Age in years', minimum: 0); final nameSchema = stringSchema(minLength: 2); final nothingSchema = voidSchema(); final anySchema = dynamicSchema(); final userSchema = mapSchema(stringSchema(), intSchema()); // Map final tagsSchema = listSchema(stringSchema()); // List ``` ## Union Types (AnyOf) To allow a field to accept multiple types, use `@AnyOf`. ```dart @Schematic() abstract class $Poly { @AnyOf([int, String, $MyObj]) Object? get id; } ``` Schemantic generates a specific helper class (e.g., `PolyId`) to handle the values: ```dart final poly1 = Poly(id: PolyId.int(123)); final poly2 = Poly(id: PolyId.string('abc')); ``` ## Field Annotations You can use specialized annotations for more validation boundaries: ```dart @Schematic() abstract class $User { @IntegerField( name: 'years_old', // Change JSON key description: 'Age of the user', minimum: 0, defaultValue: 18, ) int? get age; @StringField( minLength: 2, enumValues: ['user', 'admin'], ) String get role; } ``` ## Recursive Schemas For recursive structures (like trees), must use `useRefs: true` inside the generated jsonSchema property. You define it normally: ```dart @Schematic() abstract class $Node { String get id; List<$Node>? get children; } ``` *Note*: `Node.$schema.jsonSchema(useRefs: true)` generates schemas with JSON Schema `$ref`.