/Text/JSONQ.hs

http://github.com/sw17ch/json-query · Haskell · 184 lines · 118 code · 35 blank · 31 comment · 11 complexity · 59281b3e530a68b61f4ca189d92ee046 MD5 · raw file

  1. {-# LANGUAGE OverloadedStrings #-}
  2. module Text.JSONQ (
  3. JSONV, JSONQ, JSONS,
  4. jsonv, jsonv_,
  5. parseQ, parseQ_, check,
  6. showJSON, showQuery,
  7. query, query_,
  8. ) where
  9. import Data.Maybe
  10. import qualified Data.ByteString.Char8 as B
  11. import qualified Data.Map as M
  12. -- import Text.Regex.Posix -- To be added
  13. import Text.JSON.AttoJSON
  14. import Text.Parsec
  15. import Text.Parsec.ByteString
  16. import System.IO.Unsafe
  17. -- | A JSONV (value) is a synonym for Text.JSON.AttoJSON.JSValue
  18. type JSONV = JSValue
  19. -- | A JSONQ (query) is just a list of selectors.
  20. type JSONQ = [JSONS]
  21. -- | A JSONS (selector)
  22. data JSONS = Key B.ByteString
  23. | Pat B.ByteString
  24. | Idx Int
  25. deriving (Show)
  26. -- | Convert a string to either a JSONS or a parse error.
  27. jsonv :: B.ByteString -> Either String JSONV
  28. jsonv s = parseJSON s
  29. -- | Convert a string to a JSONS. If there is a parse
  30. -- error, an IO error will be raised.
  31. jsonv_ :: B.ByteString -> JSONV
  32. jsonv_ s = case jsonv s of
  33. Right j -> j
  34. Left e -> error e
  35. -- | Renders a query back to a string that would
  36. -- reproduce the query when parsed.
  37. showQuery :: JSONQ -> String
  38. showQuery [] = ""
  39. showQuery (q:rest) =
  40. let c = one q
  41. r = map dot rest
  42. in c ++ concat r
  43. where
  44. one q' = case q' of
  45. (Key k) -> "'" ++ B.unpack k ++ "'"
  46. (Idx i) -> "[" ++ show i ++ "]"
  47. (Pat p) -> "<" ++ B.unpack p ++ ">"
  48. dot q' = case q' of
  49. (Idx _) -> one q'
  50. _ -> "." ++ one q'
  51. -- | Tries to parse a query. True if valid, False if broken.
  52. check :: B.ByteString -> Bool
  53. check s = case parseQ s of
  54. Left _ -> False
  55. Right _ -> True
  56. {- query and query_ only support exact paths into the tree -}
  57. -- | Run a raw query string against a JSONV.
  58. query :: JSONV -> B.ByteString -> Either String JSONV
  59. query v l' = runQuery v l
  60. where Right l = parseQ l'
  61. -- | Run a raw query string against a JSONV. Errors
  62. -- are raised in IO.
  63. query_ :: JSONV -> B.ByteString -> JSONV
  64. query_ val l = case query val l of
  65. Left e -> error e
  66. Right v -> v
  67. queryMany :: JSONV -> B.ByteString -> Either String [JSONV]
  68. queryMany = undefined
  69. queryMany_ :: JSONV -> B.ByteString -> [JSONV]
  70. queryMany_ val l = case queryMany val l of
  71. Left e -> error e
  72. Right v -> v
  73. {- Non-exported functions. -}
  74. -- | Parse a JSONQ from an input string.
  75. parseJSONQ :: Parser JSONQ
  76. parseJSONQ = do
  77. qry <- parseGroup `sepBy1` (char '.')
  78. eof
  79. return $ concat qry
  80. -- | Parse a key and 0 to many indicies.
  81. parseGroup :: Parser [JSONS]
  82. parseGroup = do
  83. k <- choice [parsePat,parseKey]
  84. i <- many $ parseIdx
  85. return (k : i)
  86. parsePat :: Parser JSONS
  87. parsePat = do
  88. _ <- char '<'
  89. pat <- many1 $ noneOf ">"
  90. _ <- char '>'
  91. return $ Pat $ B.pack pat
  92. -- | Parse a key
  93. parseKey :: Parser JSONS
  94. parseKey = try parseQuotedKey
  95. <|> try parseNormKey
  96. -- | Parse an unquoted, alpha-numeric, key
  97. parseNormKey :: Parser JSONS
  98. parseNormKey = many1 alphaNum >>= return . Key . B.pack
  99. -- | Parse a quoted string.
  100. parseQuotedKey :: Parser JSONS
  101. parseQuotedKey = do
  102. _ <- char '\''
  103. k <- many (noneOf "\\'")
  104. _ <- char '\''
  105. return . Key . B.pack $ k
  106. -- | Parse an index
  107. parseIdx :: Parser JSONS
  108. parseIdx = do
  109. _ <- char '['
  110. d <- many1 digit
  111. _ <- char ']'
  112. return . Idx . read $ d
  113. -- | Shorthand to parse a query
  114. parseQ :: B.ByteString -> Either ParseError JSONQ
  115. parseQ = parse parseJSONQ "json-query"
  116. parseQ_ :: B.ByteString -> JSONQ
  117. parseQ_ s = case parseQ s of
  118. (Left e) -> error $ show e
  119. (Right q) -> q
  120. -- | Takes a value and a query. Applies the first selector
  121. -- and returns the remaining query and the selected value.
  122. decompose :: JSONV -> JSONQ -> Maybe (JSONQ, JSONV)
  123. decompose v [] = Just ([],v)
  124. decompose v (s:ss) = case (s,v) of
  125. (Key k, JSObject m) -> [g_map k m]
  126. (Pat p, JSObject m) -> extractMatch p m
  127. (Idx i, JSArray a) -> [g_ary i a]
  128. where
  129. g_map k m = case M.member k m of
  130. True -> Just (ss, fromJust $ M.lookup k m)
  131. False -> Nothing
  132. g_ary i a = case i < length a of
  133. True -> Just (ss, a !! i)
  134. False -> Nothing
  135. extractMatch :: B.ByteString -> (M.Map B.ByteString JSValue) -> Maybe (JSONQ, [JSONV])
  136. extractMatch p m = undefined
  137. -- | Run a query against a JSONV.
  138. runQuery :: JSONV -> JSONQ -> Either String JSONV
  139. runQuery v qry = case decompose v qry of
  140. (Just ([], v')) -> Right $ v'
  141. (Just (rest, v')) -> runQuery v' rest
  142. Nothing -> Left $ "Unable to find " ++ (show qry)
  143. {- Test data -}
  144. input :: B.ByteString
  145. input = unsafePerformIO $ B.readFile "sw17ch.comments.json"
  146. {- An example using this data:
  147. - jsonv_ input `q` "pets[0].name"
  148. - > Right (JSString {fromJSString = "Malcolm"})
  149. -}