FromJSON is aeson’s type class for parsing a JSON Value into a Haskell value. `decode` accepts a lazy ByteString and returns `Maybe a`, requiring a `FromJSON a` instance; `parseJSON` supplies the underlying `Value -> Parser a` parser. section 16.1.1
ghci> import Data.Aeson as J
ghci> :set -XOverloadedStrings
ghci> :i J.Value
type Value :: *
data Value
= Object !Object
| Array !Array
| String !Text
| Number !Scientific
| Bool !Bool
| Null
ghci> J.decode "{\"a\":\"Hello\", \"b\": [1,2,3]}" :: Maybe J.Value
Just (Object (fromList [("a",String "Hello"),("b",Array
[Number 1.0,Number 2.0,Number 3.0])]))
ghci> :t J.decode
J.decode :: FromJSON a => ByteString -> Maybe a
JSON first becomes aeson’s `Value`, whose constructors represent objects, arrays, strings, numbers, booleans, and null; `decode` then selects a target type through its FromJSON instance. section 16.1.1
Write a custom parser when the JSON shape matters. `withObject` checks that a value is an object, then field operators parse required keys with `.:` and optional keys with `.:?`. A failed shape or missing required field produces a parser error rather than a value. section 16.1.1
Listing 16.1 A JSON parser for TodoEntry
{-# LANGUAGE RecordWildCards #-}
...
todoEntryParser :: J.Value -> J.Parser TodoEntry
todoEntryParser = J.withObject "TodoEntry" $
\object -> do #1
entryId <- object J..:? "entryId" #2
text <- object J..: "text" #3
checked <- object J..: "checked" #3
rank <- object J..:? "rank" #4
return TodoEntry {..} #5
This parser builds `TodoEntry` from an object: `text` and `checked` are required, while `entryId` and `rank` may be absent and become `Nothing`. section 16.1.1
Attach the parser to a type
After defining `todoEntryParser`, connect it to aeson with `instance J.FromJSON TodoEntry where parseJSON = todoEntryParser`. `J.decode` can then parse a JSON object directly as `Maybe TodoEntry`. section 16.1.1
For data types whose default JSON layout is acceptable, derive `Generic` and declare an empty FromJSON instance. aeson uses `genericParseJSON`; named record fields become object key-value pairs, `Maybe` fields are optional, and nested serializable types are handled recursively. section 16.2
Listing 16.4 A ToJSON instance for TodoEntry
{-# LANGUAGE DeriveGeneric #-} #1
module Todos.Types where
import qualified Data.Aeson as J #2
import qualified Data.Text as T #2
import GHC.Generics (Generic) #3
data TodoEntry = TodoEntry #4
{ entryId :: Maybe Int,
text :: T.Text,
checked :: Bool,
rank :: Maybe Int
}
deriving (Eq, Show, Generic) #5
instance J.ToJSON TodoEntry #6
instance J.FromJSON TodoEntry #6
`DeriveGeneric`, `deriving Generic`, and an empty `FromJSON TodoEntry` instance replace the handwritten parser when aeson’s default representation fits the type. section 16.2
FromJSON pairs naturally with ToJSON: encoding and then decoding a value can preserve it. This roundtrip is useful for checking that generated JSON instances match the data type’s definition. section 16.2 section 16.4
