{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.SafeJSON.Internal where
import Control.Applicative (Applicative(..), Const(..), (<|>))
import Control.Monad (when)
import Control.Monad.Fail (MonadFail)
import Data.Aeson
import Data.Aeson.Types (Parser, explicitParseField, explicitParseFieldMaybe, explicitParseFieldMaybe')
import Data.DList as DList (DList, fromList)
import Data.Fixed (Fixed, HasResolution)
import Data.Functor.Identity (Identity(..))
import Data.Functor.Compose (Compose)
import Data.Functor.Product (Product)
import Data.Functor.Sum (Sum(..))
import Data.Hashable (Hashable)
import Data.HashMap.Strict as HM (insert, size)
import qualified Data.HashMap.Strict as HM (HashMap, delete, fromList, lookup, toList)
import qualified Data.HashSet as HS (HashSet, fromList, toList)
import Data.Int
import Data.IntMap as IM (IntMap, fromList)
import Data.IntSet (IntSet)
import qualified Data.List as List (intercalate, lookup)
import Data.List.NonEmpty (NonEmpty(..))
import Data.Map (Map)
import Data.Maybe (fromMaybe, isJust, isNothing)
#if MIN_VERSION_base(4,11,0)
import Data.Monoid (Dual(..))
#else
import Data.Monoid (Dual(..), (<>))
#endif
import Data.Proxy
import Data.Ratio (Ratio)
import Data.Scientific (Scientific)
import Data.Semigroup (First(..), Last(..), Max(..), Min(..))
import Data.Sequence (Seq)
import qualified Data.Set as S
import Data.Text as T (Text)
import qualified Data.Text.Lazy as LT (Text)
import Data.Time
import Data.Tree (Tree)
import Data.Typeable (Typeable, typeRep)
import Data.UUID.Types (UUID)
import qualified Data.Vector as V
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Primitive as VP
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Unboxed as VU
import qualified Data.Version as DV (Version)
import Data.Void (Void)
import Data.Word (Word8, Word16, Word32, Word64)
import Foreign.C.Types (CTime)
import Numeric.Natural (Natural)
import Test.Tasty.QuickCheck (Arbitrary(..), shrinkIntegral)
class SafeJSON a where
version :: Version a
version = 0
kind :: Kind a
kind = Base
safeTo :: a -> Contained Value
default safeTo :: ToJSON a => a -> Contained Value
safeTo = contain . toJSON
safeFrom :: Value -> Contained (Parser a)
default safeFrom :: FromJSON a => Value -> Contained (Parser a)
safeFrom = contain . parseJSON
typeName :: Proxy a -> String
default typeName :: Typeable a => Proxy a -> String
typeName = typeName0
internalConsistency :: Consistency a
internalConsistency = computeConsistency Proxy
objectProfile :: Profile a
objectProfile = mkProfile Proxy
{-# MINIMAL #-}
class SafeJSON (MigrateFrom a) => Migrate a where
type MigrateFrom a
migrate :: MigrateFrom a -> a
newtype Contained a = Contained {unsafeUnpack :: a}
contain :: a -> Contained a
contain = Contained
newtype Version a = Version {unVersion :: Maybe Int32}
deriving (Eq)
noVersion :: Version a
noVersion = Version Nothing
setVersion' :: forall a. SafeJSON a => Version a -> Value -> Value
setVersion' (Version mVersion) val =
case mVersion of
Nothing -> val
Just i -> case val of
Object o ->
let vField = maybe versionField
(const dataVersionField)
$ dataVersionField `HM.lookup` o
in Object $ HM.insert vField (toJSON i) o
other -> object
[ dataVersionField .= i
, dataField .= other
]
setVersion :: forall a. SafeJSON a => Value -> Value
setVersion = setVersion' (version @a)
removeVersion :: Value -> Value
removeVersion = \case
Object o -> go o
Array a -> Array $ removeVersion <$> a
other -> other
where go o = maybe regular removeVersion $ do
_ <- dataVersionField `HM.lookup` o
dataField `HM.lookup` o
where regular = Object $ removeVersion <$> HM.delete versionField o
instance Show (Version a) where
show (Version mi) = "Version " ++ showV mi
liftV :: Integer -> (Int32 -> Int32 -> Int32) -> Maybe Int32 -> Maybe Int32 -> Maybe Int32
liftV _ _ Nothing Nothing = Nothing
liftV i f ma mb = Just $ toZ ma `f` toZ mb
where toZ = fromMaybe $ fromInteger i
instance Num (Version a) where
Version ma + Version mb = Version $ liftV 0 (+) ma mb
Version ma - Version mb = Version $ liftV 0 (-) ma mb
Version ma * Version mb = Version $ liftV 1 (*) ma mb
negate (Version ma) = Version $ negate <$> ma
abs (Version ma) = Version $ abs <$> ma
signum (Version ma) = Version $ signum <$> ma
fromInteger i = Version $ Just $ fromInteger i
instance Arbitrary (Version a) where
arbitrary = Version . Just <$> arbitrary
shrink (Version Nothing) = []
shrink (Version (Just a)) = Version . Just <$> shrinkIntegral a
castVersion :: Version a -> Version b
castVersion (Version i) = Version i
newtype Reverse a = Reverse { unReverse :: a }
data Kind a where
Base :: Kind a
Extends :: Migrate a => Proxy (MigrateFrom a) -> Kind a
Extended :: Migrate (Reverse a) => Kind a -> Kind a
base :: Kind a
base = Base
extension :: (SafeJSON a, Migrate a) => Kind a
extension = Extends Proxy
extended_base :: (SafeJSON a, Migrate (Reverse a)) => Kind a
extended_base = Extended base
extended_extension :: (SafeJSON a, Migrate a, Migrate (Reverse a)) => Kind a
extended_extension = Extended extension
versionField :: Text
versionField = "!v"
dataVersionField :: Text
dataVersionField = "~v"
dataField :: Text
dataField = "~d"
safeToJSON :: forall a. SafeJSON a => a -> Value
safeToJSON a = case thisKind of
Base | i == Nothing -> tojson
Extended Base | i == Nothing -> tojson
_ -> setVersion @a tojson
where tojson = unsafeUnpack $ safeTo a
Version i = version :: Version a
thisKind = kind :: Kind a
safeFromJSON :: forall a. SafeJSON a => Value -> Parser a
safeFromJSON origVal = checkConsistency p $ \vs -> do
let hasVNil = noVersionPresent vs
case origKind of
Base | i == Nothing -> unsafeUnpack $ safeFrom origVal
Extended k | i == Nothing -> extendedCase hasVNil k
_ -> regularCase hasVNil
where Version i = version :: Version a
origKind = kind :: Kind a
p = Proxy :: Proxy a
safejsonErr s = fail $ "safejson: " ++ s
regularCase hasVNil = case origVal of
Object o -> do
(val, v) <- tryIt o
withVersion v val origKind
_ -> withoutVersion <|> safejsonErr ("unparsable JSON value (not an object): " ++ typeName p)
where withoutVersion = withVersion noVersion origVal origKind
tryIt o
| hasVNil = firstTry o <|> secondTry o <|> pure (origVal, noVersion)
| otherwise = firstTry o <|> secondTry o
extendedCase :: Migrate (Reverse a) => Bool -> Kind a -> Parser a
extendedCase hasVNil k = case k of { Base -> go; _ -> regularCase hasVNil }
where go = case origVal of
Object o -> tryNew o <|> tryOrig
_ -> tryOrig
tryNew o = do
(val, v) <- firstTry o <|> secondTry o
let forwardKind = getForwardKind k
forwardVersion = castVersion v
getForwardParser = withVersion forwardVersion val forwardKind
unReverse . migrate <$> getForwardParser
tryOrig = unsafeUnpack $ safeFrom origVal
withVersion :: forall b. SafeJSON b => Version b -> Value -> Kind b -> Parser b
withVersion v val k = either fail id eResult
where eResult = constructParserFromVersion val v k
firstTry o = do
v <- o .: versionField
let versionLessObj = HM.delete versionField o
return (Object versionLessObj, Version $ Just v)
secondTry o = do
v <- o .: dataVersionField
bd <- o .: dataField
when (HM.size o /= 2) $ fail $ "malformed simple data (" ++ show (Version $ Just v) ++ ")"
return (bd, Version $ Just v)
constructParserFromVersion :: SafeJSON a => Value -> Version a -> Kind a -> Either String (Parser a)
constructParserFromVersion val origVersion origKind =
worker False origVersion origKind
where
worker :: forall b. SafeJSON b => Bool -> Version b -> Kind b -> Either String (Parser b)
worker fwd thisVersion thisKind
| version == thisVersion = return $ unsafeUnpack $ safeFrom val
| otherwise = case thisKind of
Base -> Left versionNotFound
Extends p -> fmap migrate <$> worker fwd (castVersion thisVersion) (kindFromProxy p)
Extended k -> do
let forwardParser :: Either String (Parser b)
forwardParser = do
if castVersion thisVersion == versionFromProxy reverseProxy
then Right $ unReverse . migrate <$> unsafeUnpack (safeFrom val)
else previousParser
previousParser :: Either String (Parser b)
previousParser = worker True thisVersion k
if fwd || thisVersion == noVersion
then previousParser
else either (const previousParser) Right forwardParser
where versionNotFound = "Cannot find parser associated with: " <> show origVersion
reverseProxy :: Proxy (MigrateFrom (Reverse b))
reverseProxy = Proxy
typeName0 :: Typeable a => Proxy a -> String
typeName0 = show . typeRep
typeName1 :: forall t a. Typeable t => Proxy (t a) -> String
typeName1 _ = show $ typeRep (Proxy :: Proxy t)
typeName2 :: forall t a b. Typeable t => Proxy (t a b) -> String
typeName2 _ = show $ typeRep (Proxy :: Proxy t)
typeName3 :: forall t a b c. Typeable t => Proxy (t a b c) -> String
typeName3 _ = show $ typeRep (Proxy :: Proxy t)
typeName4 :: forall t a b c d. Typeable t => Proxy (t a b c d) -> String
typeName4 _ = show $ typeRep (Proxy :: Proxy t)
typeName5 :: forall t a b c d e. Typeable t => Proxy (t a b c d e) -> String
typeName5 _ = show $ typeRep (Proxy :: Proxy t)
data Profile a = InvalidProfile String
| Profile ProfileVersions
deriving (Eq)
data ProfileVersions = ProfileVersions {
profileCurrentVersion :: Maybe Int32,
profileSupportedVersions :: [(Maybe Int32, String)]
} deriving (Eq)
noVersionPresent :: ProfileVersions -> Bool
noVersionPresent (ProfileVersions c vs) =
isNothing c || isJust (Nothing `List.lookup` vs)
showV :: Maybe Int32 -> String
showV Nothing = "null"
showV (Just i) = show i
showVs :: [(Maybe Int32, String)] -> String
showVs = List.intercalate ", " . fmap go
where go (mi, s) = mconcat ["(", showV mi, ", ", s, ")"]
instance Show ProfileVersions where
show (ProfileVersions cur sup) = mconcat
[ "version ", showV cur, ": ["
, showVs sup, "]"
]
instance Typeable a => Show (Profile a) where
show (InvalidProfile s) = "InvalidProfile: " <> s
show (Profile pv) =
let p = Proxy :: Proxy a
in mconcat [ "Profile for \"", typeName0 p
, "\" (", show pv, ")"
]
mkProfile :: forall a. SafeJSON a => Proxy a -> Profile a
mkProfile p = case computeConsistency p of
NotConsistent t -> InvalidProfile t
Consistent -> Profile $ ProfileVersions {
profileCurrentVersion = unVersion (version @a),
profileSupportedVersions = availableVersions p
}
data Consistency a = Consistent
| NotConsistent String
checkConsistency :: (SafeJSON a, MonadFail m) => Proxy a -> (ProfileVersions -> m b) -> m b
checkConsistency p m =
case mkProfile p of
InvalidProfile s -> fail s
Profile vs -> m vs
computeConsistency :: forall a. SafeJSON a => Proxy a -> Consistency a
computeConsistency p
| isObviouslyConsistent (kind @a) = Consistent
| Just s <- invalidChain p = NotConsistent s
| otherwise = Consistent
{-# INLINE computeConsistency #-}
isObviouslyConsistent :: Kind a -> Bool
isObviouslyConsistent Base = True
isObviouslyConsistent _ = False
availableVersions :: forall a. SafeJSON a => Proxy a -> [(Maybe Int32, String)]
availableVersions _ =
worker False (kind @a)
where
worker :: forall b. SafeJSON b => Bool -> Kind b -> [(Maybe Int32, String)]
worker fwd thisKind = case thisKind of
Base -> [tup]
Extends p' -> tup : worker fwd (kindFromProxy p')
Extended k | not fwd -> worker True (getForwardKind k)
Extended k -> worker True k
where Version v = version @b
name = typeName (Proxy @b)
tup = (v, name)
invalidChain :: forall a. SafeJSON a => Proxy a -> Maybe String
invalidChain _ =
worker mempty mempty (kind @a)
where
worker :: forall b. SafeJSON b => S.Set (Maybe Int32) -> S.Set (Maybe Int32, String) -> Kind b -> Maybe String
worker vs vSs k
| i `S.member` vs = Just $ mconcat
[ "Double occurence of version number '", showV i
, "' (type: ", typeName p
, "). Looping instances if the previous combination of type and version number are found here: "
, showVs $ S.toList vSs
]
| otherwise = case k of
Base -> Nothing
Extends{} | i == Nothing -> Just $ mconcat
[ typeName p, " has defined 'version = noVersion', "
, " but it's 'kind' definition is not 'base' or 'extended_base'"
]
Extends a_proxy -> worker newVSet newVsSet (kindFromProxy a_proxy)
Extended a_kind -> let v@(Version i') = versionFromKind $ getForwardKind a_kind
tup = (i', typeName (proxyFromVersion v))
in worker (S.insert i' vs) (S.insert tup vSs) a_kind
where Version i = version @b
p = Proxy @b
newVSet = S.insert i vs
newVsSet = S.insert (i, typeName p) vSs
proxyFromVersion :: Version a -> Proxy a
proxyFromVersion _ = Proxy
kindFromProxy :: SafeJSON a => Proxy a -> Kind a
kindFromProxy _ = kind
versionFromProxy :: SafeJSON a => Proxy a -> Version a
versionFromProxy _ = version
versionFromKind :: SafeJSON a => Kind a -> Version a
versionFromKind _ = version
getForwardKind :: Migrate (Reverse a) => Kind a -> Kind (MigrateFrom (Reverse a))
getForwardKind _ = kind
withContained :: (a -> b -> c -> m d) -> a -> b -> c -> Contained (m d)
withContained f name prs = contain . f name prs
containWithObject :: String -> (Object -> Parser a) -> Value -> Contained (Parser a)
containWithObject = withContained withObject
containWithArray :: String -> (Array -> Parser a) -> Value -> Contained (Parser a)
containWithArray = withContained withArray
containWithText :: String -> (Text -> Parser a) -> Value -> Contained (Parser a)
containWithText = withContained withText
containWithScientific :: String -> (Scientific -> Parser a) -> Value -> Contained (Parser a)
containWithScientific = withContained withScientific
containWithBool :: String -> (Bool -> Parser a) -> Value -> Contained (Parser a)
containWithBool = withContained withBool
(.:$) :: SafeJSON a => Object -> Text -> Parser a
(.:$) = explicitParseField safeFromJSON
(.:$?) :: SafeJSON a => Object -> Text -> Parser (Maybe a)
(.:$?) = explicitParseFieldMaybe safeFromJSON
(.:$!) :: SafeJSON a => Object -> Text -> Parser (Maybe a)
(.:$!) = explicitParseFieldMaybe' safeFromJSON
(.=$) :: (SafeJSON a, KeyValue kv) => Text -> a -> kv
name .=$ val = name .= safeToJSON val
#define BASIC_NULLARY(T) \
instance SafeJSON T where { version = noVersion }
BASIC_NULLARY(Void)
BASIC_NULLARY(Bool)
BASIC_NULLARY(Ordering)
BASIC_NULLARY(())
BASIC_NULLARY(Char)
BASIC_NULLARY(Float)
BASIC_NULLARY(Double)
BASIC_NULLARY(Int)
BASIC_NULLARY(Natural)
BASIC_NULLARY(Integer)
BASIC_NULLARY(Int8)
BASIC_NULLARY(Int16)
BASIC_NULLARY(Int32)
BASIC_NULLARY(Int64)
BASIC_NULLARY(Word)
BASIC_NULLARY(Word8)
BASIC_NULLARY(Word16)
BASIC_NULLARY(Word32)
BASIC_NULLARY(Word64)
BASIC_NULLARY(T.Text)
BASIC_NULLARY(LT.Text)
BASIC_NULLARY(DV.Version)
BASIC_NULLARY(Scientific)
BASIC_NULLARY(IntSet)
BASIC_NULLARY(UUID)
BASIC_NULLARY(Value)
instance (FromJSON a, ToJSON a, Integral a) => SafeJSON (Ratio a) where
typeName = typeName1
version = noVersion
instance (HasResolution a) => SafeJSON (Fixed a) where
typeName = typeName1
version = noVersion
instance SafeJSON (Proxy a) where
typeName = typeName1
version = noVersion
instance {-# OVERLAPPING #-} SafeJSON String where
typeName _ = "String"
version = noVersion
BASIC_NULLARY(CTime)
BASIC_NULLARY(ZonedTime)
BASIC_NULLARY(LocalTime)
BASIC_NULLARY(TimeOfDay)
BASIC_NULLARY(UTCTime)
BASIC_NULLARY(NominalDiffTime)
BASIC_NULLARY(DiffTime)
BASIC_NULLARY(Day)
BASIC_NULLARY(DotNetTime)
instance SafeJSON a => SafeJSON (Const a b) where
safeFrom val = contain $ Const <$> safeFromJSON val
safeTo (Const a) = contain $ safeToJSON a
typeName = typeName2
version = noVersion
instance SafeJSON a => SafeJSON (Maybe a) where
safeFrom Null = contain $ pure (Nothing :: Maybe a)
safeFrom val = contain $ Just <$> safeFromJSON val
safeTo Nothing = contain $ toJSON (Nothing :: Maybe Value)
safeTo (Just a) = contain $ safeToJSON a
typeName = typeName1
version = noVersion
instance (SafeJSON a, SafeJSON b) => SafeJSON (Either a b) where
safeFrom val = contain $ do
eVal <- parseJSON val
case eVal of
Left a -> Left <$> safeFromJSON a
Right b -> Right <$> safeFromJSON b
safeTo (Left a) = contain $ toJSON (Left $ safeToJSON a :: Either Value Void)
safeTo (Right b) = contain $ toJSON (Right $ safeToJSON b :: Either Void Value)
typeName = typeName2
version = noVersion
#define BASIC_UNARY(T) \
instance SafeJSON a => SafeJSON (T a) where { \
safeFrom val = contain $ T <$> safeFromJSON val; \
safeTo (T a) = contain $ safeToJSON a; \
typeName = typeName1; \
version = noVersion }
BASIC_UNARY(Identity)
BASIC_UNARY(First)
BASIC_UNARY(Last)
BASIC_UNARY(Min)
BASIC_UNARY(Max)
BASIC_UNARY(Dual)
fromGenericVector :: (SafeJSON a, VG.Vector v a) => Value -> Contained (Parser (v a))
fromGenericVector val = contain $ do
v <- parseJSON val
VG.convert <$> VG.mapM safeFromJSON (v :: V.Vector Value)
toGenericVector :: (SafeJSON a, VG.Vector v a) => v a -> Contained Value
toGenericVector = contain . toJSON . fmap safeToJSON . VG.toList
instance SafeJSON a => SafeJSON (V.Vector a) where
safeFrom = fromGenericVector
safeTo = toGenericVector
typeName = typeName1
version = noVersion
instance (SafeJSON a, VP.Prim a) => SafeJSON (VP.Vector a) where
safeFrom = fromGenericVector
safeTo = toGenericVector
typeName = typeName1
version = noVersion
instance (SafeJSON a, VS.Storable a) => SafeJSON (VS.Vector a) where
safeFrom = fromGenericVector
safeTo = toGenericVector
typeName = typeName1
version = noVersion
instance (SafeJSON a, VG.Vector VU.Vector a) => SafeJSON (VU.Vector a) where
safeFrom = fromGenericVector
safeTo = toGenericVector
typeName = typeName1
version = noVersion
instance {-# OVERLAPPABLE #-} SafeJSON a => SafeJSON [a] where
safeFrom val = contain $ do
vs <- parseJSON val
mapM safeFromJSON vs
safeTo as = contain . toJSON $ safeToJSON <$> as
typeName = typeName1
version = noVersion
#define BASIC_UNARY_FUNCTOR(T) \
instance SafeJSON a => SafeJSON (T a) where { \
safeFrom val = contain $ do { \
vs <- parseJSON val; \
mapM safeFromJSON vs }; \
safeTo as = contain . toJSON $ safeToJSON <$> as; \
typeName = typeName1; \
version = noVersion }
BASIC_UNARY_FUNCTOR(NonEmpty)
BASIC_UNARY_FUNCTOR(Seq)
BASIC_UNARY_FUNCTOR(Tree)
instance (SafeJSON a) => SafeJSON (IntMap a) where
safeFrom val = contain $ do
vs <- parseJSON val
IM.fromList <$> mapM safeFromJSON vs
safeTo as = contain . toJSON $ safeToJSON <$> as
typeName = typeName1
version = noVersion
instance (SafeJSON a) => SafeJSON (DList a) where
safeFrom val = contain $ do
vs <- parseJSON val
DList.fromList <$> mapM safeFromJSON vs
safeTo as = contain . toJSON $ safeToJSON <$> as
typeName = typeName1
version = noVersion
instance (SafeJSON a, Ord a) => SafeJSON (S.Set a) where
safeFrom val = contain $ do
vs <- parseJSON val
S.fromList <$> safeFromJSON vs
safeTo as = contain . toJSON $ safeToJSON <$> S.toList as
typeName = typeName1
version = noVersion
instance (Ord k, FromJSONKey k, ToJSONKey k, SafeJSON a) => SafeJSON (Map k a) where
safeFrom val = contain $ do
vs <- parseJSON val
mapM safeFromJSON vs
safeTo as = contain . toJSON $ safeToJSON <$> as
typeName = typeName2
version = noVersion
instance (SafeJSON a, Eq a, Hashable a) => SafeJSON (HS.HashSet a) where
safeFrom val = contain $ do
vs <- parseJSON val
HS.fromList <$> safeFromJSON vs
safeTo as = contain . toJSON $ safeToJSON <$> HS.toList as
typeName = typeName1
version = noVersion
instance (Hashable a, FromJSONKey a, ToJSONKey a, Eq a, SafeJSON b) => SafeJSON (HM.HashMap a b) where
safeFrom val = contain $ do
vs <- parseJSON val
fmap HM.fromList . mapM (mapM safeFromJSON) $ HM.toList vs
safeTo as = contain . toJSON $ safeToJSON <$> as
typeName = typeName2
version = noVersion
instance (SafeJSON a, SafeJSON b) => SafeJSON (a, b) where
safeFrom x = contain $ do
(a',b') <- parseJSON x
a <- safeFromJSON a'
b <- safeFromJSON b'
return (a,b)
safeTo (a,b) = contain $ toJSON (safeToJSON a, safeToJSON b)
typeName = typeName2
version = noVersion
instance (SafeJSON a, SafeJSON b, SafeJSON c) => SafeJSON (a, b, c) where
safeFrom x = contain $ do
(a',b',c') <- parseJSON x
a <- safeFromJSON a'
b <- safeFromJSON b'
c <- safeFromJSON c'
return (a,b,c)
safeTo (a,b,c) = contain $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c)
typeName = typeName3
version = noVersion
instance (SafeJSON a, SafeJSON b, SafeJSON c, SafeJSON d) => SafeJSON (a, b, c, d) where
safeFrom x = contain $ do
(a',b',c',d') <- parseJSON x
a <- safeFromJSON a'
b <- safeFromJSON b'
c <- safeFromJSON c'
d <- safeFromJSON d'
return (a,b,c,d)
safeTo (a,b,c,d) = contain $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c, safeToJSON d)
typeName = typeName4
version = noVersion
instance (SafeJSON a, SafeJSON b, SafeJSON c, SafeJSON d, SafeJSON e) => SafeJSON (a, b, c, d, e) where
safeFrom x = contain $ do
(a',b',c',d',e') <- parseJSON x
a <- safeFromJSON a'
b <- safeFromJSON b'
c <- safeFromJSON c'
d <- safeFromJSON d'
e <- safeFromJSON e'
return (a,b,c,d,e)
safeTo (a,b,c,d,e) = contain $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c, safeToJSON d, safeToJSON e)
typeName = typeName5
version = noVersion