@@ -1,4 +1,4 @@
1−{-# LANGUAGE DefaultSignatures, FlexibleContexts #-}
1+{-# LANGUAGE CPP, DefaultSignatures, FlexibleContexts #-}
4-- Module: Data.Aeson.Types.Class
4-- Module: Data.Aeson.Types.Class
@@ -72,45 +72,60 @@ genericParseJSON opts = fmap to . gParseJSON opts
73-- An example type and instance:
73-- An example type and instance:
76+-- \-- Allow ourselves to write 'Text' literals.
75−-- @{-\# LANGUAGE OverloadedStrings #-}
77+-- {-\# LANGUAGE OverloadedStrings #-}
77-- data Coord = Coord { x :: Double, y :: Double }
79-- data Coord = Coord { x :: Double, y :: Double }
79-- instance ToJSON Coord where
81-- instance ToJSON Coord where
80-- toJSON (Coord x y) = 'object' [\"x\" '.=' x, \"y\" '.=' y]
82-- toJSON (Coord x y) = 'object' [\"x\" '.=' x, \"y\" '.=' y]
83−-- Note the use of the @OverloadedStrings@ language extension which enables
84+-- toEncoding (Coord x y) = 'pairs' (\"x\" '.=' x '<>' \"y\" '.=' y)
84−-- 'Text' values to be written as string literals.
85+-- @
86−-- Instead of manually writing your 'ToJSON' instance, there are three options
87+-- Instead of manually writing your 'ToJSON' instance, there are two options
87-- to do it automatically:
88-- to do it automatically:
89-- * "Data.Aeson.TH" provides Template Haskell functions which will derive an
90-- * "Data.Aeson.TH" provides Template Haskell functions which will derive an
90-- instance at compile time. The generated instance is optimized for your type
91-- instance at compile time. The generated instance is optimized for your type
91-- so will probably be more efficient than the following two options:
92-- so will probably be more efficient than the following two options:
93−-- * The compiler can provide default generic implementations for 'toJSON' and
94+-- * The compiler can provide a default generic implementation for
94−-- 'toEncoding'.
95+-- 'toJSON'.
96-- To use the second, simply add a @deriving 'Generic'@ clause to your
97-- To use the second, simply add a @deriving 'Generic'@ clause to your
97-- datatype and declare a 'ToJSON' instance for your datatype without giving
98-- datatype and declare a 'ToJSON' instance for your datatype without giving
98-- definitions for 'toJSON' or 'toEncoding'.
99-- definitions for 'toJSON' or 'toEncoding'.
100−-- For example, the previous example can be simplified to just:
101+-- For example, the previous example can be simplified to a more
102−-- @{-\# LANGUAGE DeriveGeneric \#-}
105+-- {-\# LANGUAGE DeriveGeneric \#-}
104−-- import GHC.Generics
107+-- import "GHC.Generics"
106−-- data Coord = Coord { x :: Double, y :: Double } deriving Generic
109+-- data Coord = Coord { x :: Double, y :: Double } deriving 'Generic'
108−-- instance ToJSON Coord
111+-- instance ToJSON Coord where
112+-- toEncoding = 'genericToEncoding' 'defaultOptions'
111−-- Note that, instead of using @DefaultSignatures@, it's also possible
115+-- Why do we provide an implementation for 'toEncoding' here? The
112−-- to parameterize the generic encoding using 'genericToJSON' applied
116+-- 'toEncoding' function is a relatively new addition to this class.
113−-- to your encoding/decoding 'Options':
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'.
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':
116-- instance ToJSON Coord where
131-- instance ToJSON Coord where
@@ -118,11 +133,31 @@ genericParseJSON opts = fmap to . gParseJSON opts
118-- toEncoding = 'genericToEncoding' 'defaultOptions'
133-- toEncoding = 'genericToEncoding' 'defaultOptions'
120class ToJSON a where
135class ToJSON a where
136+ -- | Convert a Haskell value to a JSON-friendly intermediate type.
121 toJSON :: a -> Value
137 toJSON :: a -> Value
138+ {-# MINIMAL toJSON #-}
123 default toJSON :: (Generic a, GToJSON (Rep a)) => a -> Value
140 default toJSON :: (Generic a, GToJSON (Rep a)) => a -> Value
124 toJSON = genericToJSON defaultOptions
141 toJSON = genericToJSON defaultOptions
143+ -- | Encode a Haskell value as JSON.
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
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.
157+ -- instance ToJSON Coord where
158+ -- toEncoding = 'genericToEncoding' 'defaultOptions'
126 toEncoding :: a -> Encoding
161 toEncoding :: a -> Encoding
127 toEncoding = Encoding . E.encodeToBuilder . toJSON
162 toEncoding = Encoding . E.encodeToBuilder . toJSON
128 {-# INLINE toEncoding #-}
163 {-# INLINE toEncoding #-}
@@ -130,6 +165,10 @@ class ToJSON a where
130-- | A type that can be converted from JSON, with the possibility of
165-- | A type that can be converted from JSON, with the possibility of
131-- failure.
166-- failure.
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
133-- There are various reasons a conversion could fail. For example, an
172-- There are various reasons a conversion could fail. For example, an
134-- 'Object' could be missing a required key, an 'Array' could be of
173-- 'Object' could be missing a required key, an 'Array' could be of
135-- the wrong size, or a value could be of an incompatible type.
174-- the wrong size, or a value could be of an incompatible type.
@@ -138,7 +177,7 @@ class ToJSON a where
139-- * 'empty' and 'mzero' work, but are terse and uninformative
178-- * 'empty' and 'mzero' work, but are terse and uninformative
141−-- * 'fail' produces a custom error message
180+-- * 'fail' yields a custom error message
143-- * 'typeMismatch' produces an informative message for cases when the
182-- * 'typeMismatch' produces an informative message for cases when the
144-- value encountered is not of the expected type
183-- value encountered is not of the expected type
@@ -146,6 +185,7 @@ class ToJSON a where
146-- An example type and instance:
185-- An example type and instance:
188+-- \-- Allow ourselves to write 'Text' literals.
149-- {-\# LANGUAGE OverloadedStrings #-}
189-- {-\# LANGUAGE OverloadedStrings #-}
151-- data Coord = Coord { x :: Double, y :: Double }
191-- data Coord = Coord { x :: Double, y :: Double }
@@ -155,15 +195,12 @@ class ToJSON a where
155-- v '.:' \"x\" '<*>'
195-- v '.:' \"x\" '<*>'
156-- v '.:' \"y\"
196-- v '.:' \"y\"
158−-- \-- A non-'Object' value is of the wrong type.
198+-- \-- We do not expect a non-'Object' value here.
159−-- \-- We could use mzero to fail, but typeMismatch
199+-- \-- We could use 'mzero' to fail, but 'typeMismatch'
160-- \-- gives a much more informative error message.
200-- \-- gives a much more informative error message.
161-- parseJSON invalid = 'typeMismatch' \"Coord\" invalid
201-- parseJSON invalid = 'typeMismatch' \"Coord\" invalid
164−-- Note the use of the @OverloadedStrings@ language extension which enables
165−-- 'Text' values to be written as string literals.
167-- Instead of manually writing your 'FromJSON' instance, there are two options
204-- Instead of manually writing your 'FromJSON' instance, there are two options
168-- to do it automatically:
205-- to do it automatically:
@@ -183,16 +220,16 @@ class ToJSON a where
184-- {-\# LANGUAGE DeriveGeneric \#-}
221-- {-\# LANGUAGE DeriveGeneric \#-}
186−-- import GHC.Generics
223+-- import "GHC.Generics"
188−-- data Coord = Coord { x :: Double, y :: Double } deriving Generic
225+-- data Coord = Coord { x :: Double, y :: Double } deriving 'Generic'
190-- instance FromJSON Coord
227-- instance FromJSON Coord
193−-- Note that, instead of using @DefaultSignatures@, it's also possible
230+-- If @DefaultSignatures@ doesn't give exactly the results you want,
194−-- to parameterize the generic decoding using 'genericParseJSON' applied
231+-- you can customize the generic decoding with only a tiny amount of
195−-- to your encoding/decoding 'Options':
232+-- effort, using 'genericParseJSON' with your preferred 'Options':
198-- instance FromJSON Coord where
235-- instance FromJSON Coord where
@@ -205,9 +242,9 @@ class FromJSON a where
205 default parseJSON :: (Generic a, GFromJSON (Rep a)) => Value -> Parser a
242 default parseJSON :: (Generic a, GFromJSON (Rep a)) => Value -> Parser a
206 parseJSON = genericParseJSON defaultOptions
243 parseJSON = genericParseJSON defaultOptions
208−-- | A key-value pair for a JSON object.
245+-- | A key-value pair for encoding a JSON object.
209−class KeyValue t where
246+class KeyValue kv where
210− (.=) :: ToJSON v => Text -> v -> t
247+ (.=) :: ToJSON v => Text -> v -> kv
211 infixr 8 .=
248 infixr 8 .=
213-- | Fail parsing due to a type mismatch, with a descriptive message.
250-- | Fail parsing due to a type mismatch, with a descriptive message.
@@ -215,7 +252,6 @@ class KeyValue t where
215-- Example usage:
252-- Example usage:
219-- instance FromJSON Coord where
255-- instance FromJSON Coord where
220-- parseJSON ('Object' v) = {- type matches, life is good -}
256-- parseJSON ('Object' v) = {- type matches, life is good -}
221-- parseJSON wat = 'typeMismatch' \"Coord\" wat
257-- parseJSON wat = 'typeMismatch' \"Coord\" wat