-
Notifications
You must be signed in to change notification settings - Fork 323
/
Copy pathObjectParser.hs
86 lines (73 loc) · 2.66 KB
/
ObjectParser.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
{-# LANGUAGE GADTs, PatternGuards #-}
module Data.Aeson.Types.ObjectParser (
ObjectParser,
withObjectParser,
withObjectParser',
runObjectParser,
runObjectParser',
liftObjectParser,
explicitObjectField,
objectField,
) where
import Prelude ()
import Prelude.Compat
import Data.Text (Text)
import qualified Data.HashSet as HS
import Data.Aeson.Types.Internal
import Data.Aeson.Types.FromJSON
-- | Applicative Object parser
data ObjectParser a = OP
{ runObjectParser :: !(Object -> Parser a)
, _opKeys :: !(HS.HashSet Text)
-- TODO: maybe fields
}
instance Functor ObjectParser where
fmap f (OP x ks) = OP (fmap f . x) ks
{-# INLINE fmap #-}
instance Applicative ObjectParser where
pure x = OP (\_ -> pure x) mempty
OP f ks <*> OP x ks' = OP (\obj -> f obj <*> x obj) (HS.union ks ks')
{-# INLINE (<*>) #-}
withObjectParser :: String -> ObjectParser a -> Value -> Parser a
withObjectParser name p = withObject name (runObjectParser p)
withObjectParser'
:: String
-> ObjectParser a
-> HS.HashSet Text -- ^ required
-> HS.HashSet Text -- ^ optional
-> Value -> Parser a
withObjectParser' name p req opt = withObject name (runObjectParser' p req opt)
liftObjectParser :: Text -> (Value -> Parser a) -> ObjectParser a
liftObjectParser k p = OP (\obj -> explicitParseField p obj k) (HS.singleton k)
{-# INLINE liftObjectParser #-}
explicitObjectField :: Text -> (Value -> Parser a) -> ObjectParser a
explicitObjectField = liftObjectParser
objectField :: FromJSON a => Text -> ObjectParser a
objectField k = explicitObjectField k parseJSON
-- | Strict 'runObjectParser'.
--
-- First checks that there aren't extra keys in the 'Object'.
runObjectParser'
:: ObjectParser a
-> HS.HashSet Text -- additional required keys, these keys MUST be present
-> HS.HashSet Text -- additional optional keys, these keys MAY be present
-> Object
-> Parser a
runObjectParser' (OP p ks) ks' os' obj
| Just missing <- required `isSubsetOf` keys =
-- take only few keys to have reasonable sized errors
fail $ "Not all required keys present: " ++ show (take 3 $ HS.toList missing)
| Just extra <- keys `isSubsetOf` optional =
fail $ "Extra keys present: " ++ show (take 3 $ HS.toList extra)
| otherwise = p obj
where
keys = HS.fromMap (() <$ obj)
required = HS.union ks ks'
optional = HS.union required os'
-- Special case: Nothing = True, Just extraKeys = False
isSubsetOf :: HS.HashSet Text -> HS.HashSet Text -> Maybe (HS.HashSet Text)
isSubsetOf as bs
| HS.null cs = Nothing
| otherwise = Just cs
where
cs = HS.difference as bs