hjson-query-0.4: src/Text/HJson/Query.hs
module Text.HJson.Query where
import qualified Data.Map as M (empty, unionWith, elems, union, lookup)
import qualified Data.Maybe as Mb (catMaybes)
import qualified Data.List as L (nub)
import qualified Text.HJson.Pretty as PP (toString)
import Text.HJson
type Jsons = [Json]
type JFilter = Json -> Jsons
infixl 1 >>>
infixr 2 <+>
--------------------------------------------------------------------------
-- | create empty JSON object
jEmpty :: Json
jEmpty = JObject (M.empty)
-- | merge two JSON Objects
jMerge :: Json -> Json -> Json
jMerge (JObject x ) (JObject y) = JObject $ M.union x y
jMerge _ _ = jEmpty
-- | recursive merge two JSON Objects
jMergeRec :: Json -> Json -> Json
jMergeRec (JObject x) (JObject y) = JObject $ M.unionWith (\m n -> jMergeRec m n) x y
jMmergeRec _ _ = jEmpty
-- | merge list JSON Objects
jMerges :: [Json] -> Json
jMerges [] = jEmpty
jMerges js = foldl1 jMerge js
-- | recursive merge lists JSON Objects
--
-- Example:
--
-- > import Text.HJson hiding (toString)
-- > import Text.HJson.Query
-- > import Text.HJson.Pretty
-- >
-- > -- Parse JSON
-- > jParse :: String -> Json
-- > jParse jstr = case (fromString jstr) of
-- > Left l -> error l
-- > Right json -> json
-- >
-- > j0 = jParse "{}"
-- > j1 = jParse "{\"Europa\": {\"Ukraine\": [\"Kiyv\", \"Gitomir\", \"Lviv\"]}}"
-- > j2 = jParse "{\"Asia\": {\"Japan\": [\"Tokyo\"]}}"
-- > j3 = jParse "{\"Europa\": {\"UnitedKingdom\": [\"London\", \"Glasgow\"]}}"
-- > j4 = jParse "{\"Europa\": {\"Germany\": [\"Berlin\", \"Bonn\"]}}"
-- > j5 = jParse "{\"Africa\": {}}"
-- > j6 = jParse"{\"America\": {\"USA\": [], \"Canada\": [\"Toronto\"]}}"
-- > j7 = jParse "{\"Australia\": [\"Melburn\", \"Adelaida\"]}"
-- > merg = jMergesRec [j0, j1, j2, j3, j4, j5, j6, j7]
-- > ex0 = putStrLn $ toString " " merg
--
-- Result:
--
-- >{
-- > "Africa": {
-- > },
-- > "America": {
-- > "Canada": ["Toronto"],
-- > "USA": []
-- > },
-- > "Asia": {
-- > "Japan": ["Tokyo"]
-- > },
-- > "Australia": ["Melburn", "Adelaida"],
-- > "Europa": {
-- > "Germany": ["Berlin", "Bonn"],
-- > "Ukraine": ["Kiyv", "Gitomir", "Lviv"],
-- > "UnitedKingdom": ["London", "Glasgow"]
-- > }
-- >}
jMergesRec :: [Json] -> Json
jMergesRec [] = jEmpty
jMergesRec js = foldl1 jMergeRec js
--------------------------------------------------------------------------
-- | filter JSON objects
isObj :: JFilter
isObj (JObject o) = [JObject o]
isObj _ = []
-- | filter JSON arrays
-- Example
isArr :: JFilter
isArr (JArray a) = [JArray a]
isArr _ = []
-- | filter JSON strings
isStr :: JFilter
isStr (JString s) = [JString s]
isStr _ = []
-- | filter JSON numbers
isNum :: JFilter
isNum (JNumber n) = [JNumber n]
isNum _ = []
-- | filter JSON Bool
isBool :: JFilter
isBool (JBool p) = [JBool p]
isBool _ = []
-- | filter JSON null
isNull :: JFilter
isNull JNull = [JNull]
isNull _ = []
-- | filter primitive types
isPrimitive :: JFilter
isPrimitive (JString s) = [JString s]
isPrimitive (JNumber n) = [JNumber n]
isPrimitive (JBool p) = [JBool p]
isPrimitive JNull = [JNull]
isPrimitive _ = []
-- | get elements from object with key
getFromKey :: String -> JFilter
getFromKey k (JObject m) = Mb.catMaybes [(M.lookup k m)]
getFromKey _ _ = []
-- | get elements from object with keys
--
-- Example:
--
-- > pprint js = mapM_ putStrLn $ map (toString " ") js --pretty print
--
-- > query1 = getFromKeys ["Europa", "America", "Africa"]
-- > json1 = query1 merg
-- > ex1 = pprint json1
--
-- Result:
--
-- > {
-- > "Germany": ["Berlin", "Bonn"],
-- > "Ukraine": ["Kiyv", "Gitomir", "Lviv"],
-- > "UnitedKingdom": ["London", "Glasgow"]
-- > }
-- > {
-- > "Canada": ["Toronto"],
-- > "USA": []
-- > }
-- > {
-- >
-- > }
getFromKeys :: [String] -> JFilter
getFromKeys ks (JObject m) = Mb.catMaybes $ map (\k -> (M.lookup k m)) (L.nub ks)
getFromKeys _ _ = []
-- | get all elements from object
getFromObj :: JFilter
getFromObj (JObject o) = M.elems o
getFromObj _ = []
-- | get all elements from array
getFromArr :: JFilter
getFromArr (JArray a) = a
getFromArr _ = []
-- | get element from array with index
getFromIndex :: Int -> JFilter
getFromIndex i (JArray a) = if i < length a then [a !! i] else []
getFromIndex _ _ = []
-- | get elements from array with indexes
getFromIndexes :: [Int] -> JFilter
getFromIndexes is ja = concat [getFromIndex i ja | i <- is]
-- | get all elements from object and array
getChildern :: JFilter
getChildern (JObject o) = M.elems o
getChildern (JArray a) = a
getChildern _ = []
-- | filter combinators
--
-- Example:
--
-- > query2 = query1 >>> getFromObj
-- > json2 = query2 merg
-- > ex2 = pprint json2
--
-- Result:
--
-- > ["Berlin", "Bonn"]
-- > ["Kiyv", "Gitomir", "Lviv"]
-- > ["London", "Glasgow"]
-- > ["Toronto"]
-- > []
--
(>>>) :: JFilter -> JFilter -> JFilter
(f >>> g) t = concat [g t' | t' <- f t]
-- | filter combinators
(<+>) :: JFilter -> JFilter -> JFilter
(f <+> g) t = f t ++ g t
-- | filter combinators
orElse :: JFilter -> JFilter -> JFilter
orElse f g t
| null res1 = g t
| otherwise = res1
where
res1 = f t
-- | filter combinators
when :: JFilter -> JFilter -> JFilter
when f g t
| null (g t) = [t]
| otherwise = f t
-- | filter combinators
guards :: JFilter -> JFilter -> JFilter
guards g f t
| null (g t) = []
| otherwise = f t
-- | tree traversal filter for object
deepObj :: JFilter -> JFilter
deepObj f = f `orElse` (getFromObj >>> deepObj f)
-- | tree traversal filter for array
deepArr :: JFilter -> JFilter
deepArr f = f `orElse` (getFromArr >>> deepArr f)
-- | tree traversal filter for objects and arrays
--
-- Example:
--
-- > -- Qwery: All city Europa, America, Australia and Africa
-- > -- q31, q32, q33 is equal
-- >
-- > q31 = getFromKeys ["Europa", "America", "Africa", "Australia"]
-- > >>> (getFromArr `orElse` getFromObj)
-- > >>> (isStr `orElse` getFromArr)
-- >
-- > q32 = getFromKeys ["Europa", "America", "Africa", "Australia"]
-- > >>> (getFromObj `when` isObj)
-- > >>> getFromArr
-- >
-- > q33 = getFromKeys ["Europa", "America", "Africa", "Australia"]
-- > >>>
-- > deep getFromArr
-- >
--
-- See also: <www.haskell.org/haskellwiki/HXT>
-- |
deep :: JFilter -> JFilter
deep f = f `orElse` (getChildern >>> deep f)
--- Debug ----------------------------------------------------------------
-- | pretty print query from JSON string
--
-- > debug (deep isPrimitive) "[1, [false, true, [null]]]" == "[1, false, true, null]"
--
debug :: JFilter -> String -> String
debug query jstr = case (fromString jstr) of
Left errmsg -> error errmsg
Right json -> PP.toString "- "$ JArray $ query json