NV ToolkitFormat · Validate · Convert

Code Generators

JSON to Elm Converter

Paste a JSON or XML sample — the format is auto-detected — and get typed Elm models with names, nesting and nullability inferred for you.

In short

Convert JSON to Elm online: paste a sample and generate typed Elm models with serialization helpers — powered by quicktype, in your browser.

By NaveenKumar T · Updated

Learn: JSON Examples: From Hello World to Real API Payloads

  • JSON or XML input, auto-detected
  • Nested objects become types
  • Nullability inferred

Runs entirely in your browser — nothing you paste or open here is uploaded, logged, or stored.How we handle data →

Example

Example: JSON order payload to Elm

A small order document with a nested object, an array of two items, a decimal total and a null field — enough to show how each Elm type, list and nullable is inferred. The same page accepts the XML form of this order too, so the sample below is an example rather than the only accepted format. The right-hand side is the exact output of this tool.

Input · JSON
{
  "id": "ord_9F2KX",
  "status": "processing",
  "paid": true,
  "customer": { "name": "Ada Lovelace", "email": "ada@example.com", "phone": null },
  "items": [
    { "sku": "KB-75", "qty": 1, "price": 149.99 },
    { "sku": "MS-12", "qty": 2, "price": 39.5 }
  ],
  "total": 228.99
}
Generated · Elm
-- To decode the JSON data, add this file to your project, run
--
--     elm-package install NoRedInk/elm-decode-pipeline
--
-- add these imports
--
--     import Json.Decode exposing (decodeString)`);
--     import QuickType exposing (root)
--
-- and you're off to the races with
--
--     decodeString root myJsonString

module QuickType exposing
    ( Root
    , rootToString
    , root
    , Customer
    , Item
    )

import Json.Decode as Jdec
import Json.Decode.Pipeline as Jpipe
import Json.Encode as Jenc
import Dict exposing (Dict, map, toList)
import Array exposing (Array, map)

type alias Root =
    { id : String
    , status : String
    , paid : Bool
    , customer : Customer
    , items : Array Item
    , total : Float
    }

type alias Customer =
    { name : String
    , email : String
    , phone : ()
    }

type alias Item =
    { sku : String
    , qty : Int
    , price : Float
    }

-- decoders and encoders

rootToString : Root -> String
rootToString r = Jenc.encode 0 (encodeRoot r)

root : Jdec.Decoder Root
root =
    Jpipe.decode Root
        |> Jpipe.required "id" Jdec.string
        |> Jpipe.required "status" Jdec.string
        |> Jpipe.required "paid" Jdec.bool
        |> Jpipe.required "customer" customer
        |> Jpipe.required "items" (Jdec.array item)
        |> Jpipe.required "total" Jdec.float

encodeRoot : Root -> Jenc.Value
encodeRoot x =
    Jenc.object
        [ ("id", Jenc.string x.id)
        , ("status", Jenc.string x.status)
        , ("paid", Jenc.bool x.paid)
        , ("customer", encodeCustomer x.customer)
        , ("items", makeArrayEncoder encodeItem x.items)
        , ("total", Jenc.float x.total)
        ]

customer : Jdec.Decoder Customer
customer =
    Jpipe.decode Customer
        |> Jpipe.required "name" Jdec.string
        |> Jpipe.required "email" Jdec.string
        |> Jpipe.optional "phone" (Jdec.null ()) ()

encodeCustomer : Customer -> Jenc.Value
encodeCustomer x =
    Jenc.object
        [ ("name", Jenc.string x.name)
        , ("email", Jenc.string x.email)
        , ("phone", always Jenc.null x.phone)
        ]

item : Jdec.Decoder Item
item =
    Jpipe.decode Item
        |> Jpipe.required "sku" Jdec.string
        |> Jpipe.required "qty" Jdec.int
        |> Jpipe.required "price" Jdec.float

encodeItem : Item -> Jenc.Value
encodeItem x =
    Jenc.object
        [ ("sku", Jenc.string x.sku)
        , ("qty", Jenc.int x.qty)
        , ("price", Jenc.float x.price)
        ]

--- encoder helpers

makeArrayEncoder : (a -> Jenc.Value) -> Array a -> Jenc.Value
makeArrayEncoder f arr =
    Jenc.array (Array.map f arr)

makeDictEncoder : (a -> Jenc.Value) -> Dict String a -> Jenc.Value
makeDictEncoder f dict =
    Jenc.object (toList (Dict.map (\k -> f) dict))

makeNullableEncoder : (a -> Jenc.Value) -> Maybe a -> Jenc.Value
makeNullableEncoder f m =
    case m of
    Just x -> f x
    Nothing -> Jenc.null

Look for the "phone" field: sampled as null, it is typed as (). "items" became Array Item, and "total" is Float.

Learn more

From JSON sample to Elm types

Hand-writing model classes from a JSON payload is transcription work: read a field, type a property, repeat eighty times, miss one. This page runs quicktype (the same engine behind the best-known generators) against your document, merging array elements to find the common shape and naming nested types from their keys.

Decoders built with Jpipe.required are positional: the fields must be listed in the same order as the type alias's constructor arguments. Reordering a field in the alias without reordering its pipeline line still compiles — and decodes the wrong value into the wrong field. Keep both lists in sync by hand, or switch to Jdec.map with explicit field names to remove the ordering dependency.

How JSON values become Elm types

Inference works from the values in your sample. Strings map to String, integers and decimals are told apart (228.99 becomes Float, while 1 and 2 stay integral), true/false become the language's boolean, and an object such as "customer" becomes its own named type. The "items" array is typed as Array Item because every element shares the same keys — quicktype unifies the shapes of all array elements into one Item type.

Nulls are the one place inference needs your help: "phone": null carries no type information, so the field is emitted as (). If the real API sometimes sends a string there, include a sample where the field is populated (paste several documents merged into one array) and the generated type becomes the union of what it saw.

Pasting XML instead: how elements and attributes become Elm types

The XML is parsed into a data model before inference (attributes included, text nodes named "text"), so the root element becomes a nested type: <order> is a field of type Order on the top-level Root rather than the top level itself. Child elements become fields, and repeated elements — the two <item> siblings — are collected into Array Item under an Items wrapper type that mirrors the <items> container.

Attributes and element text are typed differently, and this matters: element text is type-inferred, so <total>228.99</total> becomes Float, while attribute values are kept as they were written — qty="1" and price="149.99" stay String in Elm. Prefer element text for numeric data you want typed, or cast attribute values after decoding. One more trap: a single <item> would not be recognized as a list, so make sure repeated elements appear at least twice in the sample.

Annotations, nulls and nested arrays in the generated Elm

Elm has no annotations, and that is exactly why generating it pays: beside each type alias quicktype writes both a decoder and an encoder. Decoders are pipelines built from Jpipe.decode and one Jpipe.required line per field, so the wire key is spelled out on every line and the Elm field is free to be camelCase. Encoders are Jenc.object lists pairing the same keys with the matching Jenc functions, helped by generated makeArrayEncoder, makeNullableEncoder and makeDictEncoder. The module exposes every alias plus the top-level decoder and rootToString.

A field sampled only as null is typed as unit and decoded with Jpipe.optional wrapping Jdec.null, which succeeds on null and on a missing key but fails on a real value. Merge a document where the field is populated and you get what you actually want: Maybe String, decoded with Jdec.nullable and encoded through makeNullableEncoder. Keys absent from some samples become Maybe in the same way. An empty array decodes as an Array of Jdec.Value — deliberately untyped, because there was nothing to infer.

Nested objects become their own type aliases named from the key — Customer, Item — each with a matching decoder and encoder, and the parent decoder simply references the child decoder by name. Arrays of objects are typed Array Item, not List, and decoded with Jdec.array. This is where generation earns its keep: a thirty-field payload is thirty decoder lines that all have to be exactly right. The two sample traps remain — one element hides optional fields, and an empty array produces no element alias.

Help

Frequently asked questions

Everything you need to know about the JSON to Elm.

01How do I generate Elm classes from JSON?

Paste a representative document — JSON or XML, the format is detected automatically — and press Convert. A real API response works best. Nested objects become their own Elm types, arrays become Array Item, and the result downloads as Root.elm.

02What does the generated Elm code include?

Elm generation writes the type aliases AND the decoder/encoder pipelines — the most labor-intensive part of Elm JSON interop — derived mechanically from your sample.

03How do I use the generated Elm code in my project?

Install NoRedInk/elm-decode-pipeline and add the module. Jdec.decodeString root jsonString decodes into the Root alias and rootToString encodes it back; the module exports a decoder and encoder for every type alias it defines. A field that was null in the sample decodes with Jdec.null and is typed as unit.

04Why is generated Elm such a big win?

Elm requires explicit decoders for every field — safe but verbose. A 30-field payload means 30 decoder lines written exactly right; generating them removes the one genuinely tedious step in an otherwise delightful language.

Keep working

Related tools

Learn the format

Related guides

All guides →