Navigating JSON objects in a generic way in Haskell
Question: My goal is to write a program in Haskell that takes the name of a json file
and interprets the rest of the arguments as a path to navigate that json file
by and print the value navigated to. The problem is because JSON can contain
multiple value types, I don't know how to make Haskell's type system
understand what I want. Here is the Haskell code with the "navigate" function
I'm not able to implement correctly:

    
    
    import qualified Data.Aeson as A
    import qualified Data.ByteString.Char8 as BS
    import qualified Data.ByteString.Lazy.Char8 as BSL
    import Data.List
    import Data.Maybe
    import System.Environment
    
    parse :: String -> A.Value
    parse = fromJust . A.decode . BSL.pack
    
    isInteger xs = case reads xs :: [(Integer, String)] of
        [(_, "")] -> True
        _ -> False
    
    navigate :: A.Value -> String -> String
    navigate value [] = value
    navigate value [x:xs]
        | isInteger x = ??? -- value is an array, get the xth element of it.
        | otherwise = ??? -- value is an map, x is a key in it.
    
    main :: IO ()
    main = do
         [filename:path] <- getArgs
         contents <- readFile filename
         let d = parse contents
         putStrLn (show (navigate d path))
    

For reference, here is how the same program would have been written in Python:

    
    
    from json import load
    from sys import argv    
    def navigate(obj, path):
        if not path:
            return obj
        head, tail = path[0], path[1:]
        return navigate(obj[int(head) if head.isdigit() else head], tail)    
    if __name__ == '__main__':
        fname, path = argv[1], argv[2:]
        obj = load(open(fname))
        print navigate(obj, path)
    

The program would be run like this:

    
    
    $ cat data.json
    {"foo" : [[1, 2, 3, {"bar" : "barf"}]]} 
    $ python showjson.py data.json foo 0 3 bar
    barf
    


Answer: You can simply pattern match on the constructors of `A.Value` in order to
figure out what kind of JSON object you are dealing with:

    
    
    import qualified Data.HashMap.Strict as M
    import qualified Data.Vector as V
    import qualified Data.Text as T
    
    -- ... rest of the code more or less as before ...
    
    navigate :: A.Value -> [String] -> BSL.ByteString
    navigate value        []       = A.encode value
    navigate (A.Array vs) (x : xs) = navigate (vs V.! read   x) xs
    navigate (A.Object o) (x : xs) = navigate (o  M.! T.pack x) xs
    

Note that the definition of `A.Value` is as follows:

    
    
    data Value
      = Object !(HashMap Text Value)
      | Array  !(Vector Value)
      | ...  -- other constructors
    

The code for `navigate` is thus making use of the lookup function (called `!`
in both cases) on vectors and hash maps. The function `read` is used to
interpret a command line argument as a number if needed (and will fail
horribly if it isn't), whereas `T.pack` is reinterpreting the string as a
value of type `Text`.

