d_serializer
d_serializer is a static JSON serialization library for Dart and Flutter with annotation-based code generation.
It provides a simple API:
Serializer.toJson<T>(value)Serializer.fromJson<T>(json)
And a codegen flow that avoids manual toMap/fromMap boilerplate for each model.
Features
- Annotation-based model generation with
@Serializable() - Static serializer API (
toJson/fromJson) - Registry bootstrap via generated
initializeDSerializer() - Field controls via
@JsonKey(...) - Formatter pipeline via
@Format(...) - Strict and safe deserialization options
- Support for common Dart types and nested models
Installation
dependencies:
d_serializer: ^1.0.2
dev_dependencies:
build_runner: ^2.10.3
d_serializer_builder: ^1.0.2
Quick Start
1) Create a model
import 'package:d_serializer/d_serializer.dart';
part 'user.g.dart';
@Serializable()
class User {
@JsonKey(requiredKey: true)
final int id;
@JsonKey(requiredKey: true)
final String name;
User({required this.id, required this.name});
}
2) Generate code
dart run build_runner build --delete-conflicting-outputs
3) Initialize registry once
import 'd_serializer_registry.g.dart';
void main() {
initializeDSerializer();
}
4) Serialize / deserialize
final user = User(id: 1, name: 'Abner');
final json = Serializer.toJson<User>(user);
final restored = Serializer.fromJson<User>(json);
API
Serializer
Serializer.register<T>({fromJson, toJson})Serializer.toJson<T>(value)Serializer.fromJson<T>(json)Serializer.formatDate(value, pattern)Serializer.parseDate(value, pattern)
@Serializable(...)
Class-level options:
renamediscriminatortypeFieldstrictnaming(JsonNaming.none,JsonNaming.snakeCase)
@JsonKey(...)
Field-level options:
nameignoredefaultValueconverteruseEnumIndexrequiredKeyunknownEnumValue
@Format(...)
Field formatter pipeline (applied in both toJson and fromJson).
| Formatter | Supported field types | Notes |
|---|---|---|
@Format.trim() |
String, String? |
Trims leading/trailing whitespace |
@Format.uppercase() |
String, String? |
Uppercase transformation |
@Format.lowercase() |
String, String? |
Lowercase transformation |
@Format.date('yyyy-MM-dd') |
DateTime, DateTime? |
Uses built-in formatter/parser |
@Format.date('iso8601') |
DateTime, DateTime? |
Uses toIso8601String/DateTime.parse |
@Format.custom('X') |
Any | Uses custom formatter functions |
@Format.customWith(TypeName) |
Any | Typed custom formatter, resolves by type name |
Pipeline order:
- Formatters are applied in annotation order.
- Same order is applied in
toJsonand infromJson.
Build-time validation:
- String formatters on non-string fields fail generation.
- Date formatter on non-date fields fails generation.
- Empty
@Format.custom('')fails generation. @Format.customWith(TypeName)requires a valid type literal.
@Format.custom('X') and @Format.customWith(TypeName) contract:
Define top-level functions visible in the same model library scope:
XFormatToJson(dynamic value)XFormatFromJson(dynamic value)
Complete Structured Example
A production-like sample is included in example/example.dart (self-contained).
This sample demonstrates:
- nested models
- enum fallback with
unknownEnumValue List<T>,Set<T>, andMap<String, T>@JsonKey(requiredKey, defaultValue, ignore, converter)@Format.trim()and@Format.customWith(TitleCase)@Serializable(strict, naming, typeField, discriminator)
Run the sample:
dart run build_runner build
dart run example/example.dart
Advanced Examples
Custom converter (@JsonKey(converter: 'Money'))
class Money {
final int cents;
const Money(this.cents);
}
Object? MoneyToJson(dynamic value) => (value as Money).cents;
Money MoneyFromJson(dynamic value) => Money((value as num).toInt());
@Serializable()
class Invoice {
@JsonKey(converter: 'Money')
final Money total;
Invoice({required this.total});
}
Typed custom formatter (@Format.customWith(TitleCase))
String TitleCaseFormatToJson(dynamic value) {
final input = (value as String).trim().toLowerCase();
if (input.isEmpty) return input;
return input[0].toUpperCase() + input.substring(1);
}
String TitleCaseFormatFromJson(dynamic value) {
return TitleCaseFormatToJson(value);
}
class TitleCase {
const TitleCase._();
}
@Serializable()
class Post {
@JsonKey(requiredKey: true)
@Format.customWith(TitleCase)
final String title;
Post({required this.title});
}
Discriminator support
@Serializable(typeField: 'kind', discriminator: 'order')
class OrderEvent {
final int id;
OrderEvent({required this.id});
}
Unknown key policy (strict)
@Serializable(strict: true)
class StrictModel {
final int id;
StrictModel({required this.id});
}
Supported Types
int,double,String,bool,dynamicDateTime,Uri,BigInt,DurationList<T>,Set<T>,Map<String, T>- Enums (by name or index)
- Nested
@Serializable()models
Build Workflow
Do I need to run build every time?
Not for every run.
- If you changed annotated models: regenerate.
- If models did not change: no manual regeneration needed.
Recommended during development:
dart run build_runner watch --delete-conflicting-outputs
Recommended for CI/release:
dart run build_runner build --delete-conflicting-outputs
Troubleshooting
-
Type X is not registered:- Run code generation.
- Call
initializeDSerializer()before use.
-
Converter function not found:
- Ensure
XToJson/XFromJsonexist as top-level functions and are visible in the model library.
- Ensure
-
Custom formatter function not found:
- Ensure
XFormatToJson/XFormatFromJsonexist as top-level functions and are visible in the model library.
- Ensure
-
Unknown fields error:
- Check
@Serializable(strict: true)behavior or disable strict mode.
- Check
License
MIT