Model options
class Author {
final String? name;
final String? role;
const Author({
this.name,
this.role,
});
factory Author.fromJson(Map<String, dynamic> json) => Author(
name: json['name']?.toString(),
role: json['role']?.toString(),
);
Map<String, dynamic> toJson() => {
'name': name,
'role': role,
};
static const Object _undefined = Object();
Author copyWith({
Object? name = _undefined,
Object? role = _undefined,
}) {
return Author(
name: identical(name, _undefined) ? this.name : name as String?,
role: identical(role, _undefined) ? this.role : role as String?,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
other is Author &&
other.name == name &&
other.role == role;
}
@override
int get hashCode => Object.hashAll([name, role]);
@override
String toString() {
return 'Author(name: $name, role: $role)';
}
}
class ApiResponse {
final int? id;
final String? title;
final bool? isActive;
final double? price;
final List<dynamic>? tags;
final Author? author;
const ApiResponse({
this.id,
this.title,
this.isActive,
this.price,
this.tags,
this.author,
});
factory ApiResponse.fromJson(Map<String, dynamic> json) => ApiResponse(
id: (json['id'] as num?)?.toInt(),
title: json['title']?.toString(),
isActive: json['isActive'] as bool?,
price: (json['price'] as num?)?.toDouble(),
tags: json['tags'] as List<dynamic>?,
author: json['author'] is Map<String, dynamic> ? Author.fromJson(json['author'] as Map<String, dynamic>) : null,
);
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'isActive': isActive,
'price': price,
'tags': tags,
'author': author?.toJson(),
};
static const Object _undefined = Object();
ApiResponse copyWith({
Object? id = _undefined,
Object? title = _undefined,
Object? isActive = _undefined,
Object? price = _undefined,
Object? tags = _undefined,
Object? author = _undefined,
}) {
return ApiResponse(
id: identical(id, _undefined) ? this.id : id as int?,
title: identical(title, _undefined) ? this.title : title as String?,
isActive: identical(isActive, _undefined) ? this.isActive : isActive as bool?,
price: identical(price, _undefined) ? this.price : price as double?,
tags: identical(tags, _undefined) ? this.tags : tags as List<dynamic>?,
author: identical(author, _undefined) ? this.author : author as Author?,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
other is ApiResponse &&
other.id == id &&
other.title == title &&
other.isActive == isActive &&
other.price == price &&
_listEquals(other.tags, tags) &&
other.author == author;
}
@override
int get hashCode => Object.hashAll([id, title, isActive, price, tags == null ? null : Object.hashAll(tags!), author]);
static bool _listEquals<T>(List<T>? left, List<T>? right) {
if (identical(left, right)) return true;
if (left == null || right == null || left.length != right.length) return false;
for (var index = 0; index < left.length; index++) {
if (left[index] != right[index]) return false;
}
return true;
}
@override
String toString() {
return 'ApiResponse(id: $id, title: $title, isActive: $isActive, price: $price, tags: $tags, author: $author)';
}
}
import 'package:flutter/material.dart';
class GeneratedListPage extends StatelessWidget {
const GeneratedListPage({super.key, required this.items});
final List<ApiResponse> items;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('API Results')),
body: LayoutBuilder(
builder: (context, constraints) {
final isWide = constraints.maxWidth >= 720;
return GridView.builder(
padding: const EdgeInsets.all(16),
itemCount: items.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isWide ? 2 : 1,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
mainAxisExtent: 150,
),
itemBuilder: (context, index) {
final item = items[index];
final title = item.id?.toString() ?? 'Item ' + (index + 1).toString();
final subtitle = item.title?.toString() ?? '';
final badge = item.isActive?.toString() ?? 'Ready';
return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: Theme.of(context).dividerColor),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 24,
child: Text((index + 1).toString()),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
),
if (subtitle.isNotEmpty) ...[
const SizedBox(height: 6),
Text(
subtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
],
const SizedBox(height: 12),
Chip(label: Text(badge), visualDensity: VisualDensity.compact),
],
),
),
],
),
),
);
},
);
},
),
);
}
}
Flutter UI preview
Formatted JSON
Clean indentation before model generation.
Null-safe model
Optional fields, nested classes and JSON helpers.
Flutter screen
Responsive list UI starter ready to paste.
Copy workflow
Copy model and UI without leaving this page.
How to use
A clean workflow from API JSON to Flutter screen.
Use this tool when an API response is ready but the Flutter model, parser and starter screen still need to be written.
Format JSON
Clean messy API output before class generation.
Generate model
Create fields, constructor, fromJson and toJson.
Preview UI
See how API values can appear in a Flutter screen.
Copy code
Move model and UI code into your Flutter project.
FAQ
JSON to Dart generator FAQ
Does this support nested JSON?
Yes. Nested objects and arrays of objects generate separate Dart classes.
Can I format JSON here?
Yes. Use Format for readable indentation or Minify for compact API payloads.
Is this useful for beginners?
Yes. Keep optional fields enabled, copy the generated model into lib/models and use the Flutter UI template as a starter screen.
Can I use this with Dio or Retrofit?
Yes. The generated models can be used with Dio, Retrofit, HTTP clients, repositories and state-management layers.
Why are fields optional by default?
Real APIs often miss keys or return null values, so optional fields reduce runtime crashes while learning and testing.
Does this replace manual review?
No. It creates a strong starting point, but you should still review field names, required values and business rules.
Can I paste an array response?
Yes. The generator reads the first object from an array and uses it to infer model fields.
Does DateTime detection work automatically?
Yes. ISO-style date strings can be converted into Dart DateTime fields when the option is enabled.
What should I copy first?
Copy the Dart model first, place it in lib/models, then use the Flutter UI template as a starter screen.
Can I use this with state management?
Yes. The generated immutable fields, copyWith and equality helpers are useful with Provider, Riverpod, Bloc and similar patterns.