Supported field types
The generator reads the constructor the serializer uses and maps each parameter to a validator by its type. Annotations add modifiers, and nullability and defaults decide .required(). This page is the mapping table.
Type mapping
Section titled “Type mapping”| Field type | Generated validator | Annotations that apply |
|---|---|---|
String |
l.string() |
All string annotations |
DateTime, DateTime? |
l.string().dateTime() |
String annotations |
Uri |
l.string() |
String annotations |
int |
l.int() |
@HasMin, @HasMax with integral values |
double |
l.double() |
@HasMin, @HasMax |
num |
l.num() |
@HasMin, @HasMax |
bool |
l.bool() |
|
| An enum | l.oneOf([...]) of the serialized values |
|
dynamic, Object, Object? |
l.any() |
|
Null |
l.nullValue() |
none; a custom validator on it fails generation |
List<T>, Set<T>, Iterable<T> |
l.list(T's validator) |
|
Map<K, V> |
l.map(keyValidator: K's, valueValidator: V's) |
|
File from dart:io, XFile from cross_file |
l.file() |
|
Any declared type with @IsFile(), including package classes such as MultipartFile |
l.file(), or l.file(accept:) when the annotation has one |
|
A @luthor model M |
forwardRef(() => $MSchema) |
|
A class with fromJson or @MappableClass and named parameters |
forwardRef(() => _$MSchema), a private auto-generated schema |
|
A field with a JsonConverter or @JsonKey(fromJson:) |
l.any() |
only the custom validator annotations |
@WithCustomValidator and @WithSchemaCustomValidator apply to every type except Null, after the type’s own modifiers.
A double field generates l.double(), which rejects a JSON integer such as 1. That mirrors the Dart type on purpose. If your backend may send 1 for a double field, type the field as num.
An enum’s serialized values come from the serializer’s rules: the case names, or @JsonValue, @JsonEnum(fieldRename:, valueField:), @MappableEnum and @MappableValue. The same applies to enums inside lists and as map keys.
enum Role { admin, member }
@Default(Role.member) Role role, // l.oneOf(["admin", "member"])required Role role, // l.oneOf(["admin", "member"]).required()json_serializable maps a value it does not know to the fallback given in @JsonKey(unknownEnumValue:), or to null with JsonKey.nullForUndefinedEnumValue, instead of throwing. Such a field accepts any value of the serialized type, so it generates l.string() instead of l.oneOf(), or l.int() for an enum whose serialized values are integers. On a list, set or iterable of an enum, this applies to each element. .required() follows the usual rules.
@JsonKey(unknownEnumValue: Role.member) required Role role, // l.string().required()@JsonKey(unknownEnumValue: Role.member) required List<Role> roles, // l.list(l.string().required()).required()Required
Section titled “Required”After the type and its modifiers, the generator appends .required() when all of these hold: the type is not nullable, the type is not dynamic, and the parameter has no default. A default is a constructor default value, a freezed @Default, or @JsonKey(defaultValue:). Dart’s required keyword is not consulted.
required String name, // l.string().required()String? nickname, // l.string()required String? note, // l.string()@Default('en') String locale, // l.string()String locale = 'en', // l.string()required dynamic payload, // l.any()Fields with @JsonKey(includeFromJson: false) are left out of the schema.
Lists, sets and maps
Section titled “Lists, sets and maps”Element validators follow the same rules as top-level fields, including .required() for non-nullable element types, .dateTime() for DateTime elements, and l.oneOf() for enum elements.
required List<String> tags, // l.list(l.string().required()).required()List<String?>? notes, // l.list(l.string())required Set<Role> roles, // l.list(l.oneOf(["admin", "member"]).required()).required()required List<Address> places, // l.list(forwardRef(() => $AddressSchema.required())).required()JSON object keys are strings, so a map key type of int, double, num or BigInt generates a string validator with a custom check that the string parses, and a key that fails is an invalidKey of the map. Enum keys generate l.oneOf() of the names as strings.
required Map<String, int> ages,// l.map(keyValidator: l.string().required(), valueValidator: l.int().required()).required()required Map<int, String> labels,// l.map(keyValidator: l.string().custom((key) => int.tryParse(key) != null).required(), ...)Nested models and recursion
Section titled “Nested models and recursion”A field typed as another @luthor model refers to that model’s schema through forwardRef(). Every nested reference is wrapped, so a model that refers to itself, directly or through a list or map, and two models that refer to each other, work without annotation. @luthorForwardRef is deprecated and has no effect; remove it.
@luthor@freezedabstract class Node with _$Node { const factory Node({ required String value, List<Node>? children, Node? parent, }) = _Node;
factory Node.fromJson(Map<String, dynamic> json) => _$NodeFromJson(json);}final SchemaValidator $NodeSchema = l .schema({ NodeSchemaKeys.value: l.string().required(), NodeSchemaKeys.children: l.list(forwardRef(() => $NodeSchema.required())), NodeSchemaKeys.parent: forwardRef(() => $NodeSchema), }) .withName("Node");$NodeValidate(input)+1-2- valid:
{'value': 'root', 'children': [{'value': 'leaf'}]} - invalid:
{'value': 'root', 'children': [{'value': 'a', 'children': [{'value': 1}]}]} - invalid:
{'value': 'root', 'parent': {'value': 2}}
Every level is validated, including parent.
Unsupported types
Section titled “Unsupported types”Generation fails with an InvalidGenerationSourceError that names the class, the field and the type, for:
freezedunions, classes with more than one redirecting factory.- Generic models, and fields whose type is a generic class or a type parameter.
- Records and function types.
- Types without a Luthor validator, such as
Durationor aBigIntvalue. Give these aJsonConverter, which turns the field intol.any(). - An annotation on a field type it does not apply to, such as
@IsEmailon anintor@HasLengthon aList.
Validators for list elements cannot be set through annotations yet.