rj_safe_parser 0.4.0 copy "rj_safe_parser: ^0.4.0" to clipboard
rj_safe_parser: ^0.4.0 copied to clipboard

RJ Safe Parser: Annotation-based Dart generator for JSON parsing, mapping, and validation with zero boilerplate.

rj_safe_parser #

pub version likes pub points license

One annotation on the class. Zero per-field boilerplate.

rj_safe_parser is a Dart code-generation package inspired by Spring Boot's @Entity. Place @RjSafeParsable() once on a class, run build_runner, and get a fully type-safe fromMap() / toMap() with smart coercion for free — no per-field annotations, no manual schema, no handwritten converters.


Why rj_safe_parser? #

APIs in the real world rarely send perfectly typed JSON. You get "7" instead of 7, 1 instead of true, a Unix timestamp instead of an ISO string. Every other parser either crashes or forces you to write field-by-field conversion code.

rj_safe_parser reads your existing Dart field types and generates all the boilerplate automatically — including smart coercion that handles messy real-world data without any extra configuration.

// Dirty API response — wrong types everywhere
final json = {
  'id':        '7',           // String  → coerced to int
  'score':     '9.8',         // String  → coerced to double
  'isActive':  1,             // int     → coerced to bool
  'createdAt': 1712620800,    // int     → coerced to DateTime
  'address':   {'city': 'Dhaka', 'zip': '1212'}, // zip String → int
};

// One line. Just works.
final user = RjUserModel.fromMap(json);

Features #

  • One annotation — no @JsonKey, @HiveField, or per-field decoration needed
  • Smart coercion'7'int, 1bool, Unix timestamp → DateTime
  • @RjKey — map JSON keys to Dart fields (e.g. snake_case JSON → camelCase Dart)
  • Nested models — auto-detected when also annotated with @RjSafeParsable()
  • ListsList<String>, List<MyModel> — all handled automatically
  • Map<String, V>Map<String, int>, Map<String, bool> — values coerced automatically
  • Enums — by-name or by-index (@RjEnum(byIndex: true))
  • Nullable fieldsString? = optional field, absent key safely yields null
  • Strict mode — optionally reject unknown keys for hard API validation
  • Dot-path errors — exceptions include the full field path (e.g. address.zip)
  • Round-trip safetoMap() serialises back to the original shape
  • Pure Dart — no dart:mirrors, no Flutter dependency, fully tree-shakeable

vs. json_serializable #

json_serializable rj_safe_parser
Annotations per model 1 class + N fields 1 class only
Wrong types (e.g. "7" for int) ❌ throws ✅ coerced automatically
Unix timestamp → DateTime manual converter ✅ built-in
1 / 0bool manual converter ✅ built-in
Enums @JsonKey(unknownEnumValue:…) @RjEnum() or @RjEnum(byIndex:true)
Map<String, V> @JsonKey(fromJson:…) ✅ automatic value coercion
JSON key mapping @JsonKey(name: ...) @RjKey(...) or .snakeCase
Add/rename a field update class + annotation update class only
Nested models @JsonSerializable() on each @RjSafeParsable() on each
Strict unknown-key mode strict: true

Installation #

Add to your pubspec.yaml:

dependencies:
  rj_safe_parser: ^0.4.0

dev_dependencies:
  build_runner: ^2.4.0

Then run:

dart pub get

Quick start #

1. Annotate your class #

import 'package:rj_safe_parser/rj_safe_parser.dart';
part 'rj_user_model.g.dart'; // ← generated by build_runner

@RjSafeParsable()
class RjUserModel {
  final int id;
  final String name;
  final String? nickname;             // nullable → absent key returns null
  final double score;
  final bool isActive;
  final DateTime createdAt;
  final Uri profileUrl;
  final RjAddressModel address;       // nested @RjSafeParsable class
  final List<String> tags;
  final List<RjAddressModel> history;

  const RjUserModel({
    required this.id,
    required this.name,
    this.nickname,
    required this.score,
    required this.isActive,
    required this.createdAt,
    required this.profileUrl,
    required this.address,
    required this.tags,
    required this.history,
  });

  factory RjUserModel.fromMap(Map<String, dynamic> map) =>
      _$RjUserModelFromMap(map);

  Map<String, dynamic> toMap() => _$RjUserModelToMap(this);
}

2. Annotate nested models the same way #

import 'package:rj_safe_parser/rj_safe_parser.dart';
part 'rj_address_model.g.dart';

@RjSafeParsable()
class RjAddressModel {
  final String city;
  final int zip;

  const RjAddressModel({required this.city, required this.zip});

  factory RjAddressModel.fromMap(Map<String, dynamic> map) =>
      _$RjAddressModelFromMap(map);

  Map<String, dynamic> toMap() => _$RjAddressModelToMap(this);
}

3. Run the generator #

dart run build_runner build

For continuous generation during development:

dart run build_runner watch

If you see conflicts from a previous run:

dart run build_runner build --delete-conflicting-outputs

4. Use it #

final user = RjUserModel.fromMap(dirtyJson);

print(user.id);           // 7        (int, coerced from '7')
print(user.isActive);     // true     (bool, coerced from 1)
print(user.createdAt);    // 2024-04-09 ... (DateTime from Unix ts)
print(user.address.city); // Dhaka

// Round-trip back to map
final map = user.toMap();

Supported type coercions #

Dart type Accepted raw values
int int, double (truncated), bool (0/1), numeric String
double double, int, numeric String
bool bool, int (0/1), String (true / false / yes / no / 1 / 0)
String any value — .toString() is called
DateTime DateTime, Unix int/double (seconds or milliseconds), ISO-8601 String
Uri Uri, any String
Enum .name String (default), or int index when @RjEnum(byIndex: true)
Map<String, V> Map — each value coerced to V; missing key throws unless nullable
List<T> List — each element is coerced to T; missing key throws unless List<T>?
NestedModel Map<String, dynamic> — when annotated with @RjSafeParsable()

Note: Required (non-nullable) List fields now throw RjParseException when the key is missing. Previously they silently returned []. Use List<T>? if you want to accept a missing key gracefully.


Annotation options #

@RjSafeParsable(
  strict: true,   // Reject unknown keys — throws RjParseException (default: false)
)
class MyModel { ... }

strict: false (default) — unknown keys in the source map are ignored with a warning in RjParseResult.warnings. Useful for APIs that add fields over time.

strict: true — unknown keys throw RjParseException. Useful when you want to enforce an exact contract.


@RjKey — JSON key mapping #

When the JSON key differs from the Dart field name, use @RjKey on the field:

@RjSafeParsable()
class Photo {
  final String id;
  final String author;

  @RjKey('download_url')
  final String downloadUrl;

  Photo({required this.id, required this.author, required this.downloadUrl});

  factory Photo.fromMap(Map<String, dynamic> map) => _$PhotoFromMap(map);
  Map<String, dynamic> toMap() => _$PhotoToMap(this);
}

The generated code reads from 'download_url' in JSON, stores it in downloadUrl, and serialises back to 'download_url' in toMap().

Automatic snake_case conversion #

Instead of specifying the key manually, use RjKey.snakeCase() for automatic conversion (downloadUrldownload_url):

@RjKey.snakeCase()
final String downloadUrl;

@RjEnum — enum serialisation #

Dart enum fields are supported out of the box. By default the JSON value is matched against the enum constant's .name string:

enum Status { active, inactive, pending }

@RjSafeParsable()
class Order {
  @RjEnum()                     // 'active' → Status.active
  final Status status;
}

Use @RjEnum(byIndex: true) to match by ordinal position instead:

enum Priority { low, medium, high }

@RjSafeParsable()
class Task {
  @RjEnum(byIndex: true)        // 0 → Priority.low, 2 → Priority.high
  final Priority priority;
}

Nullable enum fields (Status?) work as expected — absent key or explicit null yields null. toMap() serialises back to .name (default) or .index (when byIndex: true).

If the annotation is omitted from an enum field the generator defaults to by-name matching — @RjEnum() is optional when byIndex: false.


Error handling #

All parse errors throw RjParseException with a dot-notation field path:

try {
  final user = RjUserModel.fromMap(badJson);
} on RjParseException catch (e) {
  print(e); // RjParseException at "address.zip": Cannot coerce "abc" → int
}

License #

MIT — see LICENSE

0
likes
130
points
8
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

RJ Safe Parser: Annotation-based Dart generator for JSON parsing, mapping, and validation with zero boilerplate.

Repository (GitHub)
View/report issues

Topics

#codegen #json #serialization #build-runner #annotation

License

MIT (license)

Dependencies

analyzer, build, meta, source_gen

More

Packages that depend on rj_safe_parser