json_entity 2.0.0
json_entity: ^2.0.0 copied to clipboard
A library for parsing JSON data into Dart objects, inspired by PHP Laravel ORM.
json_entity #
A lightweight, Laravel-inspired JSON mapping library for Dart/Flutter that avoids code generation. Define rich models using field objects (JsonString, JsonInteger, JsonDouble, JsonBoolean, JsonDate, JsonNumber, JsonObject, JsonList) and serialize/deserialize without .g.dart files.
This approach is inspired by Laravel Eloquent's implicit attribute mapping and whitelisting/blacklisting philosophy. It keeps models explicit and composable, while eliminating build-step overhead and IDE slowdowns.
- No code generation: zero builders, no watch tasks, no generated files.
- Explicit fields: models declare fields as objects with type-aware behavior.
- Nested models & lists: compose with
JsonObject<T>andJsonList<T>. - Validation metadata: per-field
error,warning,informationplus model-level collections. - Selective output: only non-null values are serialized; special handling for
id-suffixed fields.
For background and motivation, read the article: Overcome Flutter JSON drawbacks.
Installation #
Add to your pubspec.yaml:
dependencies:
json_entity: ^2.0.0
This package depends on get_it and intl.
Core concepts #
- JsonModel: base class for your models. Implements
fromJsonandtoJsonover a list of declaredfields. - JsonField: base class for fields. Concrete types provide parsing, defaults, and JSON conversion.
- GetIt: used to construct nested models when deserializing
JsonObject<T>andJsonList<T>.
Field types #
JsonString,JsonInteger,JsonDouble,JsonBoolean,JsonNumberJsonDate(string parsing, ISO output in UTC)JsonObject<T extends JsonModel>(nested model)JsonList<T extends JsonModel>(list of nested models)
All fields expose:
rawValue(nullable backing store)valuegetter (type-safe, with sensible defaults like empty string or 0)error/warning/information(metadata for validation/UX)
Quick start #
- Register your model types with
GetItso nested objects/lists can be constructed duringfromJson:
final getIt = GetIt.instance;
getIt.registerFactory<User>(() => User());
getIt.registerFactory<Address>(() => Address());
- Define models by extending
JsonModeland declaringfields:
class Address extends JsonModel {
final street = JsonString('street');
final city = JsonString('city');
final zipCode = JsonString('zipCode');
@override
List<JsonField> get fields => [street, city, zipCode];
}
class User extends JsonModel {
final id = JsonInteger('id');
final name = JsonString('name');
final email = JsonString('email');
final isActive = JsonBoolean('isActive');
final balance = JsonDouble('balance');
final createdAt = JsonDate('createdAt');
final address = JsonObject<Address>('address');
final tags = JsonList<Tag>('tags');
@override
List<JsonField> get fields => [
id, name, email, isActive, balance, createdAt,
address, tags,
];
}
class Tag extends JsonModel {
final id = JsonInteger('id');
final name = JsonString('name');
@override
List<JsonField> get fields => [id, name];
}
- Deserialize from JSON:
final user = getIt<User>();
user.fromJson({
'id': 0,
'name': 'Alice',
'email': 'alice@example.com',
'isActive': true,
'balance': '19.99',
'createdAt': '2024-08-07T12:34:56Z',
'address': {
'street': '1 Main St',
'city': 'Springfield',
'zipCode': '12345',
},
'tags': [
{'id': 1, 'name': 'pro'},
{'id': 2, 'name': 'beta'},
],
});
// Access values
afinal String name = user['name'];
final String city = user.address['city'];
final Tag firstTag = user.tags[0];
- Serialize to JSON:
final map = user.toJson();
final json = user.toString(); // jsonEncode(toJson())
Serialization rules:
- Only fields with non-null
rawValueare emitted. - Fields whose name ends with
id(case-insensitive) exceptstatusIdare omitted if theirvalueis0. - Fields use their specific
toJsonimplementations (e.g.,JsonDateoutputs UTC ISO 8601).
Working with values vs rawValue #
- Use
valueto read/write in a type-safe way. Implementations accept helpful inputs:JsonIntegerparses numeric strings.JsonDoubleacceptsint,double, numericString.JsonDateaccepts ISO string and outputs UTC ISO string.
- Use
rawValueif you need to check presence (null) vs defaulted value.
Examples:
user.balance.value = '42.5'; // stored as double
user.createdAt.value = '2024-08-07T12:34:56Z';
if (user.email.isNull) {
user.email.error = 'Email is required';
}
Validation metadata and messages #
At the model level:
generalErrors,generalWarnings,generalInformations(Lists)errors,warnings,informations(fieldName -> message)
fromJson maps these structures into matching field properties so your UI can bind per-field messages:
user.fromJson({
'errors': {
'email': 'Email already taken',
},
'warnings': {
'name': 'Nickname looks too short',
}
});
if (user.email.hasError) {
print(user.email.error);
}
Nested objects and lists #
JsonObject<T> and JsonList<T> construct nested model instances using GetIt. Register factories for all T types you nest.
getIt.registerFactory<Order>(() => Order());
getIt.registerFactory<OrderItem>(() => OrderItem());
class Order extends JsonModel {
final id = JsonInteger('id');
final items = JsonList<OrderItem>('items');
@override
List<JsonField> get fields => [id, items];
}
class OrderItem extends JsonModel {
final sku = JsonString('sku');
final qty = JsonInteger('qty');
@override
List<JsonField> get fields => [sku, qty];
}
Date handling #
JsonDate:
valuereturnsDateTime(defaults toDateTime.now()ifrawValueis null).- Accepts ISO strings on assignment and parses with
DateTime.tryParse. toJson()emitstoUtc().toIso8601String().format()usesintlDateFormat(defaultdd/MM/yyyy).
Utility extension DateTimeOffsetExtensions provides:
getTimezoneOffsetString()as±hh:mmor empty for UTC.toIso8601StringWithOffset()appends offset string totoIso8601String().
Access helpers #
- Index into models by field name:
model['name'],model['id'] = 123. - Index into objects/lists:
jsonObject['city'],jsonList[0].
Out-of-range or missing-field access throws an exception to surface errors early.
Why this approach (vs code generation)? #
- No build step: instant feedback, simpler CI, fewer moving parts.
- Lean repo: no
.g.dartfile explosion; easier navigation and diffs. - IDE performance: fewer files to index; faster search and refactors.
- Explicit mapping: fields are first-class citizens where you attach validation and transformation logic.
- Composable: nested models and lists are defined declaratively without annotations.
If you prefer annotation-based codegen, packages like json_serializable are great. This library targets teams that value runtime composition and development ergonomics akin to Laravel Eloquent.
Tips #
- Always register nested model types in
GetItbefore callingfromJson. - Use
rawValuenull checks to control serialization output. - For IDs, setting
0effectively omits the key (exceptstatusId). JsonListdefaults to an empty list and maps elements viaGetIt.
License #
MIT © Thanh Tung