@@ -1,4 +1,4 @@
1−{-# LANGUAGE DefaultSignatures, FlexibleContexts #-}
1+{-# LANGUAGE CPP, DefaultSignatures, FlexibleContexts #-}
22
33-- |
44-- Module: Data.Aeson.Types.Class
@@ -72,45 +72,60 @@ genericParseJSON opts = fmap to . gParseJSON opts
7272--
7373-- An example type and instance:
7474--
75−-- @{-\# LANGUAGE OverloadedStrings #-}
75+-- @
76+-- \-- Allow ourselves to write 'Text' literals.
77+-- {-\# LANGUAGE OverloadedStrings #-}
7678--
7779-- data Coord = Coord { x :: Double, y :: Double }
7880--
7981-- instance ToJSON Coord where
8082-- toJSON (Coord x y) = 'object' [\"x\" '.=' x, \"y\" '.=' y]
81−-- @
8283--
83−-- Note the use of the @OverloadedStrings@ language extension which enables
84−-- 'Text' values to be written as string literals.
84+-- toEncoding (Coord x y) = 'pairs' (\"x\" '.=' x '<>' \"y\" '.=' y)
85+-- @
8586--
86−-- Instead of manually writing your 'ToJSON' instance, there are three options
87+-- Instead of manually writing your 'ToJSON' instance, there are two options
8788-- to do it automatically:
8889--
8990-- * "Data.Aeson.TH" provides Template Haskell functions which will derive an
9091-- instance at compile time. The generated instance is optimized for your type
9192-- so will probably be more efficient than the following two options:
9293--
93−-- * The compiler can provide default generic implementations for 'toJSON' and
94−-- 'toEncoding'.
94+-- * The compiler can provide a default generic implementation for
95+-- 'toJSON'.
9596--
9697-- To use the second, simply add a @deriving 'Generic'@ clause to your
9798-- datatype and declare a 'ToJSON' instance for your datatype without giving
9899-- definitions for 'toJSON' or 'toEncoding'.
99100--
100−-- For example, the previous example can be simplified to just:
101+-- For example, the previous example can be simplified to a more
102+-- minimal instance:
101103--
102−-- @{-\# LANGUAGE DeriveGeneric \#-}
104+-- @
105+-- {-\# LANGUAGE DeriveGeneric \#-}
103106--
104−-- import GHC.Generics
107+-- import "GHC.Generics"
105108--
106−-- data Coord = Coord { x :: Double, y :: Double } deriving Generic
109+-- data Coord = Coord { x :: Double, y :: Double } deriving 'Generic'
107110--
108−-- instance ToJSON Coord
111+-- instance ToJSON Coord where
112+-- toEncoding = 'genericToEncoding' 'defaultOptions'
109113-- @
110114--
111−-- Note that, instead of using @DefaultSignatures@, it's also possible
112−-- to parameterize the generic encoding using 'genericToJSON' applied
113−-- to your encoding/decoding 'Options':
115+-- Why do we provide an implementation for 'toEncoding' here? The
116+-- 'toEncoding' function is a relatively new addition to this class.
117+-- To allow users of older versions of this library to upgrade without
118+-- having to edit all of their instances or encounter surprising
119+-- incompatibilities, the default implementation of 'toEncoding' uses
120+-- 'toJSON'. This produces correct results, but since it performs an
121+-- intermediate conversion to a 'Value', it will be less efficient
122+-- than directly emitting an 'Encoding'. Our one-liner definition of
123+-- 'toEncoding' above bypasses the intermediate 'Value'.
124+--
125+-- If @DefaultSignatures@ doesn't give exactly the results you want,
126+-- you can customize the generic encoding with only a tiny amount of
127+-- effort, using 'genericToJSON' and 'genericToEncoding' with your
128+-- preferred 'Options':
114129--
115130-- @
116131-- instance ToJSON Coord where
@@ -118,11 +133,31 @@ genericParseJSON opts = fmap to . gParseJSON opts
118133-- toEncoding = 'genericToEncoding' 'defaultOptions'
119134-- @
120135class ToJSON a where
136+ -- | Convert a Haskell value to a JSON-friendly intermediate type.
121137 toJSON :: a -> Value
138+ {-# MINIMAL toJSON #-}
122139
123140 default toJSON :: (Generic a, GToJSON (Rep a)) => a -> Value
124141 toJSON = genericToJSON defaultOptions
125142
143+ -- | Encode a Haskell value as JSON.
144+ --
145+ -- The default implementation of this method creates an
146+ -- intermediate 'Value' using 'toJSON'. This provides
147+ -- source-level compatibility for people upgrading from older
148+ -- versions of this library, but obviously offers no performance
149+ -- advantage.
150+ --
151+ -- To benefit from direct encoding, you /must/ provide an
152+ -- implementation for this method. The easiest way to do so is by
153+ -- having your types implement 'Generic' using the @DeriveGeneric@
154+ -- extension, and then have GHC generate a method body as follows.
155+ --
156+ -- @
157+ -- instance ToJSON Coord where
158+ -- toEncoding = 'genericToEncoding' 'defaultOptions'
159+ -- @
160+
126161 toEncoding :: a -> Encoding
127162 toEncoding = Encoding . E.encodeToBuilder . toJSON
128163 {-# INLINE toEncoding #-}
@@ -130,6 +165,10 @@ class ToJSON a where
130165-- | A type that can be converted from JSON, with the possibility of
131166-- failure.
132167--
168+-- In many cases, you can get the compiler to generate parsing code
169+-- for you (see below). To begin, let's cover writing an instance by
170+-- hand.
171+--
133172-- There are various reasons a conversion could fail. For example, an
134173-- 'Object' could be missing a required key, an 'Array' could be of
135174-- the wrong size, or a value could be of an incompatible type.
@@ -138,7 +177,7 @@ class ToJSON a where
138177--
139178-- * 'empty' and 'mzero' work, but are terse and uninformative
140179--
141−-- * 'fail' produces a custom error message
180+-- * 'fail' yields a custom error message
142181--
143182-- * 'typeMismatch' produces an informative message for cases when the
144183-- value encountered is not of the expected type
@@ -146,6 +185,7 @@ class ToJSON a where
146185-- An example type and instance:
147186--
148187-- @
188+-- \-- Allow ourselves to write 'Text' literals.
149189-- {-\# LANGUAGE OverloadedStrings #-}
150190--
151191-- data Coord = Coord { x :: Double, y :: Double }
@@ -155,15 +195,12 @@ class ToJSON a where
155195-- v '.:' \"x\" '<*>'
156196-- v '.:' \"y\"
157197--
158−-- \-- A non-'Object' value is of the wrong type.
159−-- \-- We could use mzero to fail, but typeMismatch
198+-- \-- We do not expect a non-'Object' value here.
199+-- \-- We could use 'mzero' to fail, but 'typeMismatch'
160200-- \-- gives a much more informative error message.
161201-- parseJSON invalid = 'typeMismatch' \"Coord\" invalid
162202-- @
163203--
164−-- Note the use of the @OverloadedStrings@ language extension which enables
165−-- 'Text' values to be written as string literals.
166−--
167204-- Instead of manually writing your 'FromJSON' instance, there are two options
168205-- to do it automatically:
169206--
@@ -183,16 +220,16 @@ class ToJSON a where
183220-- @
184221-- {-\# LANGUAGE DeriveGeneric \#-}
185222--
186−-- import GHC.Generics
223+-- import "GHC.Generics"
187224--
188−-- data Coord = Coord { x :: Double, y :: Double } deriving Generic
225+-- data Coord = Coord { x :: Double, y :: Double } deriving 'Generic'
189226--
190227-- instance FromJSON Coord
191228-- @
192229--
193−-- Note that, instead of using @DefaultSignatures@, it's also possible
194−-- to parameterize the generic decoding using 'genericParseJSON' applied
195−-- to your encoding/decoding 'Options':
230+-- If @DefaultSignatures@ doesn't give exactly the results you want,
231+-- you can customize the generic decoding with only a tiny amount of
232+-- effort, using 'genericParseJSON' with your preferred 'Options':
196233--
197234-- @
198235-- instance FromJSON Coord where
@@ -205,9 +242,9 @@ class FromJSON a where
205242 default parseJSON :: (Generic a, GFromJSON (Rep a)) => Value -> Parser a
206243 parseJSON = genericParseJSON defaultOptions
207244
208−-- | A key-value pair for a JSON object.
209−class KeyValue t where
210− (.=) :: ToJSON v => Text -> v -> t
245+-- | A key-value pair for encoding a JSON object.
246+class KeyValue kv where
247+ (.=) :: ToJSON v => Text -> v -> kv
211248 infixr 8 .=
212249
213250-- | Fail parsing due to a type mismatch, with a descriptive message.
@@ -215,7 +252,6 @@ class KeyValue t where
215252-- Example usage:
216253--
217254-- @
218−--
219255-- instance FromJSON Coord where
220256-- parseJSON ('Object' v) = {- type matches, life is good -}
221257-- parseJSON wat = 'typeMismatch' \"Coord\" wat