Skip to content

Dart ​

Three projectors, three code-generation conventions for null-safe Dart 3. Dart has no nested-class construct, so in every variant a nested object/enum field hoists to its own top-level sibling declaration.

json_serializable ​

ts
import { toDart } from "@rhi-zone/fractal-type-ir/dart-json-serializable"

toDart(t(types.object({
  id: t(types.string),
  name: t(types.string),
  email: t(types.string),
  age: opt(t(types.integer)),
})), "User")
dart
class User {
  final String id;

  final String name;

  final String email;

  final int? age;


  User({required this.id, required this.name, required this.email, this.age});

  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'] as String,
    name: json['name'] as String,
    email: json['email'] as String,
    age: json['age'] == null ? null : json['age'] as int,
  );

  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
    if (age != null) 'age': age,
  };
}

The fromJson/toJson bodies are hand-reproduced inline rather than generated by build_runner — this projector has no codegen pass of its own to hook into, so the output is a standalone, already-working module.

freezed ​

ts
import { toFreezed } from "@rhi-zone/fractal-type-ir/dart-freezed"

toFreezed(t(types.object({ id: t(types.string) })), "Item")

Unlike json_serializable's standalone output, this emits the annotated declaration a real project runs build_runner over — copyWith, deep equality, and JSON (de)serialization all come from freezed's generated mixin, not hand-rolled code:

dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'item.freezed.dart';
part 'item.g.dart';

@freezed
abstract class Item with _$Item {
  const factory Item({
    required String id,
  }) = _Item;

  factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
}

built_value ​

ts
import { toBuiltValue } from "@rhi-zone/fractal-type-ir/dart-built-value"

toBuiltValue(t(types.object({ id: t(types.string) })), "Item")

Fields are abstract getters, not constructor parameters — built_value's build.yaml/build_runner pass derives the mutable ItemBuilder from the getter signatures:

dart
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
part 'item.g.dart';

abstract class Item implements Built<Item, ItemBuilder> {

  String get id;

  static Serializer<Item> get serializer => _$itemSerializer;

  Item._();
  factory Item([void Function(ItemBuilder) updates]) = _$Item;
}