I'm going to start from the end then get back to your questions.
Solving With Class
Typically you make a Haskell data type for each of your JSON types and write FromJSON
classes that implement the parser. You don't have to, but it does lighten the mental load and fall inline with what you might observe in other project. To that end lets make just a couple types My
for your elements and Mys
for a list of these elements:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as L8
import qualified Data.Vector as V
sample :: L8.ByteString
sample = "{\"k1\":{\"k2\":[{\"a\": 3, \"b\": 4, \"c\": 2}, {\"a\": 1, \"b\": 2, \"c\": 9}]}, \"irrelevant\": \"x\"} "
newtype Mys = Mys [My]
deriving (Eq,Ord,Show)
data My = My Int Int
deriving (Eq,Ord,Show)
Ok, no issues. Now we can extract from your k1
record a list of the a-b-c objects and run the My
parser on these objects to get just the a
and b
values:
instance FromJSON Mys where
parseJSON (Object v) = do
do k1Val <- v .: "k1"
case k1Val of
Object k1 ->
do k2Val <- k1 .: "k2"
Mys . V.toList <$> mapM parseJSON k2Val
_ -> fail "k1 was not an Object"
parseJSON o = fail $ "Invalid type for Mys: " ++ show o
That is, to parse a Mys
we need an object, the object must have a k1
entry that is another object. k1
must have a k2
entry which we can parse as a Vector
of My
values.
instance FromJSON My where
parseJSON (Object v) = My <$> v .: "a" <*> v .: "b"
parseJSON o = fail $ "Invalid type for My: " ++ show o
And the My
data is just a parsing of a
and b
fields as Int
. Behold:
> decode sample :: Maybe Mys
Just (Mys [My 3 4,My 1 2])
Without Class
You asked about :t (fromJust $ (decode sample :: Maybe Object)) .: "k1"
, which is just a fancy way of asking about the type of .:
:
> :t (.:)
(.:) :: FromJSON a => Object -> Text -> Parser a
So you are providing an Object and Text to get a Parser
, as you said. I wouldn't advise using the Parser
monad again - you effectively just used it for decode
. In short I'd say no, you weren't on a path to happiness.
If you aren't going to use the API as designed then just forget the combinators and use the data types directly. That is, lots of case
destruction of the Value
types. First is k1 which is an Object
(just a HashMap
) then extract the k2 value which is an Array
(a Vector
), finally for each element of the Vector you extract out an Object again and lookup the a
and b
keys there. I was going to write it up for example, but it is exceedingly ugly if you don't at least allow yourself a Maybe
monad.