git.iko.soy/typeable/hjsonpointer
on master help sign in
1.1.0.0.

+ Rename the module to `JSONPointer`. + Bump aeson. + Derive `Semigroup` for `Pointer`. + Reorganize code.

commite9b9827cef0317d23afc17d9f165d1f907b1e3a8 authorIan Grant Jeffries <ian@housejeffries.com> dateThu 29 Dec 2016, 16:47 (10 years ago) parent2228679
6 files changed+117−60
Example.hs+7−8 README.md+7−9 changelog.md+7 hjsonpointer.cabal+9−10 src/Data/Aeson/Pointer.hs → src/JSONPointer.hs+78−24 test/Unit.hs+9−9
Example.hs+7−8
@@ -1,27 +1,26 @@
1−{-# LANGUAGE OverloadedStrings #-}
21
32module Example where
43
5−import Control.Monad (unless)
4+import Control.Monad (unless)
65import Data.Aeson
7−import qualified Data.Aeson.Pointer as P
6+import qualified JSONPointer as JP
87
98main :: IO ()
109main = do
1110 -- JSON Pointers must either be empty or start with a /.
12− pntr1 <- case P.unescape "/foo/0" of
11+ pntr1 <- case JP.unescape "/foo/0" of
1312 Left _ -> error "Failed to construct JSON Pointer."
1413 Right pntr -> return pntr
1514
1615 -- We can also write JSON Pointers in Haskell.
17− let pntr2 = P.Pointer [P.Token "/"]
16+ let pntr2 = JP.Pointer [JP.Token "/"]
1817 -- When we do this we don't have to escape / or ~ characters
1918 -- (as ~1 and ~0 respectively) like we do in an escaped JSON
2019 -- Pointer string.
21− unless (P.unescape "/~1" == Right pntr2) (error "ohno!")
20+ unless (JP.unescape "/~1" == Right pntr2) (error "ohno!")
2221
23− print (P.resolve pntr1 document)
24− print (P.resolve pntr2 document)
22+ print (JP.resolve pntr1 document)
23+ print (JP.resolve pntr2 document)
2524
2625 where
2726 document :: Value
README.md+7−9
@@ -7,30 +7,28 @@
77# Example
88
99```haskell
10−{-# LANGUAGE OverloadedStrings #-}
11−
1210module Example where
1311
14−import Control.Monad (unless)
12+import Control.Monad (unless)
1513import Data.Aeson
16−import qualified Data.Aeson.Pointer as P
14+import qualified JSONPointer as JP
1715
1816main :: IO ()
1917main = do
2018 -- JSON Pointers must either be empty or start with a /.
21− pntr1 <- case P.unescape "/foo/0" of
19+ pntr1 <- case JP.unescape "/foo/0" of
2220 Left _ -> error "Failed to construct JSON Pointer."
2321 Right pntr -> return pntr
2422
2523 -- We can also write JSON Pointers in Haskell.
26− let pntr2 = P.Pointer [P.Token "/"]
24+ let pntr2 = JP.Pointer [JP.Token "/"]
2725 -- When we do this we don't have to escape / or ~ characters
2826 -- (as ~1 and ~0 respectively) like we do in an escaped JSON
2927 -- Pointer string.
30− unless (P.unescape "/~1" == Right pntr2) (error "ohno!")
28+ unless (JP.unescape "/~1" == Right pntr2) (error "ohno!")
3129
32− print (P.resolve pntr1 document)
33− print (P.resolve pntr2 document)
30+ print (JP.resolve pntr1 document)
31+ print (JP.resolve pntr2 document)
3432
3533 where
3634 document :: Value
changelog.md+7
@@ -1,3 +1,10 @@
1+# 1.1.0.0
2+
3++ Rename the module to `JSONPointer`.
4++ Bump aeson.
5++ Derive `Semigroup` for `Pointer`.
6++ Reorganize code.
7+
18# 1.0.0.2
29
310+ Bump hspec.
hjsonpointer.cabal+9−10
@@ -1,5 +1,5 @@
11name: hjsonpointer
2−version: 1.0.0.2
2+version: 1.1.0.0
33synopsis: JSON Pointer library
44homepage: https://github.com/seagreen/hjsonpointer
55license: MIT
@@ -17,19 +17,18 @@ library
1717 hs-source-dirs:
1818 src
1919 default-language: Haskell2010
20− default-extensions:
21− OverloadedStrings
22− other-extensions:
23− GeneralizedNewtypeDeriving
2420 ghc-options:
2521 -Wall
2622 exposed-modules:
27− Data.Aeson.Pointer
23+ JSONPointer
2824 build-depends:
29− aeson >= 0.7 && < 1.1
30− , base >= 4.6 && < 4.10
25+ base >= 4.6 && < 4.10
26+ , aeson >= 0.7 && < 1.2
27+ , hashable >= 1.2 && < 1.3
3128 , QuickCheck >= 2.8 && < 2.10
3229 , unordered-containers >= 0.2 && < 0.3
30+ , semigroups >= 0.18 && < 1.0
31+ -- ^ for GHCs < 8
3332 , text >= 1.2 && < 1.3
3433 , vector >= 0.10 && < 0.12
3534
@@ -49,12 +48,12 @@ test-suite unit
4948 aeson
5049 , base
5150 , hjsonpointer
51+ , hspec >= 2.2 && < 2.4
52+ , http-types >= 0.8 && < 0.10
5253 , QuickCheck
5354 , unordered-containers
5455 , text
5556 , vector
56− , hspec >= 2.2 && < 2.4
57− , http-types >= 0.8 && < 0.10
5857
5958source-repository head
6059 type: git
src/Data/Aeson/Pointer.hs → src/JSONPointer.hsrenamed+78−24
@@ -1,13 +1,18 @@
1+{-# LANGUAGE DeriveGeneric #-}
12{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE OverloadedStrings #-}
24
3−module Data.Aeson.Pointer where
5+module JSONPointer where
46
57import Control.Monad (when)
68import 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)
812import Data.Text (Text)
913import qualified Data.Text as T
1014import qualified Data.Vector as V
15+import GHC.Generics (Generic)
1116import Test.QuickCheck
1217import Text.Read (readMaybe)
1318
@@ -16,9 +21,30 @@ import Control.Applicative
1621import Data.Monoid
1722import Data.Traversable
1823
24+--------------------------------------------------
25+-- * Resolution
26+--------------------------------------------------
27+
28+data ResolutionError
29+ = ObjectLookupFailed
30+ | ArrayIndexInvalid
31+ | ArrayElemNotFound
32+ | ExpectedObjectOrArray
33+ deriving (Eq, Show)
34+
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)
38+
39+--------------------------------------------------
40+-- * Main types and escaping
41+--------------------------------------------------
42+
1943newtype Pointer
2044 = Pointer { _unPointer :: [Token] }
21− deriving (Eq, Show, Monoid, Arbitrary)
45+ deriving (Eq, Show, Semigroup, Monoid, Generic, Arbitrary)
46+
47+instance HA.Hashable Pointer
2248
2349instance FromJSON Pointer where
2450 parseJSON = withText "JSON Pointer" $ \t ->
@@ -32,13 +58,22 @@ instance ToJSON Pointer where
3258-- | We don't try to distinguish between integer tokens and string
3359-- tokens since all tokens start as strings, and all tokens can
3460-- be used to resolve JSON objects.
61+--
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.
3565newtype Token
3666 = Token { _unToken :: Text }
37− deriving (Eq, Show)
67+ deriving (Eq, Show, Generic)
68+
69+instance HA.Hashable Token
3870
3971instance Arbitrary Token where
4072 arbitrary = Token . T.pack <$> arbitrary
4173
74+-- | This escapes @"/"@ (because it's the token separator character).
75+--
76+-- It also escapes @"~"@ (because it's the escape character).
4277escape :: Pointer -> Text
4378escape (Pointer []) = ""
4479escape (Pointer ts) =
@@ -47,15 +82,12 @@ escape (Pointer ts) =
4782 . fmap (T.replace "/" "~1" . T.replace "~" "~0" . _unToken)
4883 $ ts
4984
50−-- * Unescaping
51−
5285data FormatError
5386 = InvalidFirstChar
5487 -- ^ JSON Pointers must either be empty or start with a @/@.
5588 | UnescapedTilde
5689 deriving (Eq, Show)
5790
58−
5991-- | JSON Pointers must either be empty or start with a @/@. This means
6092-- that if you're turning a URI Fragment into a JSON Pointer you must
6193-- drop the initial @#@.
@@ -77,7 +109,40 @@ unescape txt =
77109 Nothing -> Left UnescapedTilde
78110 Just tok -> Right tok
79111
80−-- | For internal use by 'unescape'.
112+--------------------------------------------------
113+-- * Wrapper Types
114+--
115+-- These aren't used by the rest of the library
116+-- (as explained in the docs for 'Token').
117+--
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+--------------------------------------------------
122+
123+-- | A glorified @type@ alias. If you need to do JSON Pointer operations
124+-- you're looking for 'Token' instead.
125+--
126+-- NOTE: Unlike 'Token' this is escaped.
127+newtype Key
128+ = Key { _unKey :: Text }
129+ deriving (Eq, Show, Generic)
130+
131+instance HA.Hashable Key
132+
133+-- | A glorified @type@ alias. If you need to do JSON Pointer operations
134+-- you're looking for 'Token' instead.
135+newtype Index
136+ = Index { _unIndex :: Int }
137+ deriving (Eq, Show, Generic)
138+
139+instance HA.Hashable Index
140+
141+--------------------------------------------------
142+-- * Internals
143+--------------------------------------------------
144+
145+-- | For internal use (by 'unescape').
81146unescapeToken :: Text -> Maybe Token
82147unescapeToken t
83148 | not (isValid t) = Nothing
@@ -93,21 +158,10 @@ unescapeToken t
93158 replace :: Text -> Text
94159 replace = T.replace "~0" "~" . T.replace "~1" "/"
95160
96−-- * Resolution
97−
98−data ResolutionError
99− = ObjectLookupFailed
100− | ArrayIndexInvalid
101− | ArrayElemNotFound
102− | ExpectedObjectOrArray
103− deriving (Eq, Show)
104−
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)
108−
109−-- | For internal use (or specialized applications that don't want to
110−-- resolve an entire pointer at once).
161+-- | For internal use (by 'resolve').
162+--
163+-- Might also be useful for specialized applications that don't
164+-- want to resolve an entire pointer at once.
111165resolveToken :: Token -> Value -> Either ResolutionError Value
112166resolveToken tok (Array vs) =
113167 case readMaybe . T.unpack . _unToken $ tok of
@@ -118,7 +172,7 @@ resolveToken tok (Array vs) =
118172 Nothing -> Left ArrayElemNotFound
119173 Just res -> Right res
120174resolveToken tok (Object h) =
121− case H.lookup (_unToken tok) h of
175+ case HM.lookup (_unToken tok) h of
122176 Nothing -> Left ObjectLookupFailed
123177 Just res -> Right res
124178resolveToken _ _ = Left ExpectedObjectOrArray
test/Unit.hs+9−9
@@ -3,15 +3,15 @@ module Main where
33
44import Control.Arrow
55import Data.Aeson
6−import qualified Data.Aeson.Pointer as P
7−import Data.Text (Text)
8−import qualified Data.Text as T
6+import Data.Text (Text)
7+import qualified Data.Text as T
98import Data.Text.Encoding
10−import qualified Data.Vector as V
11−import Network.HTTP.Types.URI (urlDecode)
9+import qualified Data.Vector as V
10+import qualified JSONPointer as JP
11+import Network.HTTP.Types.URI (urlDecode)
1212
1313import Test.Hspec
14−import Test.QuickCheck (property)
14+import Test.QuickCheck (property)
1515
1616-- For GHCs before 7.10:
1717import Control.Applicative
@@ -29,7 +29,7 @@ main = hspec $ do
2929 it "can be represented in a JSON string value" jsonString
3030 it "can be represented in a URI fragment identifier" uriFragment
3131
32−roundtrip :: P.Pointer -> Bool
32+roundtrip :: JP.Pointer -> Bool
3333roundtrip a = Just a == decode (encode a)
3434
3535jsonString :: Expectation
@@ -69,9 +69,9 @@ uriFragment = traverse_ resolvesTo . fmap (first decodeFragment) $
6969
7070resolvesTo :: (Text, Value) -> Expectation
7171resolvesTo (t, expected) =
72− case P.unescape t of
72+ case JP.unescape t of
7373 Left e -> expectationFailure (show e <> " error for pointer: " <> show t)
74− Right p -> P.resolve p specExample `shouldBe` Right expected
74+ Right p -> JP.resolve p specExample `shouldBe` Right expected
7575
7676specExample :: Value
7777specExample = object