tibbe avatar tibbe committed 42d0210

Improve introductionary documentation

De-emphasize working directly with the Value type as this is not the
common (or recommended) way to use the library, but still include
documentation on how to use it when it's called for.

Since the documentation on using generics is documented in the FromJSON
and ToJSON classes, refer to that documentation instead of only
documenting one method (which is not the preferred method anymore with
default signatures I believe).

Comments (0)

Files changed (1)

 --
 -- (A note on naming: in Greek mythology, Aeson was the father of Jason.)
 --
--- /DECODING TO AN ADT ('VALUE')/
---
--- To parse JSON into something useful, everything goes through the
--- 'decode' function, which is polymorphic on the 'FromJSON'
--- class. For representing arbitrary JSON AST there is a 'Value' type,
--- which is an instance of 'FromJSON'. For example:
---
--- > λ> decode "{\"foo\":123}" :: Maybe Value
--- > Just (Object (fromList [("foo",Number 123)]))
--- > λ> decode "{\"foo\":[\"abc\",\"def\"]}" :: Maybe Value
--- > Just (Object (fromList [("foo",Array (fromList [String "abc",String "def"]))]))
---
--- To run these examples, you need to enable @OverloadedStrings@ (in
--- GHCi you can write @:set -XOverloadedStrings@) so that you can use
--- string literals for non-'String' types. We're using (the lazy
--- version of) 'Data.ByteString.Lazy.ByteString', which requires at
--- least version 0.9.0.4 of the bytestring package to provide the
--- 'Data.String.IsString' instance. You probably have something newer
--- than this installed.
---
--- /DECODING TO HASKELL TYPES/
---
--- Any instance of 'FromJSON' can be specified (but see the PITFALLS section):
---
--- > λ> decode "[1,2,3]" :: Maybe [Int]
--- > Just [1,2,3]
---
--- Alternatively, there are instances for standard data types, so you
--- can use them directly. For example, use the 'Data.Map.Map' type to
--- get a map of 'Int's.
---
--- > λ> :m + Data.Map
--- > λ> decode "{\"foo\":1,\"bar\":2}" :: Maybe (Map String Int)
--- > Just (fromList [("bar",2),("foo",1)])
---
--- /DECODING A HETEROGENOUS OBJECT/
---
--- The above approach with maps of course will not work for
--- heterogenous objects, so there are a couple of approaches available
--- to you.
---
--- The 'Object' type contains JSON objects:
---
--- > λ> decode "{\"name\":\"Dave\",\"age\":2}" :: Maybe Object
--- > Just (fromList) [("name",String "Dave"),("age",Number 2)]
---
--- And you extract values from it with a parser using 'parse',
--- 'parseEither' or, in this example, 'parseMaybe':
---
--- > λ> do result <- decode "{\"name\":\"Dave\",\"age\":2}"
--- >       flip parseMaybe result $ \obj -> do
--- >         age <- obj .: "age"
--- >         name <- obj .: "name"
--- >         return (name ++ ": " ++ show (age*2))
--- >
--- > Just "Dave: 4"
---
--- Considering that any type that implements 'FromJSON' can be used
--- here, this is quite a powerful way to parse JSON. See the
--- documentation in 'FromJSON' for how to implement this class for
--- your own data types.
---
--- The downside is that you have to write the parser yourself, the
--- upside is that you have complete control over the way the JSON is
--- parsed.
---
--- /DECODING CUSTOM DATA TYPES GENERICALLY WITH TYPEABLE/
---
--- If you don't want such control and would prefer the JSON be parsed
--- to your own data types automatically according to some reasonably
--- sensible isomorphic implementation, you can use the generic parser
--- based on 'Data.Typeable.Typeable' and 'Data.Data.Data'. Switch to
--- the 'Data.Aeson.Generic' module, and you can do the following:
---
--- > λ> decode "[1]" :: Maybe [Int]
--- > Just [1]
--- > λ> :m + Data.Typeable Data.Data
--- > λ> :set -XDeriveDataTypeable
--- > λ> data Person = Person { personName :: String, personAge :: Int } deriving (Data,Typeable,Show)
--- > λ> encode Person { personName = "Chris", personAge = 123 }
--- > "{\"personAge\":123,\"personName\":\"Chris\"}"
--- > λ> decode "{\"personAge\":123,\"personName\":\"Chris\"}" :: Maybe Person
--- > Just (Person {
--- > personName = "Chris", personAge = 123
--- > })
---
--- Be aware that the encoding might not be what you expect:
---
--- > λ> data Foo = Foo Int Int deriving (Data,Typeable,Show)
--- > λ> encode (Foo 1 2)
--- > "[1,2]"
---
--- So it's better to treat the 'Data.Aeson.Generic.decode' and
--- 'Data.Aeson.Generic.encode' functions as an isomorphism, but do not
--- rely or care about the actual intermediate representation.
---
--- /PITFALLS/
---
--- Note that the JSON standard only allows arrays or objects of things
--- at the top-level, so calling decode on a simple type will not work:
---
--- > λ> decode "1" :: Maybe Int
--- > Nothing
--- > λ> decode "1" :: Maybe String
--- > Nothing
---
--- So stick to objects (e.g. maps in Haskell) or arrays (lists in Haskell):
---
--- > λ> decode "[1,2,3]" :: Maybe [Int]
--- > Just [1,2,3]
---
--- Likewise, for encoding to JSON you can encode anything that's an
--- instance of 'ToJSON', which does include simple types. So beware
--- that this aspect of the API is not isomorphic:
---
--- > λ> encode [1,2,3]
--- > "[1,2,3]"
--- > λ> decode (encode [1]) :: Maybe [Int]
--- > Just [1]
--- > λ> encode 1
--- > "1"
--- > λ> decode (encode (1 :: Int)) :: Maybe Int
--- > Nothing
---
--- Alternatively see 'Data.Aeson.Parser.value' to parse non-toplevel
--- JSON values.
 
 module Data.Aeson
     (
+    -- * Usage example
+    -- $usage-example
+
+    -- * Working directly with the JSON AST
+    -- $json-ast
+
+    -- * Pitfalls
+    -- $pitfalls
+
     -- * Encoding and decoding
+    -- $encoding-and-decoding
       decode
     , decode'
     , eitherDecode
 eitherDecode' :: (FromJSON a) => L.ByteString -> Either String a
 eitherDecode' = eitherDecodeWith json' fromJSON
 {-# INLINE eitherDecode' #-}
+
+-- $usage-example
+--
+-- The most common way to use the library is to define a data type,
+-- corresponding to some JSON data you want to work with, and then
+-- write either a 'FromJSON' instance, a to 'ToJSON' instance, or both
+-- for that type. For example, given this JSON data:
+--
+-- > { "name": "Joe", "age": 12 }
+--
+-- we create a matching data type:
+--
+-- > data Person = Person
+-- >     { name :: Text
+-- >     , age  :: Int
+-- >     } deriving Show
+--
+-- To decode data, we need to define a 'FromJSON' instance:
+--
+-- > {-# LANGUAGE OverloadedStrings #-}
+-- >
+-- > instance FromJSON Coord where
+-- >     parseJSON (Object v) = Person <$>
+-- >                            v .: "name" <*>
+-- >                            v .: "age"
+-- >     -- A non-Object value is of the wrong type, so fail.
+-- >     parseJSON _          = mzero
+--
+-- We can now parse the JSON data like so:
+--
+-- > >>> decode "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person
+-- > Just (Person {name = "Joe", age = 12})
+--
+-- The explicit type signature can often be omitted as the compiler
+-- can deduce the type using type inference.
+--
+-- To encode data, we need to define a 'ToJSON' instance:
+--
+-- > instance ToJSON Person where
+-- >     toJSON (Person name age) = object ["name" .= name, "age" .= age]
+--
+-- We can now encode a value like so:
+--
+-- > >>> encode (Person {name = "Joe", age = 12})
+-- > "{\"name\":\"Joe\",\"age\":12}"
+--
+-- There are predefined 'FromJSON' and 'ToJSON' instances for many
+-- types. Here's an example using lists and 'Int's:
+--
+-- > >>> decode "[1,2,3]" :: Maybe [Int]
+-- > Just [1,2,3]
+--
+-- And here's an example using the 'Data.Map.Map' type to get a map of
+-- 'Int's.
+--
+-- > >>> decode "{\"foo\":1,\"bar\":2}" :: Maybe (Map String Int)
+-- > Just (fromList [("bar",2),("foo",1)])
+--
+-- See the documentation of 'FromJSON' and 'ToJSON' for some examples
+-- how you can automatically dervice instances in some circumstances.
+
+
+-- $json-ast
+--
+-- Sometimes you want to work with JSON data directly, without first
+-- converting it to a custom data type. This can be useful if you want
+-- to e.g. convert JSON data to YAML data, without knowing what the
+-- contents of the original JSON data was. The 'Value' type, which is
+-- an instance of 'FromJSON', is used to represent an arbitrary JSON
+-- AST. Example usage:
+--
+-- > >>> decode "{\"foo\": 123}" :: Maybe Value
+-- > Just (Object (fromList [("foo",Number 123)]))
+-- > >>> decode "{\"foo\": [\"abc\",\"def\"]}" :: Maybe Value
+-- > Just (Object (fromList [("foo",Array (fromList [String "abc",String "def"]))]))
+--
+-- Once you have a 'Value' you can write recursive functions to
+-- traverse it and make arbitrary transformations.
+
+-- $pitfalls
+--
+-- Note that the JSON standard only allows arrays or objects of things
+-- at the top-level, so calling decode on a simple type will not work:
+--
+-- > >>> decode "1" :: Maybe Int
+-- > Nothing
+-- > >>> decode "1" :: Maybe String
+-- > Nothing
+--
+-- Likewise, for encoding to JSON you can encode anything that's an
+-- instance of 'ToJSON', which does include simple types. So beware
+-- that this aspect of the API is not isomorphic:
+--
+-- > >>> encode [1,2,3]
+-- > "[1,2,3]"
+-- > >>> decode (encode [1]) :: Maybe [Int]
+-- > Just [1]
+-- > >>> encode 1
+-- > "1"
+-- > >>> decode (encode (1 :: Int)) :: Maybe Int
+-- > Nothing
+--
+-- Alternatively see 'Data.Aeson.Parser.value' to parse non-toplevel
+-- JSON values.
+
+-- $encoding-and-decoding
+--
+-- Encoding and decoding is a two step process. To encode a value, it
+-- is first converted to a generic representation, using 'ToJSON'. The
+-- generic representation is then encoded as JSON data. To decode a
+-- value the process is reversed and 'FromJSON' is used instead. Both
+-- these steps are combined in the 'encode' and 'decode' functions.
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.