JSON to Dart Class Generator

Generate Dart classes with null safety, fromJson factory, and toJson method from JSON. Perfect for Flutter app development with built-in support for copyWith, toString, and freezed.

What is JSON to Dart Class Generator?

Flutter and Dart use a manual serialization pattern: each model class must implement a fromJson(Map<String, dynamic> json) factory constructor and a toJson() method. Unlike some frameworks that use runtime reflection, Dart compiles ahead-of-time (AOT) and disallows mirrors in Flutter, so there is no built-in JSON-to-class magic. Every field mapping must be written explicitly. Dart 2.12 introduced sound null safety, which means every type is either non-nullable (String — cannot be null) or nullable (String? — can be null). Getting this wrong crashes your Flutter app: a non-nullable field receiving null from the API will throw a Null check operator used on a null value error at runtime. This generator handles null safety correctly based on the sample data, generates fromJson/toJson for the entire class hierarchy, and optionally adds copyWith (essential for Bloc/Cubit state management) and freezed boilerplate.

How to Use

  1. Set the root class name to match the Flutter model concept (e.g. "UserProfile", "ProductItem") — it becomes the outermost Dart class
  2. Enable "copyWith" to generate a copyWith method on each class — required for immutable state updates in Bloc, Cubit, or Riverpod state management patterns
  3. Enable "toString" to generate a human-readable toString() override for debugging
  4. Enable "freezed" to generate code compatible with the freezed package — adds @freezed annotations and @JsonSerializable; run flutter pub run build_runner build after pasting to generate the .g.dart and .freezed.dart files
  5. Paste your JSON (from a real API response or mock data) and click "Generate Dart Code" — copy the output into a .dart file in your Flutter project

Why Use This Tool?

Generates Dart null-safe classes (Dart 2.12+) — nullable fields are String?, non-nullable fields are String, preventing runtime null errors
fromJson factory constructors handle nested object deserialization recursively — Address.fromJson(json["address"] as Map<String, dynamic>)
toJson methods enable serialization back to JSON for API requests
Double fields use (json["key"] as num).toDouble() — a critical detail that prevents type cast exceptions when the API sends 1 instead of 1.0
copyWith method enables the immutable state update pattern used by Bloc/Cubit: state.copyWith(isLoading: false)
Runs entirely in the browser — your API response data never leaves your device

Tips & Best Practices

  • The (json["key"] as num).toDouble() pattern is important for double fields — Dart JSON parsing sometimes gives int where you expect double (e.g. a price of 10 instead of 10.0). Without this cast, the app throws a type 'int' is not a subtype of type 'double' error
  • For Flutter Bloc/Cubit, always generate copyWith — bloc pattern requires creating a new state instance with modified fields, and copyWith makes this one-liner clean: emit(state.copyWith(user: updatedUser))
  • The freezed package generates additional features: value equality (== compares by value, not reference), pattern matching on sealed classes, and union types — worth adding for complex models with multiple variants
  • For Riverpod + freezed, add the @riverpod annotation to your provider and run build_runner once to generate everything — the generated Dart class is the same base
  • Dart's json_serializable package is an alternative to manual fromJson/toJson — it uses @JsonSerializable() and code generation, producing the same result as this manual output but with less maintenance for large models

Frequently Asked Questions

What is the complete JSON type → Dart type mapping?

JSON string → String (non-nullable if never null in sample). JSON integer → int. JSON float → double (via (num).toDouble() cast). JSON boolean → bool. JSON null → T? (nullable version of the inferred type). JSON array → List<T> with T inferred from the first element. JSON object → a separate named Dart class with its own fromJson/toJson. Heterogeneous arrays → List<dynamic>.

Why does Dart need fromJson/toJson instead of automatic JSON parsing?

Flutter compiles ahead-of-time (AOT) and tree-shakes unused code. Runtime reflection (like Python's json module or Java's Jackson without annotations) requires the runtime to inspect class metadata — which AOT compilation removes. Dart's solution is either manual fromJson/toJson methods (what this generator produces) or code generation via build_runner + json_serializable / freezed, which generates the fromJson/toJson at build time.

What is the copyWith method and why is it important for Flutter?

copyWith creates a new instance of the class with specified fields replaced. In Flutter, state objects must be immutable — you never mutate a Bloc state directly, you emit a new one. copyWith makes this concise: emit(state.copyWith(isLoading: false, user: freshUser)) instead of creating the full object from scratch. The generated copyWith takes all fields as optional named parameters, defaulting to the current value if not provided.

What is freezed and when should I use it?

freezed is a Dart code generation package that adds: (1) value equality — two instances with the same fields are == equal; (2) copyWith generation; (3) sealed union types with pattern matching. Use freezed for complex state models in Bloc (multiple states like Loading/Success/Error), for API response models that need equality comparison, and for models shared across the app. Add the freezed and freezed_annotation packages, then run flutter pub run build_runner build.

Is my data sent to a server?

No. All code generation runs entirely in your browser. Your JSON data never leaves your device.

Real-world Examples

Flutter e-commerce product model with Bloc state management

A product detail page in Flutter fetches a product from an API and stores it in a Bloc state. The generated Dart class with copyWith enables clean state emissions: emit(state.copyWith(product: fetchedProduct, isLoading: false)). Note the (json["price"] as num).toDouble() pattern for the price field.

Input
{
  "productId": "prod-5512",
  "name": "Mechanical Keyboard",
  "price": 89.99,
  "rating": 4.5,
  "reviewCount": 128,
  "inStock": true,
  "imageUrl": "https://cdn.example.com/keyboards/5512.jpg",
  "category": "Electronics",
  "tags": ["keyboard", "mechanical", "gaming"]
}
Output
class Product {
  final String productId;
  final String name;
  final double price;
  final double rating;
  final int reviewCount;
  final bool inStock;
  final String imageUrl;
  final String category;
  final List<String> tags;

  const Product({
    required this.productId,
    required this.name,
    required this.price,
    required this.rating,
    required this.reviewCount,
    required this.inStock,
    required this.imageUrl,
    required this.category,
    required this.tags,
  });

  factory Product.fromJson(Map<String, dynamic> json) => Product(
    productId: json['productId'] as String,
    name: json['name'] as String,
    price: (json['price'] as num).toDouble(),
    rating: (json['rating'] as num).toDouble(),
    reviewCount: json['reviewCount'] as int,
    inStock: json['inStock'] as bool,
    imageUrl: json['imageUrl'] as String,
    category: json['category'] as String,
    tags: (json['tags'] as List).map((e) => e as String).toList(),
  );

  Map<String, dynamic> toJson() => {
    'productId': productId,
    'name': name,
    'price': price,
    'rating': rating,
    'reviewCount': reviewCount,
    'inStock': inStock,
    'imageUrl': imageUrl,
    'category': category,
    'tags': tags,
  };

  Product copyWith({
    String? productId, String? name, double? price, double? rating,
    int? reviewCount, bool? inStock, String? imageUrl,
    String? category, List<String>? tags,
  }) => Product(
    productId: productId ?? this.productId,
    name: name ?? this.name,
    price: price ?? this.price,
    rating: rating ?? this.rating,
    reviewCount: reviewCount ?? this.reviewCount,
    inStock: inStock ?? this.inStock,
    imageUrl: imageUrl ?? this.imageUrl,
    category: category ?? this.category,
    tags: tags ?? this.tags,
  );
}

Nullable nested location in a ride-sharing response

When a driver has not yet accepted a ride request, the driver location is null. The Dart nullable type DriverLocation? means the UI can show a loading placeholder without a null check crash.

Input
{
  "rideId": "ride_8821",
  "status": "searching",
  "estimatedArrival": null,
  "driverLocation": null,
  "pickupAddress": "123 Main St, San Francisco, CA"
}
Output
class DriverLocation {
  const DriverLocation();
  factory DriverLocation.fromJson(Map<String, dynamic> json) =>
      const DriverLocation();
  Map<String, dynamic> toJson() => {};
}

class RideRequest {
  final String rideId;
  final String status;
  final String? estimatedArrival;
  final DriverLocation? driverLocation;
  final String pickupAddress;

  const RideRequest({
    required this.rideId,
    required this.status,
    this.estimatedArrival,
    this.driverLocation,
    required this.pickupAddress,
  });

  factory RideRequest.fromJson(Map<String, dynamic> json) => RideRequest(
    rideId: json['rideId'] as String,
    status: json['status'] as String,
    estimatedArrival: json['estimatedArrival'] as String?,
    driverLocation: json['driverLocation'] == null
        ? null
        : DriverLocation.fromJson(json['driverLocation'] as Map<String, dynamic>),
    pickupAddress: json['pickupAddress'] as String,
  );
}

Related Tools