@@ -1,13 +1,18 @@
1+{-# LANGUAGE DeriveGeneric #-}
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE OverloadedStrings #-}
3−module Data.Aeson.Pointer where
5+module JSONPointer where
5import Control.Monad (when)
7import Control.Monad (when)
6import Data.Aeson
8import Data.Aeson
7−import qualified Data.HashMap.Strict as H
9+import qualified Data.Hashable as HA
10+import qualified Data.HashMap.Strict as HM
11+import Data.Semigroup (Semigroup)
8import Data.Text (Text)
12import Data.Text (Text)
9import qualified Data.Text as T
13import qualified Data.Text as T
10import qualified Data.Vector as V
14import qualified Data.Vector as V
15+import GHC.Generics (Generic)
11import Test.QuickCheck
16import Test.QuickCheck
12import Text.Read (readMaybe)
17import Text.Read (readMaybe)
@@ -16,9 +21,30 @@ import Control.Applicative
16import Data.Monoid
21import Data.Monoid
17import Data.Traversable
22import Data.Traversable
24+--------------------------------------------------
26+--------------------------------------------------
32+ | ExpectedObjectOrArray
35+resolve :: Pointer -> Value -> Either ResolutionError Value
36+resolve (Pointer []) v = Right v
37+resolve (Pointer (t:ts)) v = resolveToken t v >>= resolve (Pointer ts)
39+--------------------------------------------------
40+-- * Main types and escaping
41+--------------------------------------------------
19newtype Pointer
43newtype Pointer
20 = Pointer { _unPointer :: [Token] }
44 = Pointer { _unPointer :: [Token] }
21− deriving (Eq, Show, Monoid, Arbitrary)
45+ deriving (Eq, Show, Semigroup, Monoid, Generic, Arbitrary)
47+instance HA.Hashable Pointer
23instance FromJSON Pointer where
49instance FromJSON Pointer where
24 parseJSON = withText "JSON Pointer" $ \t ->
50 parseJSON = withText "JSON Pointer" $ \t ->
@@ -32,13 +58,22 @@ instance ToJSON Pointer where
32-- | We don't try to distinguish between integer tokens and string
58-- | We don't try to distinguish between integer tokens and string
33-- tokens since all tokens start as strings, and all tokens can
59-- tokens since all tokens start as strings, and all tokens can
34-- be used to resolve JSON objects.
60-- be used to resolve JSON objects.
62+-- Since these are unescaped you can write @"/"@ and @"~"@ normally.
63+-- (e.g. if you're referencing a key such as @"abc/123"@, go ahead
64+-- and write that exactly.
35newtype Token
65newtype Token
36 = Token { _unToken :: Text }
66 = Token { _unToken :: Text }
37− deriving (Eq, Show)
67+ deriving (Eq, Show, Generic)
69+instance HA.Hashable Token
39instance Arbitrary Token where
71instance Arbitrary Token where
40 arbitrary = Token . T.pack <$> arbitrary
72 arbitrary = Token . T.pack <$> arbitrary
74+-- | This escapes @"/"@ (because it's the token separator character).
76+-- It also escapes @"~"@ (because it's the escape character).
42escape :: Pointer -> Text
77escape :: Pointer -> Text
43escape (Pointer []) = ""
78escape (Pointer []) = ""
44escape (Pointer ts) =
79escape (Pointer ts) =
@@ -47,15 +82,12 @@ escape (Pointer ts) =
47 . fmap (T.replace "/" "~1" . T.replace "~" "~0" . _unToken)
82 . fmap (T.replace "/" "~1" . T.replace "~" "~0" . _unToken)
52data FormatError
85data FormatError
53 = InvalidFirstChar
86 = InvalidFirstChar
54 -- ^ JSON Pointers must either be empty or start with a @/@.
87 -- ^ JSON Pointers must either be empty or start with a @/@.
55 | UnescapedTilde
88 | UnescapedTilde
56 deriving (Eq, Show)
89 deriving (Eq, Show)
59-- | JSON Pointers must either be empty or start with a @/@. This means
91-- | JSON Pointers must either be empty or start with a @/@. This means
60-- that if you're turning a URI Fragment into a JSON Pointer you must
92-- that if you're turning a URI Fragment into a JSON Pointer you must
61-- drop the initial @#@.
93-- drop the initial @#@.
@@ -77,7 +109,40 @@ unescape txt =
77 Nothing -> Left UnescapedTilde
109 Nothing -> Left UnescapedTilde
78 Just tok -> Right tok
110 Just tok -> Right tok
112+--------------------------------------------------
115+-- These aren't used by the rest of the library
116+-- (as explained in the docs for 'Token').
118+-- However, they might be useful if you need to distinguish JSON Pointer
119+-- tokens from plain 'Text' or 'Int' without losing information by
120+-- converting to 'Token'.
121+--------------------------------------------------
123+-- | A glorified @type@ alias. If you need to do JSON Pointer operations
124+-- you're looking for 'Token' instead.
126+-- NOTE: Unlike 'Token' this is escaped.
128+ = Key { _unKey :: Text }
129+ deriving (Eq, Show, Generic)
131+instance HA.Hashable Key
133+-- | A glorified @type@ alias. If you need to do JSON Pointer operations
134+-- you're looking for 'Token' instead.
136+ = Index { _unIndex :: Int }
137+ deriving (Eq, Show, Generic)
139+instance HA.Hashable Index
141+--------------------------------------------------
143+--------------------------------------------------
80−-- | For internal use by 'unescape'.
145+-- | For internal use (by 'unescape').
81unescapeToken :: Text -> Maybe Token
146unescapeToken :: Text -> Maybe Token
82unescapeToken t
147unescapeToken t
83 | not (isValid t) = Nothing
148 | not (isValid t) = Nothing
@@ -93,21 +158,10 @@ unescapeToken t
93 replace :: Text -> Text
158 replace :: Text -> Text
94 replace = T.replace "~0" "~" . T.replace "~1" "/"
159 replace = T.replace "~0" "~" . T.replace "~1" "/"
96−-- * Resolution
161+-- | For internal use (by 'resolve').
102− | ExpectedObjectOrArray
105−resolve :: Pointer -> Value -> Either ResolutionError Value
106−resolve (Pointer []) v = Right v
107−resolve (Pointer (t:ts)) v = resolveToken t v >>= resolve (Pointer ts)
109−-- | For internal use (or specialized applications that don't want to
163+-- Might also be useful for specialized applications that don't
110−-- resolve an entire pointer at once).
164+-- want to resolve an entire pointer at once.
111resolveToken :: Token -> Value -> Either ResolutionError Value
165resolveToken :: Token -> Value -> Either ResolutionError Value
112resolveToken tok (Array vs) =
166resolveToken tok (Array vs) =
113 case readMaybe . T.unpack . _unToken $ tok of
167 case readMaybe . T.unpack . _unToken $ tok of
@@ -118,7 +172,7 @@ resolveToken tok (Array vs) =
118 Nothing -> Left ArrayElemNotFound
172 Nothing -> Left ArrayElemNotFound
119 Just res -> Right res
173 Just res -> Right res
120resolveToken tok (Object h) =
174resolveToken tok (Object h) =
121− case H.lookup (_unToken tok) h of
175+ case HM.lookup (_unToken tok) h of
122 Nothing -> Left ObjectLookupFailed
176 Nothing -> Left ObjectLookupFailed
123 Just res -> Right res
177 Just res -> Right res
124resolveToken _ _ = Left ExpectedObjectOrArray
178resolveToken _ _ = Left ExpectedObjectOrArray