Dart · Swift · Kotlin

JSON to Model Generator

Paste a JSON response and get null-safe data classes for Flutter, iOS, or Android. Nested objects, arrays of objects, reserved keys, and mixed arrays are handled — and every line is generated in your browser.

Updated 21 October 2026 Built by Khalil Rahman 100% client-side No sign-up

Model generator

Nothing leaves your device
Paste a single object or an array of objects. One representative record is enough — the generator merges keys across array elements.

Ready. Paste JSON and choose a language.

Generated code

// Generated classes appear here.
// Press Generate, or load a sample above.

What this generator does

Hand-writing model classes from an API payload is mechanical work that invites typos, especially when a response nests objects three levels deep and mixes snake_case with camelCase. This tool reads one representative JSON sample and writes the equivalent class hierarchy for your target language, including the parsing code that turns a decoded map into a typed object.

It infers types from the sample rather than from a schema, so the output reflects what your API actually returns. Numbers are split into integers and decimals, arrays are inspected element by element, and every object inside an array contributes its keys to a single class. The result is a starting point you can paste into a project and adjust, not a black box you have to trust.

Everything runs in JavaScript on your device. There is no upload step, no server round trip, and no logging of your payload — which matters when the JSON you are debugging contains customer data or an access token.

How JSON types map to each language

The same JSON value becomes a different type depending on the target. This table shows the defaults used by the generator.

JSON type to language type mapping
JSON valueDartSwiftKotlin
"text"StringStringString
42intIntInt or Long
3.14doubleDoubleDouble
trueboolBoolBoolean
nulldynamicAnyJsonElement
{"a":1}nested classnested structnested data class
[1,2]List<int>[Int]List<Int>
[{"a":1}]List<A>[A]List<A>
Why integers matter. JSON has one number type, so 1 and 1.0 are indistinguishable on the wire. Dart is the strictest case: as int throws if the runtime value is a double, so generated Dart casts through num and calls toInt() or toDouble(). That one change removes the most common TypeError in hand-written Flutter parsers.

Nullable versus strict fields

Real APIs are inconsistent: a key that is always present in your test fixture is sometimes absent in production, and fields that look required are occasionally null. The generator offers two modes because both are legitimate.

Nullable (default)

Every field is optional and every read is guarded. A missing key yields null instead of an exception, which is usually what you want while an API is still being built. In Dart this means String? plus this.name in the constructor; in Swift, String?; in Kotlin, String? = null.

Strict

Fields are non-nullable and required. Dart emits required this.name, Swift emits let name: String, Kotlin emits val name: String without a default. This catches contract drift at decode time instead of letting a null propagate into your UI. Choose strict only when the API documents the field as guaranteed.

A practical pattern: generate in nullable mode, ship it, and tighten individual fields to strict as you confirm them against the API contract. Tightening one field at a time keeps decoding failures traceable to a single change.

Nested objects and arrays of objects

Nesting is where hand-written models usually go wrong, so it is worth describing exactly what the generator emits.

An object inside an object

The inner object becomes its own class named after its key, and the parent field is typed as that class. In Dart the parent's fromJson checks for null before delegating, which avoids a null-check crash on an absent sub-object.

An array of objects

The element type becomes a class, and the field becomes a list of that class. Crucially, every element in the array contributes keys, not just the first one. If element one has id and name but element two also has discount, the generated class includes discount as a nullable field. This is the difference between code that decodes your fixture and code that survives a real payload.

Arrays of arrays

A nested list is typed one level deep, for example a list of string lists. Beyond two levels the generator falls back to a dynamic type and warns you, because the manual cast chain stops being readable.

Mixed arrays

If an array contains both strings and numbers, or objects with no shared shape, no single static type can describe it. The generator types the field as dynamic in Dart, JsonElement in Kotlin, and in Swift it omits Codable conformance for that struct, because Any is not Decodable. A warning explains what to fix.

Invalid keys, snake_case, and reserved words

JSON keys are arbitrary strings; identifiers are not. Three cases need special handling, and the generator covers all of them.

Keys that are not valid identifiers

"first-name", "user id", and "2fa_enabled" cannot be used directly as a property name. They are split on non-alphanumeric characters, joined in camelCase, and prefixed if the result would start with a digit. The original key is retained in the serialization mapping so decoding still works.

Reserved words

"class", "default", "is", "object", and "in" are keywords in at least one of the three languages. Swift and Kotlin allow keyword identifiers inside backticks, so the generator emits `default`. Dart has no escape syntax, so a trailing underscore is appended.

Collisions after conversion

"user_id" and "userId" both become userId. The generator appends a numeric suffix to the second occurrence and keeps the correct JSON key for each, so no field is silently lost.

Examples of key sanitisation per language
JSON keyDartSwiftKotlin
user_iduserIduserIduserId
first-namefirstNamefirstNamefirstName
classclass_`class``class`
2faf2faf2faf2fa

Serialization libraries by language

Dart and Flutter

The output is dependency-free: a plain class with a named constructor, a factory fromJson, and an optional toJson. That works in any Flutter project without build_runner. If you already use json_serializable or freezed, keep the field list and replace the parsing methods with annotations — the hard part, deciding the types, is already done.

Swift

Output conforms to Codable, the standard-library protocol pair, so it works with JSONDecoder with no third-party dependency. CodingKeys is emitted only when at least one property name differs from its JSON key, keeping simple structs clean. Optional properties decode a missing key as nil without extra work.

Kotlin

Output targets kotlinx.serialization with @Serializable and @SerialName, which is the official multiplatform option and needs only the serialization plugin. If your project uses Gson or Moshi, rename the annotations; the property list and nullable types stay identical.

Dates and enums are strings. JSON has no date or enum type, so a timestamp arrives as a string and is typed as a string. Add a custom serializer for those fields after pasting — the generator flags them in the warnings list so you can find them quickly.

How to use the generator

  1. Copy a real response

    Take one object from your API — a list response is fine, the generator uses the first element and merges the rest. A sample with nulls and a nested object produces a more accurate model than a minimal one.

  2. Pick the language and root name

    The root class name becomes the outermost type. Use the name your codebase already uses, such as User or OrderResponse.

  3. Choose null mode

    Leave it on Nullable while the API is in flux. Switch to Strict once fields are contractually guaranteed.

  4. Generate, then read the warnings

    Warnings list renamed keys, mixed arrays, and fields typed as dynamic. Those are the places that need a decision from you, not from the tool.

  5. Copy or download

    Copy puts the whole output on the clipboard. Download writes a file with the correct extension: .dart, .swift, or .kt.

  6. Decode one real payload in a test

    Before wiring the model into UI, decode an actual response in a unit test. That single step catches every inference mistake the sample did not reveal.

What the generator cannot do

  • It cannot see values it was not given. A field that is null in your sample is typed as dynamic even if production always sends a string. Paste a richer sample or fix the type by hand.
  • It does not infer enums. A string that only ever takes three values is still a string. Map it to an enum yourself.
  • It does not detect date formats. ISO timestamps, Unix seconds, and Unix milliseconds all arrive as numbers or strings.
  • It does not handle polymorphism. A field that is sometimes an object and sometimes a string becomes dynamic, and you will need a custom decoder.
  • It does not resolve $ref schemas. This is a sample-based generator, not a JSON Schema or OpenAPI compiler.
  • It does not merge classes across calls. Each generation is independent; there is no project-wide type registry.
  • Large inputs are capped. Samples above roughly two million characters are rejected to keep the tab responsive.
Treat the output as a first draft. It removes the typing, not the review. Decode a real payload in a test before you rely on any generated model.

Frequently asked questions

Does the generator produce null-safe Dart code?

By default every field is nullable and every cast uses an as T? pattern, so a missing key or null value will not throw. Switch to Strict mode if you want non-nullable fields with required constructor parameters.

How does it decide the type of a field?

It reads the JSON sample you paste. Strings become String, whole numbers become int or Long, decimals become double, and objects become nested classes. Arrays are inspected element by element, and the field types of every object in the array are merged into one class.

What happens with snake_case or invalid JSON keys?

Keys are converted to camelCase for the property name. Keys that are not valid identifiers, or that collide with language keywords, are escaped: backticks in Swift and Kotlin, a trailing underscore in Dart. The original key is preserved in CodingKeys for Swift and @SerialName for Kotlin.

Does it support arrays of objects?

Yes. An array of objects generates a separate class for the element type, and the parent field becomes a list of that class. If different objects in the array have different keys, the class includes the union of all keys.

What if an array contains mixed types?

The field is typed as dynamic in Dart and JsonElement in Kotlin. In Swift the struct omits Codable conformance, because a heterogeneous array cannot be decoded automatically. A warning appears in the output so you know to write custom decoding.

Is my JSON uploaded anywhere?

No. Parsing and code generation happen in JavaScript on your device. There is no API call and no analytics event containing your data, and the page works with the network disconnected after it loads.

Which serialization library does each language target?

Dart output is plain manual fromJson and toJson with no dependency. Swift output uses Codable and CodingKeys from the standard library. Kotlin output targets kotlinx.serialization with @Serializable and @SerialName.

Can I generate from an API response that has a top-level array?

Yes. If the root is an array, the generator builds a class from the first object element and tells you the class represents a list item rather than the whole response.

About this tool and how it is maintained

Built and reviewed by Khalil Rahman, founder of SkillsInsider. Last reviewed 21 October 2026.

Generator output is checked against the current compilers and serialization libraries for Dart, Swift, and Kotlin whenever a language emitter changes. If you find a case that produces code that will not compile, report it through the contact page with the JSON sample that triggered it.

Editorial and verification standards are documented in our editorial policy. Data handling is described in the privacy policy.