    {-# LANGUAGE CPP, DefaultSignatures, FlexibleContexts #-}

    -- |
    -- Module:      Data.Aeson.Types.Class
    -- Copyright:   (c) 2011-2015 Bryan O'Sullivan
    --              (c) 2011 MailRank, Inc.
    -- License:     Apache
    -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>
    -- Stability:   experimental
    -- Portability: portable
    --
    -- Types for working with JSON data.

    module Data.Aeson.Types.Class
        (
        -- * Core JSON classes
          FromJSON(..)
        , ToJSON(..)
        -- * Generic JSON classes
        , GFromJSON(..)
        , GToJSON(..)
        , genericToJSON
        , genericToEncoding
        , genericParseJSON
        -- * Object key-value pairs
        , KeyValue(..)
        -- * Functions needed for documentation
        , typeMismatch
        ) where

    import Data.Aeson.Types.Internal
    import Data.Text (Text)
    import GHC.Generics (Generic, Rep, from, to)
    import qualified Data.Aeson.Encode.Builder as E

    -- | Class of generic representation types ('Rep') that can be converted to
    -- JSON.
    class GToJSON f where
        -- | This method (applied to 'defaultOptions') is used as the
        -- default generic implementation of 'toJSON'.
        gToJSON :: Options -> f a -> Value

        -- | This method (applied to 'defaultOptions') can be used as the
        -- default generic implementation of 'toEncoding'.
        gToEncoding :: Options -> f a -> Encoding

    -- | Class of generic representation types ('Rep') that can be converted from JSON.
    class GFromJSON f where
        -- | This method (applied to 'defaultOptions') is used as the
        -- default generic implementation of 'parseJSON'.
        gParseJSON :: Options -> Value -> Parser (f a)

    -- | A configurable generic JSON creator. This function applied to
    -- 'defaultOptions' is used as the default for 'toJSON' when the type
    -- is an instance of 'Generic'.
    genericToJSON :: (Generic a, GToJSON (Rep a)) => Options -> a -> Value
    genericToJSON opts = gToJSON opts . from

    -- | A configurable generic JSON encoder. This function applied to
    -- 'defaultOptions' is used as the default for 'toEncoding' when the type
    -- is an instance of 'Generic'.
    genericToEncoding :: (Generic a, GToJSON (Rep a)) => Options -> a -> Encoding
    genericToEncoding opts = gToEncoding opts . from

    -- | A configurable generic JSON decoder. This function applied to
    -- 'defaultOptions' is used as the default for 'parseJSON' when the
    -- type is an instance of 'Generic'.
    genericParseJSON :: (Generic a, GFromJSON (Rep a)) => Options -> Value -> Parser a
    genericParseJSON opts = fmap to . gParseJSON opts

    -- | A type that can be converted to JSON.
    --
    -- An example type and instance:
    --
    -- @
    -- \-- Allow ourselves to write 'Text' literals.
    -- {-\# LANGUAGE OverloadedStrings #-}
    --
    -- data Coord = Coord { x :: Double, y :: Double }
    --
    -- instance ToJSON Coord where
    --   toJSON (Coord x y) = 'object' [\"x\" '.=' x, \"y\" '.=' y]
    --
    --   toEncoding (Coord x y) = 'pairs' (\"x\" '.=' x '<>' \"y\" '.=' y)
    -- @
    --
    -- Instead of manually writing your 'ToJSON' instance, there are two options
    -- to do it automatically:
    --
    -- * "Data.Aeson.TH" provides Template Haskell functions which will derive an
    -- instance at compile time. The generated instance is optimized for your type
    -- so will probably be more efficient than the following two options:
    --
    -- * The compiler can provide a default generic implementation for
    -- 'toJSON'.
    --
    -- To use the second, simply add a @deriving 'Generic'@ clause to your
    -- datatype and declare a 'ToJSON' instance for your datatype without giving
    -- definitions for 'toJSON' or 'toEncoding'.
    --
    -- For example, the previous example can be simplified to a more
    -- minimal instance:
    --
    -- @
    -- {-\# LANGUAGE DeriveGeneric \#-}
    --
    -- import "GHC.Generics"
    --
    -- data Coord = Coord { x :: Double, y :: Double } deriving 'Generic'
    --
    -- instance ToJSON Coord where
    --     toEncoding = 'genericToEncoding' 'defaultOptions'
    -- @
    --
    -- Why do we provide an implementation for 'toEncoding' here?  The
    -- 'toEncoding' function is a relatively new addition to this class.
    -- To allow users of older versions of this library to upgrade without
    -- having to edit all of their instances or encounter surprising
    -- incompatibilities, the default implementation of 'toEncoding' uses
    -- 'toJSON'.  This produces correct results, but since it performs an
    -- intermediate conversion to a 'Value', it will be less efficient
    -- than directly emitting an 'Encoding'.  Our one-liner definition of
    -- 'toEncoding' above bypasses the intermediate 'Value'.
    --
    -- If @DefaultSignatures@ doesn't give exactly the results you want,
    -- you can customize the generic encoding with only a tiny amount of
    -- effort, using 'genericToJSON' and 'genericToEncoding' with your
    -- preferred 'Options':
    --
    -- @
    -- instance ToJSON Coord where
    --     toJSON     = 'genericToJSON' 'defaultOptions'
    --     toEncoding = 'genericToEncoding' 'defaultOptions'
    -- @
    class ToJSON a where
        -- | Convert a Haskell value to a JSON-friendly intermediate type.
        toJSON     :: a -> Value
        {-# MINIMAL toJSON #-}

        default toJSON :: (Generic a, GToJSON (Rep a)) => a -> Value
        toJSON = genericToJSON defaultOptions

        -- | Encode a Haskell value as JSON.
        --
        -- The default implementation of this method creates an
        -- intermediate 'Value' using 'toJSON'.  This provides
        -- source-level compatibility for people upgrading from older
        -- versions of this library, but obviously offers no performance
        -- advantage.
        --
        -- To benefit from direct encoding, you /must/ provide an
        -- implementation for this method.  The easiest way to do so is by
        -- having your types implement 'Generic' using the @DeriveGeneric@
        -- extension, and then have GHC generate a method body as follows.
        --
        -- @
        -- instance ToJSON Coord where
        --     toEncoding = 'genericToEncoding' 'defaultOptions'
        -- @

        toEncoding :: a -> Encoding
        toEncoding = Encoding . E.encodeToBuilder . toJSON
        {-# INLINE toEncoding #-}

    -- | A type that can be converted from JSON, with the possibility of
    -- failure.
    --
    -- In many cases, you can get the compiler to generate parsing code
    -- for you (see below).  To begin, let's cover writing an instance by
    -- hand.
    --
    -- There are various reasons a conversion could fail.  For example, an
    -- 'Object' could be missing a required key, an 'Array' could be of
    -- the wrong size, or a value could be of an incompatible type.
    --
    -- The basic ways to signal a failed conversion are as follows:
    --
    -- * 'empty' and 'mzero' work, but are terse and uninformative
    --
    -- * 'fail' yields a custom error message
    --
    -- * 'typeMismatch' produces an informative message for cases when the
    -- value encountered is not of the expected type
    --
    -- An example type and instance:
    --
    -- @
    -- \-- Allow ourselves to write 'Text' literals.
    -- {-\# LANGUAGE OverloadedStrings #-}
    --
    -- data Coord = Coord { x :: Double, y :: Double }
    --
    -- instance FromJSON Coord where
    --   parseJSON ('Object' v) = Coord    '<$>'
    --                          v '.:' \"x\" '<*>'
    --                          v '.:' \"y\"
    --
    --   \-- We do not expect a non-'Object' value here.
    --   \-- We could use 'mzero' to fail, but 'typeMismatch'
    --   \-- gives a much more informative error message.
    --   parseJSON invalid    = 'typeMismatch' \"Coord\" invalid
    -- @
    --
    -- Instead of manually writing your 'FromJSON' instance, there are two options
    -- to do it automatically:
    --
    -- * "Data.Aeson.TH" provides Template Haskell functions which will derive an
    -- instance at compile time. The generated instance is optimized for your type
    -- so will probably be more efficient than the following two options:
    --
    -- * The compiler can provide a default generic implementation for
    -- 'parseJSON'.
    --
    -- To use the second, simply add a @deriving 'Generic'@ clause to your
    -- datatype and declare a 'FromJSON' instance for your datatype without giving
    -- a definition for 'parseJSON'.
    --
    -- For example, the previous example can be simplified to just:
    --
    -- @
    -- {-\# LANGUAGE DeriveGeneric \#-}
    --
    -- import "GHC.Generics"
    --
    -- data Coord = Coord { x :: Double, y :: Double } deriving 'Generic'
    --
    -- instance FromJSON Coord
    -- @
    --
    -- If @DefaultSignatures@ doesn't give exactly the results you want,
    -- you can customize the generic decoding with only a tiny amount of
    -- effort, using 'genericParseJSON' with your preferred 'Options':
    --
    -- @
    -- instance FromJSON Coord where
    --     parseJSON = 'genericParseJSON' 'defaultOptions'
    -- @

    class FromJSON a where
        parseJSON :: Value -> Parser a

        default parseJSON :: (Generic a, GFromJSON (Rep a)) => Value -> Parser a
        parseJSON = genericParseJSON defaultOptions

    -- | A key-value pair for encoding a JSON object.
    class KeyValue kv where
        (.=) :: ToJSON v => Text -> v -> kv
        infixr 8 .=

    -- | Fail parsing due to a type mismatch, with a descriptive message.
    --
    -- Example usage:
    --
    -- @
    -- instance FromJSON Coord where
    --   parseJSON ('Object' v) = {- type matches, life is good -}
    --   parseJSON wat        = 'typeMismatch' \"Coord\" wat
    -- @
    typeMismatch :: String -- ^ The name of the type you are trying to parse.
                 -> Value  -- ^ The actual value encountered.
                 -> Parser a
    typeMismatch expected actual =
        fail $ "expected " ++ expected ++ ", encountered " ++ name
      where
        name = case actual of
                 Object _ -> "Object"
                 Array _  -> "Array"
                 String _ -> "String"
                 Number _ -> "Number"
                 Bool _   -> "Boolean"
                 Null     -> "Null"