Skip to content

JSON to Dart Model Class

Convert JSON to Dart model classes with fromJson and toJson, null-safe, for Flutter or plain Dart.

Input · JSON
Output · Dart
Valid JSONRuns in your browser

Load from a URL

Your browser fetches it directly, so the server must allow cross-origin requests (CORS).

JSON to Dart model in three steps

  1. Drop a .json file on the editor, paste the text, or fetch it with From URL.
  2. Set Root name to your model’s name, for example Product.
  3. Copy the output: plain Dart, with no imports, annotations or build step.

A shop product

The sample’s main class is below; Rating and Image follow it in the output, built the same way.

{
  "id": 118,
  "title": "Linen shirt",
  "price": 59.9,
  "currency": "EUR",
  "sizes": ["S", "M", "L"],
  "images": [
    {
      "url": "https://cdn.example.com/s/118-a.jpg",
      "primary": true
    },
    {
      "url": "https://cdn.example.com/s/118-b.jpg",
      "primary": false
    }
  ],
  "rating": { "average": 4.6, "count": 214 },
  "onSale": false
}
class Product {
  final int id;
  final String title;
  final double price;
  final String currency;
  final List<String> sizes;
  final List<Image> images;
  final Rating rating;
  final bool onSale;

  Product({required this.id, required this.title, required this.price, required this.currency, required this.sizes, required this.images, required this.rating, required this.onSale});

  factory Product.fromJson(Map<String, dynamic> json) => Product(
        id: json['id'] as int,
        title: json['title'] as String,
        price: (json['price'] as num).toDouble(),
        currency: json['currency'] as String,
        sizes: (json['sizes'] as List<dynamic>).map((e) => e as String).toList(),
        images: (json['images'] as List<dynamic>).map((e) => Image.fromJson(e as Map<String, dynamic>)).toList(),
        rating: Rating.fromJson(json['rating'] as Map<String, dynamic>),
        onSale: json['onSale'] as bool,
      );

  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
        'price': price,
        'currency': currency,
        'sizes': sizes,
        'images': images.map((e) => e.toJson()).toList(),
        'rating': rating.toJson(),
        'onSale': onSale,
      };
}

Why the casts look like that

jsonDecode returns dynamic (objects as Map<String, dynamic>, arrays as List<dynamic>), so every value needs a cast:

  • Decimals: on the Dart VM a whole number such as 60 decodes as an int, and as double would throw. (json['price'] as num).toDouble() accepts both.
  • Lists: a List<dynamic> can’t be cast to List<String> with as, so each element is cast inside a map. Lists of objects call Image.fromJson per element, and toJson maps them back.
  • Nested objects go through their own fromJson and toJson.

Field names are camelCase, but fromJson and toJson use the exact JSON key: on_sale would become the field onSale, read from json['on_sale'].

Nullable fields

Values that were null or missing get a nullable type and drop required from the constructor; array items are merged first to find them (rules on JSON to TypeScript). With Coupons as the root name:

[
  { "code": "SPRING", "percent": 15, "expires": "2026-12-31" },
  { "code": "VIP", "percent": null }
]
class Coupon {
  final String code;
  final int? percent;
  final String? expires;

  Coupon({required this.code, this.percent, this.expires});

  factory Coupon.fromJson(Map<String, dynamic> json) => Coupon(
        code: json['code'] as String,
        percent: json['percent'] as int?,
        expires: json['expires'] as String?,
      );

  Map<String, dynamic> toJson() => {
        'code': code,
        'percent': percent,
        'expires': expires,
      };
}

Mixed types and values that were only ever null become dynamic.

Using the model

import 'dart:convert';

Product parseProduct(String body) =>
    Product.fromJson(jsonDecode(body) as Map<String, dynamic>);

String encodeProduct(Product product) => jsonEncode(product.toJson());

List<Coupon> parseCoupons(String body) => (jsonDecode(body) as List<dynamic>)
    .map((e) => Coupon.fromJson(e as Map<String, dynamic>))
    .toList();

With the http package, pass response.body to parseProduct. jsonEncode(product) also works, because jsonEncode calls toJson() on objects it doesn’t know.

The generated Image class clashes with Flutter’s Image widget in any file that also imports package:flutter/material.dart, so rename it (say, ProductImage) or hide one import. The classes also don’t override == or hashCode, so two products with the same data aren’t equal; add them, or use a package such as equatable, if you compare models. For native apps, JSON to Kotlin and JSON to Swift build the same model.

Questions

Does it need json_serializable or build_runner?

No. It writes plain classes with fromJson and toJson spelled out, so there is no code generation step. If your project already uses json_serializable, its annotations do the same job through build_runner.

Why are all fields final?

A model parsed from a response usually should not change afterwards. Drop final, or add a copyWith method, if you need to edit values.

What does toJson do with null values?

It writes them as null and never leaves a key out. If the server wants the key omitted, use a collection if inside the map literal, such as if (expires != null) 'expires': expires,.

Related tools

Esc