Skip to content

Instantly share code, notes, and snippets.

@RyanGlScott
Created July 28, 2015 18:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RyanGlScott/913bc56984322fa4bc59 to your computer and use it in GitHub Desktop.
Save RyanGlScott/913bc56984322fa4bc59 to your computer and use it in GitHub Desktop.
FromJSONWith
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
Module: Deep
Copyright: (c) 2015 Ryan Scott
License: BSD3
Stability: Experimental
Portability: Template Haskell
A demonstration of what 'deriveJSONWith' can do.
-}
module Deep where
import Data.Foldable.Deriving
import FromJSONWithTH
data E a where
Bool :: a ~ Bool => a -> E a
And :: a ~ Bool => E a -> E a -> E a
Or :: a ~ Bool => E a -> E a -> E a
Int :: a ~ Int => a -> E a
Add :: a ~ Int => E a -> E a -> E a
deriving instance Eq (E a)
deriving instance Ord (E a)
deriving instance Show (E a)
-- TODO: Replace with DeriveFoldable in GHC 7.12
deriveFoldable ''E
deriveJSONWith defaultOptions ''E
data RE a = RBool Bool
| RAnd (RE Bool) (RE Bool)
| ROr (RE Bool) (RE Bool)
| RInt Int
| RAdd (RE Int) (RE Int)
deriving (Eq, Ord, Read, Show)
deriveJSON remoteOptions ''RE
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE IncoherentInstances #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-|
Module: FromJSONWith
Copyright: (c) 2011, 2012 Bryan O'Sullivan
(c) 2011 MailRank, Inc.
(c) 2015 Ryan Scott
License: BSD3
Stability: Experimental
Portability: Loads of extensions
A minor twist on 'FromJSON'.
-}
module FromJSONWith where
import Control.Monad (zipWithM)
import Data.Aeson
import Data.Aeson.Types (Parser, parse)
import Data.Either.Combinators (isLeft, isRight, fromLeft', fromRight')
import Data.Fixed
import Data.Functor.Identity (Identity(..))
import Data.Hashable (Hashable)
import qualified Data.HashMap.Strict as H (empty, foldrWithKey, insert, intersectionWith, lookup, toList)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashSet as HashSet (fromList, toList)
import Data.HashSet (HashSet)
import Data.Int
import qualified Data.IntMap as IntMap (fromList, toList)
import Data.IntMap (IntMap)
import Data.IntSet (IntSet)
import qualified Data.Map as M (empty, foldrWithKey, insert)
import Data.Map (Map)
import Data.Monoid
import Data.Primitive.Types (Prim)
import Data.Ratio
import Data.Scientific (Scientific)
import qualified Data.Sequence as Seq (fromList, zip)
import Data.Sequence (Seq)
import qualified Data.Set as Set (fromList, toList)
import Data.Set (Set)
import qualified Data.Text as S (Text, pack, unpack)
import qualified Data.Text.Lazy as L (Text, fromStrict, toStrict)
import Data.Time
import Data.Tree (Tree(..))
import qualified Data.Vector.Generic as G (Vector, convert)
import qualified Data.Vector.Unboxed as U (Vector)
import qualified Data.Vector.Primitive as P (Vector)
import qualified Data.Vector.Storable as S (Vector)
import qualified Data.Vector as V (Vector, convert, length, toList, unsafeIndex, zipWithM)
import Data.Word
import Foreign.Storable (Storable)
class FromJSONWith a where
parseJSONWith :: a -> Value -> Parser a
default parseJSONWith :: FromJSON a => a -> Value -> Parser a
parseJSONWith = const parseJSON
-- | Convert a value from JSON, failing if the types do not match.
fromJSONWith :: FromJSONWith a => a -> Value -> Result a
fromJSONWith e = parse $ parseJSONWith e
{-# INLINE fromJSONWith #-}
-- | Retrieve the value associated with the given key of an 'Object'.
-- The result is 'empty' if the key is not present or the value cannot
-- be converted to the desired type.
--
-- This accessor is appropriate if the key and value /must/ be present
-- in an object for it to be valid. If the key and value are
-- optional, use 'retrieveWithMaybe' instead.
retrieveWith :: FromJSONWith a => a -> Object -> S.Text -> Parser a
retrieveWith e obj key = case H.lookup key obj of
Nothing -> fail $ "key " ++ show key ++ " not present"
Just v -> parseJSONWith e v
{-# INLINE retrieveWith #-}
-- | Retrieve the value associated with the given key of an 'Object'.
-- The result is 'Nothing' if the key is not present, or 'empty' if
-- the value cannot be converted to the desired type.
--
-- This accessor is most useful if the key and value can be absent
-- from an object without affecting its validity. If the key and
-- value are mandatory, use 'retrieveWith' instead.
retrieveWithMaybe :: FromJSONWith a => Maybe a -> Object -> S.Text -> Parser (Maybe a)
retrieveWithMaybe e obj key = case H.lookup key obj of
Nothing -> pure Nothing
Just v -> parseJSONWith e v
{-# INLINE retrieveWithMaybe #-}
instance FromJSONWith Bool
instance FromJSONWith Char
instance FromJSONWith Double
instance FromJSONWith Float
instance FromJSONWith Int
instance FromJSONWith Int8
instance FromJSONWith Int16
instance FromJSONWith Int32
instance FromJSONWith Int64
instance FromJSONWith Integer
instance FromJSONWith Word
instance FromJSONWith Word8
instance FromJSONWith Word16
instance FromJSONWith Word32
instance FromJSONWith Word64
instance FromJSONWith ()
instance FromJSONWith S.Text
instance FromJSONWith L.Text
instance FromJSONWith IntSet
instance FromJSONWith Scientific
instance FromJSONWith ZonedTime
instance FromJSONWith UTCTime
instance FromJSONWith DotNetTime
instance FromJSONWith Value
instance FromJSONWith [Char]
instance FromJSONWith (Ratio Integer)
instance HasResolution a => FromJSONWith (Fixed a)
instance FromJSONWith a => FromJSONWith (Maybe a) where
parseJSONWith _ Null = pure Nothing
parseJSONWith (Just e) a = Just <$> parseJSONWith e a
parseJSONWith Nothing _ = fail "Invariant failure."
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith [a] where
parseJSONWith l = withArray "[a]" $ zipWithM parseJSONWith l . V.toList
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith (Dual a) where
parseJSONWith (Dual d) = fmap Dual . parseJSONWith d
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith (First a) where
parseJSONWith (First f) = fmap First . parseJSONWith f
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith (Last a) where
parseJSONWith (Last l) = fmap Last . parseJSONWith l
{-# INLINE parseJSONWith #-}
left, right :: S.Text
left = "Left"
right = "Right"
instance (FromJSONWith a, FromJSONWith b) => FromJSONWith (Either a b) where
parseJSONWith lr (Object (H.toList -> [(key, value)]))
| isLeft lr && key == left = Left <$> parseJSONWith (fromLeft' lr) value
| isRight lr && key == right = Right <$> parseJSONWith (fromRight' lr) value
parseJSONWith _ _ = fail $
"expected an object with a single property " ++
"where the property key should be either " ++
"\"Left\" or \"Right\""
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith (Identity a) where
parseJSONWith (Identity i) a = Identity <$> parseJSONWith i a
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith (IntMap a) where
parseJSONWith e = fmap IntMap.fromList . parseJSONWith (IntMap.toList e)
{-# INLINE parseJSONWith #-}
instance (Ord a, FromJSONWith a) => FromJSONWith (Set a) where
parseJSONWith s = fmap Set.fromList . parseJSONWith (Set.toList s)
{-# INLINE parseJSONWith #-}
instance FromJSONWith v => FromJSONWith (Tree v) where
parseJSONWith (Node a l) j = uncurry Node <$> parseJSONWith (a, l) j
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith (Seq a) where
parseJSONWith s = withArray "Seq a" $ traverse (uncurry parseJSONWith) . Seq.zip s . Seq.fromList . V.toList
{-# INLINE parseJSONWith #-}
instance (Eq a, Hashable a, FromJSONWith a) => FromJSONWith (HashSet a) where
parseJSONWith hs = fmap HashSet.fromList . parseJSONWith (HashSet.toList hs)
{-# INLINE parseJSONWith #-}
instance FromJSONWith a => FromJSONWith (V.Vector a) where
parseJSONWith v = withArray "Vector a" $ V.zipWithM parseJSONWith v
{-# INLINE parseJSONWith #-}
vectorParseJSONWith :: (FromJSONWith a, G.Vector w a) => String -> w a -> Value -> Parser (w a)
vectorParseJSONWith s v = withArray s $ fmap V.convert . V.zipWithM parseJSONWith (G.convert v)
{-# INLINE vectorParseJSONWith #-}
instance (G.Vector U.Vector a, FromJSONWith a) => FromJSONWith (U.Vector a) where
parseJSONWith = vectorParseJSONWith "Data.Vector.Unboxed.Vector a"
instance (Storable a, FromJSONWith a) => FromJSONWith (S.Vector a) where
parseJSONWith = vectorParseJSONWith "Data.Vector.Storable.Vector a"
instance (Prim a, FromJSONWith a) => FromJSONWith (P.Vector a) where
parseJSONWith = vectorParseJSONWith "Data.Vector.Primitive.Vector a"
mapHash :: (Eq k, Hashable k) => Map k v -> HashMap k v
mapHash = mapHashKey id
{-# INLINE mapHash #-}
-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys.
mapHashKey :: (Eq k2, Hashable k2) => (k1 -> k2)
-> Map k1 v -> HashMap k2 v
mapHashKey kv = M.foldrWithKey (H.insert . kv) H.empty
{-# INLINE mapHashKey #-}
-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys and values.
mapHashKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)
-> Map k1 v1 -> HashMap k2 v2
mapHashKeyVal fk kv = M.foldrWithKey (\k v -> H.insert (fk k) (kv v)) H.empty
{-# INLINE mapHashKeyVal #-}
hashMap :: Ord k => HashMap k v -> Map k v
hashMap = hashMapKey id
{-# INLINE hashMap #-}
-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys.
hashMapKey :: (Ord k2) => (k1 -> k2)
-> HashMap k1 v -> Map k2 v
hashMapKey kv = H.foldrWithKey (M.insert . kv) M.empty
{-# INLINE hashMapKey #-}
-- | Transform the keys and values of a 'H.HashMap'.
mapKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)
-> HashMap k1 v1 -> HashMap k2 v2
mapKeyVal fk kv = H.foldrWithKey (\k v -> H.insert (fk k) (kv v)) H.empty
{-# INLINE mapKeyVal #-}
-- | Transform the keys of a 'H.HashMap'.
mapKey :: (Eq k2, Hashable k2) => (k1 -> k2) -> HashMap k1 v -> HashMap k2 v
mapKey fk = mapKeyVal fk id
{-# INLINE mapKey #-}
instance FromJSONWith v => FromJSONWith (Map String v) where
parseJSONWith m = fmap (hashMapKey S.unpack) . parseJSONWith (mapHashKey S.pack m)
instance FromJSONWith v => FromJSONWith (Map S.Text v) where
parseJSONWith m = withObject "Map Text a" $
fmap hashMap . traverse (uncurry parseJSONWith) . H.intersectionWith (,) (mapHash m)
instance FromJSONWith v => FromJSONWith (Map L.Text v) where
parseJSONWith m = fmap (hashMapKey L.fromStrict) . parseJSONWith (mapHashKey L.toStrict m)
instance FromJSONWith v => FromJSONWith (HashMap String v) where
parseJSONWith hm = fmap (mapKey S.unpack) . parseJSONWith (mapKey S.pack hm)
instance FromJSONWith v => FromJSONWith (HashMap S.Text v) where
parseJSONWith hm = withObject "HashMap Text a" $ traverse (uncurry parseJSONWith) . H.intersectionWith (,) hm
instance FromJSONWith v => FromJSONWith (HashMap L.Text v) where
parseJSONWith hm = fmap (mapKey L.fromStrict) . parseJSONWith (mapKey L.toStrict hm)
instance (FromJSONWith a, FromJSONWith b) => FromJSONWith (a, b) where
parseJSONWith (a, b) = withArray "(a,b)" $ \ab ->
let n = V.length ab
in if n == 2
then (,) <$> parseJSONWith a (V.unsafeIndex ab 0)
<*> parseJSONWith b (V.unsafeIndex ab 1)
else fail $ "cannot unpack array of length " ++
show n ++ " into a pair"
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c) => FromJSONWith (a, b, c) where
parseJSONWith (a, b, c) = withArray "(a,b,c)" $ \abc ->
let n = V.length abc
in if n == 3
then (,,) <$> parseJSONWith a (V.unsafeIndex abc 0)
<*> parseJSONWith b (V.unsafeIndex abc 1)
<*> parseJSONWith c (V.unsafeIndex abc 2)
else fail $ "cannot unpack array of length " ++
show n ++ " into a 3-tuple"
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d) => FromJSONWith (a, b, c, d) where
parseJSONWith (a, b, c, d) = withArray "(a,b,c,d)" $ \abcd ->
let n = V.length abcd
in if n == 4
then (,,,) <$> parseJSONWith a (V.unsafeIndex abcd 0)
<*> parseJSONWith b (V.unsafeIndex abcd 1)
<*> parseJSONWith c (V.unsafeIndex abcd 2)
<*> parseJSONWith d (V.unsafeIndex abcd 3)
else fail $ "cannot unpack array of length " ++
show n ++ " into a 4-tuple"
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e) => FromJSONWith (a, b, c, d, e) where
parseJSONWith (a, b, c, d, e) = withArray "(a,b,c,d,e)" $ \abcde ->
let n = V.length abcde
in if n == 5
then (,,,,) <$> parseJSONWith a (V.unsafeIndex abcde 0)
<*> parseJSONWith b (V.unsafeIndex abcde 1)
<*> parseJSONWith c (V.unsafeIndex abcde 2)
<*> parseJSONWith d (V.unsafeIndex abcde 3)
<*> parseJSONWith e (V.unsafeIndex abcde 4)
else fail $ "cannot unpack array of length " ++
show n ++ " into a 5-tuple"
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f) => FromJSONWith (a, b, c, d, e, f) where
parseJSONWith (a, b, c, d, e, f) = withArray "(a,b,c,d,e,f)" $ \abcdef ->
let n = V.length abcdef
in if n == 6
then (,,,,,) <$> parseJSONWith a (V.unsafeIndex abcdef 0)
<*> parseJSONWith b (V.unsafeIndex abcdef 1)
<*> parseJSONWith c (V.unsafeIndex abcdef 2)
<*> parseJSONWith d (V.unsafeIndex abcdef 3)
<*> parseJSONWith e (V.unsafeIndex abcdef 4)
<*> parseJSONWith f (V.unsafeIndex abcdef 5)
else fail $ "cannot unpack array of length " ++
show n ++ " into a 6-tuple"
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g) => FromJSONWith (a, b, c, d, e, f, g) where
parseJSONWith (a, b, c, d, e, f, g) = withArray "(a,b,c,d,e,f,g)" $ \abcdefg ->
let n = V.length abcdefg
in if n == 7
then (,,,,,,) <$> parseJSONWith a (V.unsafeIndex abcdefg 0)
<*> parseJSONWith b (V.unsafeIndex abcdefg 1)
<*> parseJSONWith c (V.unsafeIndex abcdefg 2)
<*> parseJSONWith d (V.unsafeIndex abcdefg 3)
<*> parseJSONWith e (V.unsafeIndex abcdefg 4)
<*> parseJSONWith f (V.unsafeIndex abcdefg 5)
<*> parseJSONWith g (V.unsafeIndex abcdefg 6)
else fail $ "cannot unpack array of length " ++
show n ++ " into a 7-tuple"
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h) => FromJSONWith (a, b, c, d, e, f, g, h) where
parseJSONWith (a, b, c, d, e, f, g, h) = withArray "(a,b,c,d,e,f,g,h)" $ \ary ->
let n = V.length ary
in if n /= 8
then fail $ "cannot unpack array of length " ++
show n ++ " into an 8-tuple"
else (,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h, FromJSONWith i) => FromJSONWith (a, b, c, d, e, f, g, h, i) where
parseJSONWith (a, b, c, d, e, f, g, h, i) = withArray "(a,b,c,d,e,f,g,h,i)" $ \ary ->
let n = V.length ary
in if n /= 9
then fail $ "cannot unpack array of length " ++
show n ++ " into a 9-tuple"
else (,,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
<*> parseJSONWith i (V.unsafeIndex ary 8)
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h, FromJSONWith i, FromJSONWith j) => FromJSONWith (a, b, c, d, e, f, g, h, i, j) where
parseJSONWith (a, b, c, d, e, f, g, h, i, j) = withArray "(a,b,c,d,e,f,g,h,i,j)" $ \ary ->
let n = V.length ary
in if n /= 10
then fail $ "cannot unpack array of length " ++
show n ++ " into a 10-tuple"
else (,,,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
<*> parseJSONWith i (V.unsafeIndex ary 8)
<*> parseJSONWith j (V.unsafeIndex ary 9)
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h, FromJSONWith i, FromJSONWith j, FromJSONWith k) => FromJSONWith (a, b, c, d, e, f, g, h, i, j, k) where
parseJSONWith (a, b, c, d, e, f, g, h, i, j, k) = withArray "(a,b,c,d,e,f,g,h,i,j,k)" $ \ary ->
let n = V.length ary
in if n /= 11
then fail $ "cannot unpack array of length " ++
show n ++ " into an 11-tuple"
else (,,,,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
<*> parseJSONWith i (V.unsafeIndex ary 8)
<*> parseJSONWith j (V.unsafeIndex ary 9)
<*> parseJSONWith k (V.unsafeIndex ary 10)
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h, FromJSONWith i, FromJSONWith j, FromJSONWith k, FromJSONWith l) => FromJSONWith (a, b, c, d, e, f, g, h, i, j, k, l) where
parseJSONWith (a, b, c, d, e, f, g, h, i, j, k, l) = withArray "(a,b,c,d,e,f,g,h,i,j,k,l)" $ \ary ->
let n = V.length ary
in if n /= 12
then fail $ "cannot unpack array of length " ++
show n ++ " into a 12-tuple"
else (,,,,,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
<*> parseJSONWith i (V.unsafeIndex ary 8)
<*> parseJSONWith j (V.unsafeIndex ary 9)
<*> parseJSONWith k (V.unsafeIndex ary 10)
<*> parseJSONWith l (V.unsafeIndex ary 11)
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h, FromJSONWith i, FromJSONWith j, FromJSONWith k, FromJSONWith l, FromJSONWith m) => FromJSONWith (a, b, c, d, e, f, g, h, i, j, k, l, m) where
parseJSONWith (a, b, c, d, e, f, g, h, i, j, k, l, m) = withArray "(a,b,c,d,e,f,g,h,i,j,k,l,m)" $ \ary ->
let n = V.length ary
in if n /= 13
then fail $ "cannot unpack array of length " ++
show n ++ " into a 13-tuple"
else (,,,,,,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
<*> parseJSONWith i (V.unsafeIndex ary 8)
<*> parseJSONWith j (V.unsafeIndex ary 9)
<*> parseJSONWith k (V.unsafeIndex ary 10)
<*> parseJSONWith l (V.unsafeIndex ary 11)
<*> parseJSONWith m (V.unsafeIndex ary 12)
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h, FromJSONWith i, FromJSONWith j, FromJSONWith k, FromJSONWith l, FromJSONWith m, FromJSONWith n) => FromJSONWith (a, b, c, d, e, f, g, h, i, j, k, l, m, n) where
parseJSONWith (a, b, c, d, e, f, g, h, i, j, k, l, m, n) = withArray "(a,b,c,d,e,f,g,h,i,j,k,l,m,n)" $ \ary ->
let num = V.length ary
in if num /= 14
then fail $ "cannot unpack array of length " ++
show num ++ " into a 14-tuple"
else (,,,,,,,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
<*> parseJSONWith i (V.unsafeIndex ary 8)
<*> parseJSONWith j (V.unsafeIndex ary 9)
<*> parseJSONWith k (V.unsafeIndex ary 10)
<*> parseJSONWith l (V.unsafeIndex ary 11)
<*> parseJSONWith m (V.unsafeIndex ary 12)
<*> parseJSONWith n (V.unsafeIndex ary 13)
{-# INLINE parseJSONWith #-}
instance (FromJSONWith a, FromJSONWith b, FromJSONWith c, FromJSONWith d, FromJSONWith e, FromJSONWith f, FromJSONWith g, FromJSONWith h, FromJSONWith i, FromJSONWith j, FromJSONWith k, FromJSONWith l, FromJSONWith m, FromJSONWith n, FromJSONWith o) => FromJSONWith (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) where
parseJSONWith (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = withArray "(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)" $ \ary ->
let num = V.length ary
in if num /= 15
then fail $ "cannot unpack array of length " ++
show num ++ " into a 15-tuple"
else (,,,,,,,,,,,,,,)
<$> parseJSONWith a (V.unsafeIndex ary 0)
<*> parseJSONWith b (V.unsafeIndex ary 1)
<*> parseJSONWith c (V.unsafeIndex ary 2)
<*> parseJSONWith d (V.unsafeIndex ary 3)
<*> parseJSONWith e (V.unsafeIndex ary 4)
<*> parseJSONWith f (V.unsafeIndex ary 5)
<*> parseJSONWith g (V.unsafeIndex ary 6)
<*> parseJSONWith h (V.unsafeIndex ary 7)
<*> parseJSONWith i (V.unsafeIndex ary 8)
<*> parseJSONWith j (V.unsafeIndex ary 9)
<*> parseJSONWith k (V.unsafeIndex ary 10)
<*> parseJSONWith l (V.unsafeIndex ary 11)
<*> parseJSONWith m (V.unsafeIndex ary 12)
<*> parseJSONWith n (V.unsafeIndex ary 13)
<*> parseJSONWith o (V.unsafeIndex ary 14)
{-# INLINE parseJSONWith #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE IncoherentInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
-- Uncomment this for GHCi purposes
-- #define MIN_VERSION_template_haskell(x,y,z) 1
{-|
Module: FromJSONWithTH
Copyright: (c) 2011, 2012 Bryan O'Sullivan
(c) 2011 MailRank, Inc.
(c) 2015 Ryan Scott
License: BSD3
Stability: Experimental
Portability: Template Haskell
Functions to mechanically derive 'ToJSON' and 'FromJSONWith' instances.
This is mostly identical to 'Data.Aeson.TH', except with 'FromJSON' gutted
out and replaced with 'FromJSONWith'.
-}
module FromJSONWithTH
( -- * Encoding configuration
Options(..), SumEncoding(..)
, defaultOptions, defaultTaggedObject, remoteOptions
-- * FromJSON, FromJSONWith and ToJSON derivation
, deriveJSONWith
, deriveFromJSONWith
, mkParseJSONWith
, deriveJSON
, deriveToJSON
, mkToJSON
, deriveFromJSON
, mkParseJSON
) where
--------------------------------------------------------------------------------
-- Imports
--------------------------------------------------------------------------------
-- from aeson:
import Data.Aeson ( Object )
import FromJSONWith ( FromJSONWith(..), retrieveWith, retrieveWithMaybe )
import Data.Aeson.TH ( deriveJSON, deriveToJSON, mkToJSON
, deriveFromJSON, mkParseJSON
)
import Data.Aeson.Types ( Value(..), Parser
, Options(..)
, SumEncoding(..)
, defaultOptions
, defaultTaggedObject
)
-- from base:
import Control.Applicative ( pure, (<$>), (<*>) )
import Control.Monad ( return, liftM2, fail, replicateM )
import Data.Bool ( Bool(False, True), otherwise, (&&) )
import Data.Eq ( (==) )
import Data.Function ( ($), (.) )
import Data.Functor ( fmap )
import Data.Int ( Int )
import Data.Either ( Either(Left, Right) )
import Data.List ( (++), foldl, foldl', intercalate
, length, map, zip, genericLength, all
)
import Data.Maybe ( Maybe(Nothing, Just) )
import Prelude ( String, error, undefined )
import Text.Printf ( printf )
import Text.Show ( show )
-- from unordered-containers:
import qualified Data.HashMap.Strict as H ( lookup, toList )
-- from template-haskell:
import Language.Haskell.TH
import Language.Haskell.TH.Syntax ( VarStrictType )
-- from text:
import qualified Data.Text as T ( Text, pack, unpack )
-- from vector:
import qualified Data.Vector as V ( unsafeIndex, null, length )
--------------------------------------------------------------------------------
-- Convenience
--------------------------------------------------------------------------------
-- | Generates both 'ToJSON' and 'FromJSONWith' instance declarations for the
-- given data type.
--
-- This is a convienience function which is equivalent to calling both
-- 'deriveToJSON' and 'deriveFromJSONWith'.
deriveJSONWith :: Options
-- ^ Encoding options.
-> Name
-- ^ Name of the type for which to generate 'ToJSON' and 'FromJSONWith'
-- instances.
-> Q [Dec]
deriveJSONWith opts name =
liftM2 (++)
(deriveToJSON opts name)
(deriveFromJSONWith opts name)
remoteOptions :: Options
remoteOptions = defaultOptions {
constructorTagModifier = \conTag -> case conTag of
('R':rest) -> rest
_ -> error "Wrong constructor prefix"
, fieldLabelModifier = \fieldLabel -> case fieldLabel of
('r':rest) -> rest
_ -> error "Wrong field label prefix"
}
--------------------------------------------------------------------------------
-- FromJSONWith
--------------------------------------------------------------------------------
-- | Generates a 'FromJSONWith' instance declaration for the given data type.
deriveFromJSONWith :: Options
-- ^ Encoding options.
-> Name
-- ^ Name of the type for which to generate a 'FromJSONWith' instance
-- declaration.
-> Q [Dec]
deriveFromJSONWith opts name =
withType name $ \tvbs cons -> fmap (:[]) $ fromCons tvbs cons
where
fromCons :: [TyVarBndr] -> [Con] -> Q Dec
fromCons tvbs cons =
instanceD (applyCon ''FromJSONWith typeNames)
(classType `appT` instanceType)
[ funD 'parseJSONWith
[ clause []
(normalB $ consFromJSONWith name opts cons)
[]
]
]
where
classType = conT ''FromJSONWith
typeNames = map tvbName tvbs
instanceType = foldl' appT (conT name) $ map varT typeNames
-- | Generates a lambda expression which parses the JSON encoding of the given
-- data type.
mkParseJSONWith :: Options -- ^ Encoding options.
-> Name -- ^ Name of the encoded type.
-> Q Exp
mkParseJSONWith opts name =
withType name (\_ cons -> consFromJSONWith name opts cons)
-- | Helper function used by both 'deriveFromJSONWith' and 'mkParseJSONWith'. Generates
-- code to parse the JSON encoding of a number of constructors. All constructors
-- must be from the same type.
consFromJSONWith :: Name
-- ^ Name of the type to which the constructors belong.
-> Options
-- ^ Encoding options
-> [Con]
-- ^ Constructors for which to generate JSON parsing code.
-> Q Exp
consFromJSONWith _ _ [] = error $ "Data.Aeson.FromJSONWith.TH.consFromJSONWith: "
++ "Not a single constructor given!"
consFromJSONWith tName opts [con] = do
ref <- newName "ref"
value <- newName "value"
rs <- replicateM (numFields con) $ newName "_r"
lamE [varP ref, varP value] (parseArgs tName opts con ref rs (Right value))
consFromJSONWith tName opts cons = do
ref <- newName "ref"
value <- newName "value"
let allNullaryMatches conName =
[ do txt <- newName "txt"
match (conP 'String [varP txt])
(guardedB $
[ liftM2 (,) (normalG $
infixApp (varE txt)
[|(==)|]
([|T.pack|] `appE`
conStringE opts conName)
)
([|pure|] `appE` conE conName)
]
++
[ liftM2 (,)
(normalG [|otherwise|])
( [|noMatchFail|]
`appE` (varE ref)
`appE` (litE $ stringL $ show tName)
`appE` ([|T.unpack|] `appE` varE txt)
)
]
)
[]
, do other <- newName "other"
match (varP other)
(normalB $ [|noStringFail|]
`appE` (varE ref)
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
mixedMatches con rs =
case sumEncoding opts of
TaggedObject {tagFieldName, contentsFieldName} ->
parseObject $ parseTaggedObject con rs tagFieldName contentsFieldName
ObjectWithSingleField ->
parseObject $ parseObjectWithSingleField con rs
TwoElemArray ->
[ do arr <- newName "array"
match (conP 'Array [varP arr])
(guardedB $
[ liftM2 (,) (normalG $ infixApp ([|V.length|] `appE` varE arr)
[|(==)|]
(litE $ integerL 2))
(parse2ElemArray con rs arr)
, liftM2 (,) (normalG [|otherwise|])
(([|not2ElemArray|]
`appE` (varE ref)
`appE` (litE $ stringL $ show tName)
`appE` ([|V.length|] `appE` varE arr)))
]
)
[]
, do other <- newName "other"
match (varP other)
( normalB
$ [|noArrayFail|]
`appE` (varE ref)
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
parseObject f =
[ do obj <- newName "obj"
match (conP 'Object [varP obj]) (normalB $ f obj) []
, do other <- newName "other"
match (varP other)
( normalB
$ [|noObjectFail|]
`appE` (varE ref)
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
parseTaggedObject con rs typFieldName valFieldName obj = do
conKey <- newName "conKey"
doE [ bindS (varP conKey)
([|retrieveWith|]
`appE` [|undefined|]
`appE` varE obj
`appE` ([|T.pack|] `appE` stringE typFieldName))
, noBindS $ parseContents con rs conKey (Left (valFieldName, obj)) 'conNotFoundFailTaggedObject
]
parse2ElemArray con rs arr = do
conKey <- newName "conKey"
conVal <- newName "conVal"
let letIx n ix =
valD (varP n)
(normalB ([|V.unsafeIndex|] `appE`
varE arr `appE`
litE (integerL ix)))
[]
letE [ letIx conKey 0
, letIx conVal 1
]
(caseE (varE conKey)
[ do txt <- newName "txt"
match (conP 'String [varP txt])
(normalB $ parseContents con
rs
txt
(Right conVal)
'conNotFoundFail2ElemArray
)
[]
, do other <- newName "other"
match (varP other)
( normalB
$ [|firstElemNoStringFail|]
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
)
parseObjectWithSingleField con rs obj = do
conKey <- newName "conKey"
conVal <- newName "conVal"
caseE ([e|H.toList|] `appE` varE obj)
[ match (listP [tupP [varP conKey, varP conVal]])
(normalB $ parseContents con rs conKey (Right conVal) 'conNotFoundFailObjectSingleField)
[]
, do other <- newName "other"
match (varP other)
(normalB $ [|wrongPairCountFail|]
`appE` (varE ref)
`appE` (litE $ stringL $ show tName)
`appE` ([|show . length|] `appE` varE other)
)
[]
]
parseContents con rs conKey contents errorFun =
caseE (varE conKey)
[ match wildP
( guardedB $
[ do g <- normalG $ infixApp (varE conKey)
[|(==)|]
([|T.pack|] `appE`
conNameExp opts con)
e <- parseArgs tName opts con ref rs contents
return (g, e)
]
++
[ liftM2 (,)
(normalG [e|otherwise|])
( varE errorFun
`appE` (varE ref)
`appE` (litE $ stringL $ show tName)
`appE` listE (map ( litE
. stringL
. constructorTagModifier opts
. nameBase
. getConName
) cons
)
`appE` ([|T.unpack|] `appE` varE conKey)
)
]
)
[]
]
lamE [varP ref, varP value] $ caseE (varE ref) $
[ do rs <- replicateM (numFields con) $ newName "_r"
match (conP conName $ map varP rs)
(normalB $ caseE (varE value) $
if allNullaryToStringTag opts && all isNullary cons
then allNullaryMatches conName
else mixedMatches con rs
)
[]
| con <- cons
, let conName = getConName con
]
parseNullaryMatches :: Name -> Name -> Name -> [Q Match]
parseNullaryMatches tName conName ref =
[ do arr <- newName "arr"
match (conP 'Array [varP arr])
(guardedB $
[ liftM2 (,) (normalG $ [|V.null|] `appE` varE arr)
([|pure|] `appE` conE conName)
, liftM2 (,) (normalG [|otherwise|])
(parseTypeMismatch (varE ref) tName conName
(litE $ stringL "an empty Array")
(infixApp (litE $ stringL $ "Array of length ")
[|(++)|]
([|show . V.length|] `appE` varE arr)
)
)
]
)
[]
, matchFailed ref tName conName "Array"
]
parseUnaryMatches :: Name -> Name -> [Q Match]
parseUnaryMatches conName r =
[ do arg <- newName "arg"
match (varP arg)
( normalB $ infixApp (conE conName)
[|(<$>)|]
([|parseJSONWith|]
`appE` varE r
`appE` varE arg)
)
[]
]
parseRecord :: Options -> Name -> Name -> [VarStrictType] -> Name -> [Name] -> ExpQ
parseRecord opts tName conName ts obj rs = do
let x:xs = [ [|lookupFieldWith|]
`appE` (litE $ stringL $ show tName)
`appE` (litE $ stringL $ constructorTagModifier opts $ nameBase conName)
`appE` varE r
`appE` (varE obj)
`appE` ( [|T.pack|] `appE` fieldLabelExp opts field
)
| ((field, _, _), r) <- zip ts rs
]
foldl' (\a b -> infixApp a [|(<*>)|] b)
(infixApp (conE conName) [|(<$>)|] x)
xs
getValField :: Name -> String -> [MatchQ] -> Q Exp
getValField obj valFieldName matches = do
val <- newName "val"
doE [ bindS (varP val) $ [|retrieveWith|]
`appE` [|undefined|]
`appE` varE obj
`appE` ([|T.pack|] `appE`
(litE $ stringL valFieldName))
, noBindS $ caseE (varE val) matches
]
-- | Generates code to parse the JSON encoding of a single constructor.
parseArgs :: Name -- ^ Name of the type to which the constructor belongs.
-> Options -- ^ Encoding options.
-> Con -- ^ Constructor for which to generate JSON parsing code.
-> Name
-> [Name]
-> Either (String, Name) Name -- ^ Left (valFieldName, objName) or
-- Right valName
-> Q Exp
-- Nullary constructors.
parseArgs tName _ (NormalC conName []) ref _ (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseNullaryMatches tName conName ref
parseArgs tName _ (NormalC conName []) ref _ (Right valName) =
caseE (varE valName) $ parseNullaryMatches tName conName ref
-- Unary constructors.
parseArgs _ _ (NormalC conName [_]) _ [r] (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseUnaryMatches conName r
parseArgs _ _ (NormalC conName [_]) _ [r] (Right valName) =
caseE (varE valName) $ parseUnaryMatches conName r
-- Polyadic constructors.
parseArgs tName _ (NormalC conName _) ref rs (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseProduct tName conName ref rs
parseArgs tName _ (NormalC conName _) ref rs (Right valName) =
caseE (varE valName) $ parseProduct tName conName ref rs
-- Records.
parseArgs tName opts (RecC conName ts) _ rs (Left (_, obj)) =
parseRecord opts tName conName ts obj rs
parseArgs tName opts (RecC conName ts) ref rs (Right valName) = do
obj <- newName "recObj"
caseE (varE valName)
[ match (conP 'Object [varP obj]) (normalB $ parseRecord opts tName conName ts obj rs) []
, matchFailed ref tName conName "Object"
]
-- Infix constructors. Apart from syntax these are the same as
-- polyadic constructors.
parseArgs tName _ (InfixC _ conName _) ref rs (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseProduct tName conName ref rs
parseArgs tName _ (InfixC _ conName _) ref rs (Right valName) =
caseE (varE valName) $ parseProduct tName conName ref rs
-- Existentially quantified constructors. We ignore the quantifiers
-- and proceed with the contained constructor.
parseArgs tName opts (ForallC _ _ con) ref rs contents =
parseArgs tName opts con ref rs contents
-- | Generates code to parse the JSON encoding of an n-ary
-- constructor.
parseProduct :: Name -- ^ Name of the type to which the constructor belongs.
-> Name -- ^ 'Con'structor name.
-> Name
-> [Name]
-> [Q Match]
parseProduct tName conName ref rs =
[ do let numArgs = genericLength rs
arr <- newName "arr"
-- List of: "parseJSONWith <r_IX> (arr `V.unsafeIndex` <IX>)"
let x:xs = [ [|parseJSONWith|]
`appE` varE r
`appE`
infixApp (varE arr)
[|V.unsafeIndex|]
(litE $ integerL ix)
| (ix, r) <- zip [0 ..] rs
]
match (conP 'Array [varP arr])
(normalB $ condE ( infixApp ([|V.length|] `appE` varE arr)
[|(==)|]
(litE $ integerL numArgs)
)
( foldl' (\a b -> infixApp a [|(<*>)|] b)
(infixApp (conE conName) [|(<$>)|] x)
xs
)
( parseTypeMismatch (varE ref) tName conName
(litE $ stringL $ "Array of length " ++ show numArgs)
( infixApp (litE $ stringL $ "Array of length ")
[|(++)|]
([|show . V.length|] `appE` varE arr)
)
)
)
[]
, matchFailed ref tName conName "Array"
]
--------------------------------------------------------------------------------
-- Parsing errors
--------------------------------------------------------------------------------
matchFailed :: Name -> Name -> Name -> String -> MatchQ
matchFailed ref tName conName expected = do
other <- newName "other"
match (varP other)
( normalB $ parseTypeMismatch (varE ref) tName conName
(litE $ stringL expected)
([|valueConName|] `appE` varE other)
)
[]
parseTypeMismatch :: ExpQ -> Name -> Name -> ExpQ -> ExpQ -> ExpQ
parseTypeMismatch ref tName conName expected actual =
foldl appE
[|parseTypeMismatch'|]
[ ref
, litE $ stringL $ nameBase conName
, litE $ stringL $ show tName
, expected
, actual
]
class (FromJSONWith a) => LookupFieldWith a where
lookupFieldWith :: String -> String -> a -> Object -> T.Text -> Parser a
instance (FromJSONWith a) => LookupFieldWith a where
lookupFieldWith tName rec e obj key =
case H.lookup key obj of
Nothing -> unknownFieldFail e tName rec (T.unpack key)
Just v -> parseJSONWith e v
instance (FromJSONWith a) => LookupFieldWith (Maybe a) where
lookupFieldWith _ _ = retrieveWithMaybe
unknownFieldFail :: fail -> String -> String -> String -> Parser fail
unknownFieldFail _ tName rec key =
fail $ printf "When parsing the record %s of type %s the key %s was not present."
rec tName key
noArrayFail :: fail -> String -> String -> Parser fail
noArrayFail _ t o = fail $ printf "When parsing %s expected Array but got %s." t o
noObjectFail :: fail -> String -> String -> Parser fail
noObjectFail _ t o = fail $ printf "When parsing %s expected Object but got %s." t o
firstElemNoStringFail :: fail -> String -> String -> Parser fail
firstElemNoStringFail _ t o = fail $ printf "When parsing %s expected an Array of 2 elements where the first element is a String but got %s at the first element." t o
wrongPairCountFail :: fail -> String -> String -> Parser fail
wrongPairCountFail _ t n =
fail $ printf "When parsing %s expected an Object with a single tag/contents pair but got %s pairs."
t n
noStringFail :: fail -> String -> String -> Parser fail
noStringFail _ t o = fail $ printf "When parsing %s expected String but got %s." t o
noMatchFail :: fail -> String -> String -> Parser fail
noMatchFail _ t o =
fail $ printf "When parsing %s expected a String with the tag of a constructor but got %s." t o
not2ElemArray :: fail -> String -> Int -> Parser fail
not2ElemArray _ t i = fail $ printf "When parsing %s expected an Array of 2 elements but got %i elements" t i
conNotFoundFail2ElemArray :: fail -> String -> [String] -> String -> Parser fail
conNotFoundFail2ElemArray _ t cs o =
fail $ printf "When parsing %s expected a 2-element Array with a tag and contents element where the tag is one of [%s], but got %s."
t (intercalate ", " cs) o
conNotFoundFailObjectSingleField :: fail -> String -> [String] -> String -> Parser fail
conNotFoundFailObjectSingleField _ t cs o =
fail $ printf "When parsing %s expected an Object with a single tag/contents pair where the tag is one of [%s], but got %s."
t (intercalate ", " cs) o
conNotFoundFailTaggedObject :: fail -> String -> [String] -> String -> Parser fail
conNotFoundFailTaggedObject _ t cs o =
fail $ printf "When parsing %s expected an Object with a tag field where the value is one of [%s], but got %s."
t (intercalate ", " cs) o
parseTypeMismatch' :: fail -> String -> String -> String -> String -> Parser fail
parseTypeMismatch' _ tName conName expected actual =
fail $ printf "When parsing the constructor %s of type %s expected %s but got %s."
conName tName expected actual
--------------------------------------------------------------------------------
-- Utility functions
--------------------------------------------------------------------------------
-- | Boilerplate for top level splices.
--
-- The given 'Name' must be from a type constructor. Furthermore, the
-- type constructor must be either a data type or a newtype. Any other
-- value will result in an exception.
withType :: Name
-> ([TyVarBndr] -> [Con] -> Q a)
-- ^ Function that generates the actual code. Will be applied
-- to the type variable binders and constructors extracted
-- from the given 'Name'.
-> Q a
-- ^ Resulting value in the 'Q'uasi monad.
withType name f = do
info <- reify name
case info of
TyConI dec ->
case dec of
DataD _ _ tvbs cons _ -> f tvbs cons
NewtypeD _ _ tvbs con _ -> f tvbs [con]
other -> error $ "Data.Aeson.FromJSONWith.TH.withType: Unsupported type: "
++ show other
_ -> error "Data.Aeson.FromJSONWith.TH.withType: I need the name of a type."
-- | Extracts the name from a constructor.
getConName :: Con -> Name
getConName (NormalC name _) = name
getConName (RecC name _) = name
getConName (InfixC _ name _) = name
getConName (ForallC _ _ con) = getConName con
-- | Extracts the name from a type variable binder.
tvbName :: TyVarBndr -> Name
tvbName (PlainTV name ) = name
tvbName (KindedTV name _) = name
-- | Makes a string literal expression from a constructor's name.
conNameExp :: Options -> Con -> Q Exp
conNameExp opts = litE
. stringL
. constructorTagModifier opts
. nameBase
. getConName
-- | Creates a string literal expression from a record field label.
fieldLabelExp :: Options -- ^ Encoding options
-> Name
-> Q Exp
fieldLabelExp opts = litE . stringL . fieldLabelModifier opts . nameBase
-- | The name of the outermost 'Value' constructor.
valueConName :: Value -> String
valueConName (Object _) = "Object"
valueConName (Array _) = "Array"
valueConName (String _) = "String"
valueConName (Number _) = "Number"
valueConName (Bool _) = "Boolean"
valueConName Null = "Null"
applyCon :: Name -> [Name] -> Q [Pred]
applyCon con typeNames = return (map apply typeNames)
where apply t =
#if MIN_VERSION_template_haskell(2,10,0)
AppT (ConT con) (VarT t)
#else
ClassP con [VarT t]
#endif
conStringE :: Options -> Name -> Q Exp
conStringE opts = stringE . constructorTagModifier opts . nameBase
-- | If constructor is nullary.
isNullary :: Con -> Bool
isNullary (NormalC _ []) = True
isNullary _ = False
numFields :: Con -> Int
numFields (NormalC _ ts) = length ts
numFields (RecC _ ts) = length ts
numFields InfixC{} = 2
numFields (ForallC _ _ con) = numFields con
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment