HomeJSON ConvertersJSON to Dart Class Generator

JSON to Dart Class Generator

Convert any JSON object into null-safe Dart classes with fromJson and toJson factory methods, correct Dart type mappings, nullable type handling, nested class decomposition, and immutable final fields — ready to paste into your Flutter or Dart project.

Convert any JSON object into null-safe Dart classes with fromJson and toJson factory methods, correct Dart type mappings, nullable type handling, nested class decomposition, and immutable final fields — ready to paste into your Flutter or Dart project.

This tool is designed to provide a seamless experience for developers by handling complex operations directly in your browser with maximum speed and security.

100% Private
Instant Results
Customizable
Offline Ready
Dev-Friendly
Easy Export

Dart introduced sound null safety in Dart 2.12 (released with Flutter 2.0 in March 2021). Sound null safety means that variables cannot be null unless you explicitly declare them as nullable — a String can never be null, but a String? can. The question mark suffix is the nullable type modifier. This system eliminates an entire class of null pointer exceptions at compile time rather than runtime. When your app compiles with null safety enabled, the Dart analyzer guarantees that anywhere you use a non-nullable type, the value cannot possibly be null — no defensive null checks scattered through your UI code, no null pointer crashes on production devices when an API returns unexpected null values.

The practical consequence for working with REST APIs in Flutter is significant. JSON responses from APIs can have fields that are sometimes null, sometimes absent, or sometimes the wrong type — and the Dart type system requires you to declare upfront which fields are nullable. A model class is the bridge between the weakly-typed JSON world and the strongly-typed Dart world. It defines exactly what fields the JSON is expected to have, what type each field must be, and which fields can be null. The fromJson factory constructor converts a Map (what dart:convert's json.decode() returns) into a typed Dart object. The toJson method converts the object back to a map for re-serialization. This pattern is so ubiquitous in Flutter development that there are code generation tools (json_serializable, freezed) that generate it automatically from class annotations.

Writing these classes by hand is correct but repetitive. A response with 15 fields means 15 field declarations, a fromJson with 15 map lookups and type casts, a toJson with 15 map entries, and possibly a copyWith method for immutable updates. For a Flutter app that calls several API endpoints, you might have 10–15 model classes to write before you can even start building UI. This tool generates that boilerplate in seconds.

Read the Full Guide

Give it any JSON object and it produces a null-safe Dart class with final fields, a required-parameter constructor, a fromJson factory constructor, and a toJson method. Type mapping: JSON strings become String, integers become int, floats become double, booleans become bool, JSON arrays become List with the element type inferred, JSON null values become the nullable version of the inferred type (String?, int?, bool?), and nested JSON objects become separate named Dart classes. The fromJson constructor uses the pattern factory ClassName.fromJson(Map json) and casts each field from the map with the correct Dart type: json['fieldName'] as String for required strings, json['fieldName'] as String? for nullable strings, and (json['fieldName'] as List).map((e) => NestedClass.fromJson(e)).toList() for lists of objects. The toJson method returns a Map with each field mapped back to its JSON key. Both methods handle null fields correctly — nullable fields are included with null values in toJson rather than being omitted. The generated class uses final fields and a required positional or named parameter constructor, which is the conventional immutable model pattern in Flutter. Using final fields means the object cannot be modified after construction, which plays well with Flutter's state management patterns — you create a new object rather than mutating an existing one. If your project uses the Provider, Riverpod, or BLoC pattern for state management, the immutable model class is the foundational data type that flows through the widget tree. Nested objects each get their own class definition in the output, with the outermost class at the bottom and the inner classes defined first so the file compiles top-to-bottom without forward reference issues.

1. Paste your JSON into the Input JSON field — use a real API response sample that includes all the fields you need. If some fields are sometimes null in the API, try to include a sample where they have actual values so the tool can infer the correct type for the nullable version. Click Load Example to see a complete nested JSON structure first.

2. Click Convert to Dart Class — the tool parses every field, maps it to a null-safe Dart type, generates the class with fromJson and toJson, creates separate classes for nested objects, and outputs the complete Dart code in the result panel.

3. Rename the generated class from the auto-generated placeholder to a meaningful name matching your domain — User, Product, OrderResponse, GitHubRepo. Class names in Dart are PascalCase and appear in your widget tree, provider declarations, and repository layer. Choose names that make the code readable without needing comments.

4. Review each nullable field (the ones with ? suffix) and decide whether the nullability is correct for your use case — fields the API genuinely sometimes omits should stay nullable; fields the API always sends should be made non-null by removing the ?. Making a field non-null when it can actually be null from the API gives you a cleaner codebase but a potential runtime cast exception. Nullable fields require null-aware operators (?., ??, !) throughout your widget code, so be deliberate.

5. Copy the generated class and paste it into a new file in your models or data/models directory — follow your project's naming convention (user.dart, user_model.dart, user_response.dart). Add the file to your project and use it: final user = User.fromJson(jsonDecode(responseBody) as Map). If you are using Dio, the response data is already a Map so you can use User.fromJson(response.data).

Every Flutter developer who has built an app that calls a REST API has spent time writing these model classes. It is not intellectually demanding — you look at the JSON, write the field, write the cast in fromJson, write the entry in toJson, repeat 20 times. The opportunity for mistakes is high precisely because it is mechanical: a wrong type cast (casting an int field as String), a misspelled JSON key ('userName' vs 'username'), forgetting to handle a nested object and leaving it as dynamic — all of these compile fine but fail at runtime when real data flows through. This tool generates the class from the actual JSON structure, so the field names and types match what the API actually returns rather than what you remembered while writing the class. The nullable field question in Flutter is subtler than it sounds. When an API returns a user profile, some fields are genuinely optional — a biography field that the user may or may not have filled in, a profile picture URL that might not exist yet. Other fields are nominally present but occasionally return null in edge cases — a username that the API docs say is required but your backend sends null for during account creation. And some fields look optional in JSON (they can be null or absent) but your UI code assumes they are always present. Treating the third category as nullable forces null checks throughout your UI — treating it as non-nullable gives you a runtime crash if the API ever sends null. Getting this right before you have a production crash requires reading the API documentation carefully. After generating the class with this tool, review each nullable field and make an active decision about its nullability rather than defaulting to the tool's inference. One specific use case worth highlighting: when you use the http package or dio to call an API and then json.decode() the response, you get back a dynamic value. Passing that through MyModel.fromJson(jsonDecode(responseBody)) is the standard Flutter pattern and it gives you a typed Dart object from that point forward. The model class is the boundary where untyped JSON becomes typed Dart — and this tool builds that boundary for you.

Null-safe Dart 2.12+ code — generated classes use sound null safety with correct nullable String? and non-nullable String fields matching Dart's null safety system

fromJson and toJson included — both factory constructor and serialization method are generated with correct type casts for every field including nested objects and lists

Nested class decomposition — separate Dart class definitions are generated for nested JSON objects with inner classes defined before outer classes for top-to-bottom compilation

Immutable final fields — all fields are final and the constructor uses required named parameters matching the idiomatic Dart immutable model pattern used with Flutter state management

Correct type casting — numeric fields use (json['field'] as num).toDouble() for doubles to handle APIs that return integers for float fields avoiding runtime cast exceptions

List deserialization handled — JSON arrays generate the correct (json['field'] as List).map((e) => e as Type).toList() pattern with element type inferred

100% browser-based — your JSON API responses never leave your machine whether they contain user data internal API structures or proprietary data models

Instant generation — all type inference and code generation runs in your browser with no round-trip; results appear immediately

Generating Dart model classes for Flutter apps that call REST APIs using the http package or Dio

Creating model classes for Flutter state management with Provider Riverpod or BLoC that require typed data objects

Bootstrapping Dart data models for new Flutter features that consume a new API endpoint

Generating model classes for Dart backend projects built with Dart Frog or Shelf that receive JSON request bodies

Creating Dart classes for Firebase Firestore document models where documents are stored and retrieved as JSON-like maps

Generating model types for Flutter apps that parse JSON from local assets or bundled configuration files

Creating Dart classes when migrating a React Native or Flutter app feature that needs typed API response models

Learning the Dart null-safe fromJson toJson pattern by seeing it generated for different JSON structures

Example Input

{
  "id": 1,
  "name": "Priya Singh",
  "email": "priya@learnhubly.com",
  "isActive": true,
  "score": 98.5,
  "tags": ["developer", "admin"],
  "profile": {
    "bio": "Principal Software Engineer",
    "skills": ["Go", "React", "TypeScript"]
  },
  "deletedAt": null
}

Example Output

class Profile {
  final String bio;
  final List skills;

  Profile({
    required this.bio,
    required this.skills,
  });

  factory Profile.fromJson(Map json) => Profile(
        bio: json['bio'] as String,
        skills: (json['skills'] as List).map((e) => e as String).toList(),
      );

  Map toJson() => {
        'bio': bio,
        'skills': skills,
      };
}

class User {
  final int id;
  final String name;
  final String email;
  final bool isActive;
  final double score;
  final List tags;
  final Profile profile;
  final String? deletedAt;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.isActive,
    required this.score,
    required this.tags,
    required this.profile,
    this.deletedAt,
  });

  factory User.fromJson(Map json) => User(
        id: json['id'] as int,
        name: json['name'] as String,
        email: json['email'] as String,
        isActive: json['isActive'] as bool,
        score: (json['score'] as num).toDouble(),
        tags: (json['tags'] as List).map((e) => e as String).toList(),
        profile: Profile.fromJson(json['profile'] as Map),
        deletedAt: json['deletedAt'] as String?,
      );

  Map toJson() => {
        'id': id,
        'name': name,
        'email': email,
        'isActive': isActive,
        'score': score,
        'tags': tags,
        'profile': profile.toJson(),
        'deletedAt': deletedAt,
      };
}

Null Safety Configuration: The generated code targets Dart 2.12+ null safety. If your project has not been migrated to null safety (pubspec.yaml environment sdk is below '>=2.12.0'), the nullable type syntax (String?, int?) will cause compile errors. Update your sdk constraint to '>=2.12.0 <4.0.0' and run dart migrate to migrate your project. New Flutter and Dart projects created after Flutter 2.0 have null safety enabled by default.

Invalid JSON Input: The tool requires valid JSON to generate Dart classes. Syntax errors in the JSON — unquoted keys, trailing commas, single quotes — will prevent generation. Paste your JSON into the JSON Formatter and Validator first to fix any errors, then try the conversion again.

Type Cast Failures at Runtime on Malformed API Responses: The generated fromJson uses direct type casts like json['name'] as String. If the API sends a different type than expected — an integer where a string is expected, null where a non-null value is expected — the cast throws a TypeError at runtime. The type system cannot protect you from APIs that violate their own contracts. For production apps consuming third-party APIs, consider adding null checks and type guards in fromJson: (json['name'] as String?) ?? '' for fields that might unexpectedly be null, or using a try-catch around the entire fromJson when parsing potentially malformed data.

Nested Object JSON Key Different from Generated Class Name: The generated nested class name comes from the JSON field name — a 'profile' field generates a Profile class. If your project already has a Profile class with different fields, the generated class will conflict. Rename the generated class before adding it to your project. Check for naming conflicts in your existing models directory before pasting generated code.

json.decode() Returns dynamic, Not Map: The dart:convert json.decode() function returns dynamic. Passing it directly to fromJson without casting causes a type error at compile time because fromJson expects Map. Cast explicitly: User.fromJson(json.decode(responseBody) as Map). With Dio, response.data is already typed as dynamic but is a Map at runtime — cast it the same way. With the http package, response.body is a String, so use json.decode(response.body) as Map.

Not adding copyWith for use with Flutter state management

Fix: The generated class uses final fields which means you cannot modify fields after construction — you must create a new object to represent changed state. This is the right approach for Flutter state management (Provider, Riverpod, BLoC), but it requires a copyWith method to create modified copies conveniently: User copyWith({String? name, String? email}) => User(id: id, name: name ?? this.name, email: email ?? this.email, ...). Without copyWith, updating a single field means reconstructing the entire object manually. After generating the class, add a copyWith method that accepts all fields as nullable optional parameters and returns a new instance using the provided values or the current values as fallback. This is the pattern that makes immutable Dart models practical in real applications.

Treating all JSON null fields as nullable Dart fields without considering API contract

Fix: The tool generates String? for any field that appears as null in your sample. But not every nullable JSON field should be a nullable Dart field. If the API documentation says a field is required and always present, but your particular test response happened to have null (maybe during testing, maybe from an edge case account), making it String? in Dart adds unnecessary null handling throughout your codebase. Read the API docs or ask the backend team which fields are genuinely optional by design versus which are just nullable in certain edge cases. Required fields that are never actually null should be non-nullable in your model. Only fields that are legitimately sometimes absent should be nullable.

Using the generated class directly in Flutter widgets without an equals override

Fix: By default, Dart classes use reference equality — two User objects with identical field values are not equal to each other. This causes problems in Flutter: if a Provider or Riverpod notifier emits a new User object built from the same API response, Flutter cannot detect that nothing changed and will rebuild widgets unnecessarily. Override the == operator and hashCode in your model class, or use a package like equatable (add Equatable as a base class and list your fields in props). The freezed package generates all of this automatically. For models used in Flutter state management, value equality is almost always what you want.

Not considering json_serializable or freezed for larger projects

Fix: For a small project with a few models, hand-written fromJson and toJson (or this generated version) is perfectly reasonable. For a larger Flutter project with many models that evolve frequently, code generation packages are worth the setup cost. json_serializable generates fromJson and toJson from annotations automatically when you run flutter pub run build_runner build. freezed generates immutable classes with copyWith, equals, hashCode, and pattern matching from a single annotation. Both require adding dev dependencies and running code generation, but they pay off quickly when you are adding new fields to existing models — update the class declaration and regenerate rather than updating fromJson, toJson, copyWith, and equals manually.

Assuming the generated class handles polymorphic JSON (different objects in the same field)

Fix: Some APIs return fields that can be one of several different object shapes — a 'content' field that might be a TextContent object or an ImageContent object depending on a 'type' discriminator field. The generated class maps every JSON object to a single fixed Dart class. For polymorphic JSON, you need to handle the type discrimination manually: in fromJson, read the type field first and then call the appropriate subclass constructor. The common pattern in Dart is sealed classes (available from Dart 3.0) or a sealed-like hierarchy with an abstract base class and concrete subclasses. The tool generates a good starting point for the individual shapes, but the dispatch logic requires manual implementation.

Does it support json_serializable?

The generated code is hand-written fromJson and toJson — clean, dependency-free Dart that works without any code generation packages. It is not annotated for json_serializable. If you want to use json_serializable for code generation, the generated class is still useful as a reference for field names and types — use it to understand the structure, then rewrite it as a json_serializable-annotated class with @JsonSerializable() and run flutter pub run build_runner build to generate the boilerplate. For small projects and quick prototypes, the hand-written pattern from this tool is faster to get started with. json_serializable becomes worth the setup overhead when you have many models that change frequently.

Does it generate null-safe Dart code?

Yes. The generated code uses Dart 2.12+ sound null safety. Required fields are declared as non-nullable types (String, int, bool) and the constructor uses required named parameters. JSON null fields are generated as nullable types (String?, int?, bool?) with optional constructor parameters. The fromJson uses correct null-safe casts: json['field'] as String for non-nullable and json['field'] as String? for nullable. Your project must have null safety enabled (environment sdk >= 2.12.0 in pubspec.yaml) for the generated code to compile.

How do I use the generated class with the http package?

With the http package, import 'package:http/http.dart' as http and import 'dart:convert'. Make the request, decode the response, and cast: final response = await http.get(Uri.parse(url)); final data = jsonDecode(response.body) as Map; final user = User.fromJson(data). For list responses: final data = jsonDecode(response.body) as List; final users = data.map((e) => User.fromJson(e as Map)).toList(). Always check response.statusCode before decoding — decoding an error response body as your model class will throw a cast exception.

What is the difference between using this tool and the freezed package?

This tool generates plain Dart classes with hand-written fromJson and toJson — no packages required, works immediately. The freezed package generates immutable value types with fromJson, toJson, copyWith, equals, hashCode, pattern matching, and union types — but requires adding freezed and json_annotation to dependencies and running code generation (build_runner). For a quick API integration in a prototype or a small project, hand-written models from this tool are faster to set up. For a production Flutter app where models change frequently, freezed is worth the setup because adding a field automatically regenerates all the methods. Think of freezed as the long-term maintainability investment and this tool as the fast prototype path.

How do I handle a JSON field that might be an int or a double?

JSON numbers can be integers or floats, and Dart distinguishes between int and double at the type level. The generated code uses (json['field'] as num).toDouble() for fields that appear as floats in your sample — this is safer than json['field'] as double because some APIs return integer values (42) for fields that are logically decimal (42.0), and casting an int to double throws a TypeError. If a field that should be double sometimes comes back as an integer from the API, the (json['field'] as num).toDouble() cast handles both cases correctly. For fields you know are always integers, json['field'] as int is fine.

Can I use the generated class with Riverpod or Provider?

Yes. The generated immutable class with final fields is exactly the right data type for Flutter state management. With Provider: expose a value of type User or List through a ChangeNotifier or StateProvider and update it by replacing the entire object using copyWith or creating a new instance from a fresh API call. With Riverpod: a FutureProvider that calls the API and returns a User is a common pattern — the provider caches the result and rebuilds dependent widgets when the data changes. The immutability of the generated class ensures state management frameworks can track changes correctly — a new User object with different fields is a different object, triggering widget rebuilds.

What do I do if the API returns a list at the top level instead of a single object?

Generate the class from a single element of the array, then deserialize the full response as a list: final data = jsonDecode(responseBody) as List; final items = data.map((e) => MyClass.fromJson(e as Map)).toList(). For paginated responses where the array is wrapped in an object like {'data': [...], 'total': 100}, generate the outer wrapper class from the full response — the tool will create a wrapper class with a List field for the array and the pagination fields as separate typed fields.

Should I put generated model classes in a separate file or inline in the widget file?

Separate files. The standard Flutter project structure puts model classes in a lib/models/ or lib/data/models/ directory, one class per file named after the class: user.dart for User, product.dart for Product. This makes them importable from anywhere in the project, easy to find, and testable in isolation. Models that are only used by a single feature can go in the feature's directory: lib/features/profile/models/user_profile.dart. Putting model classes inline in widget files couples data representation to UI code, makes the models untestable without the widget, and makes it impossible to use the same model in multiple widgets. Keep models in their own layer.