JSON to Haskell with Aeson
Aeson is the standard JSON library for Haskell. It decodes into your own data types through FromJSON instances and encodes through ToJSON. This JSON to Haskell generator writes both the record types and the instances from a sample, together with a decodeTopLevel function, giving you a module that compiles with Aeson and a couple of common packages.
Example
{ "id": 42, "title": "Write docs", "done": false,
"owner": { "name": "Ada", "email": null },
"subtasks": [ { "title": "Outline", "estimate": 2 }, { "title": "Draft" } ] }
data Task = Task
{ taskIDTask :: Int
, titleTask :: Text
, doneTask :: Bool
, ownerTask :: Owner
, subtasksTask :: [Subtask]
} deriving (Show)
data Subtask = Subtask
{ titleSubtask :: Text
, estimateSubtask :: Maybe Int
} deriving (Show)
instance FromJSON Subtask where
parseJSON (Object v) = Subtask
<$> v .: "title"
<*> v .:? "estimate"
Required keys are read with .: and optional keys with .:?, which yields Nothing when the key is absent. Strings use Text rather than String, as is idiomatic for Aeson.
Sum types for mixed values
When a JSON value can have several types, such as an array containing both numbers and strings, the generator creates a Haskell sum type, for example data X = IntegerInX Int | StringInX Text, with FromJSON and ToJSON instances that try each alternative. Numbers without decimals become Int and decimals Float; change Float to Double if you need more precision, since Aeson supports both.
Why the field names are so long
Haskell record fields are functions in the module's namespace, so two records cannot both have a field called title. The generator avoids collisions by appending the type name: titleTask, titleSubtask. With GHC's DuplicateRecordFields and OverloadedRecordDot extensions you can shorten them to title and write task.title; the FromJSON instances keep the JSON keys explicit, so decoding is unaffected.
Using the module
import qualified Data.ByteString.Lazy as BL
main = do
bytes <- BL.readFile "task.json"
case decodeTopLevel bytes of
Just task -> print (titleTask task)
Nothing -> putStrLn "Invalid JSON"
The module enables OverloadedStrings and StrictData and imports Data.Aeson, Data.Text and Data.HashMap.Strict, so add aeson, text, bytestring and unordered-containers to your package dependencies. For better error messages, switch decode to eitherDecode, which returns the reason decoding failed.
Validate your sample first with the JSON Validator. For a schema-based contract shared with other languages, try JSON to JSON Schema. Generation runs in your browser.