How to parse an array with Haskell Aeson

Aeson have instance for list, so I think it is not necessary to deal with vectors.

{-# LANGUAGE LambdaCase #-}
import Data.Aeson

data Series = Series [DataPoint]
data DataPoint = DataPoint Int Int

instance FromJSON DataPoint where
  parseJSON jsn = do
    [x,y] <- parseJSON jsn
    return $ DataPoint x y

instance FromJSON Series where
  parseJSON = \case
    Object o -> (o .: "series") >>= fmap Series . parseJSON
    x -> fail $ "unexpected json: " ++ show x

The trick here is getting the instance for FromJSON DataPoint correct, which takes a little bit of matching but isn't too bad. I came up with

instance FromJSON DataPoint where
    parseJSON (Array v)
        | V.length v == 2 = do
            x <- parseJSON $ v V.! 0
            y <- parseJSON $ v V.! 1
            return $ DataPoint x y
        | otherwise = mzero
    parseJSON _ = mzero

Which will fail to parse cleanly if it isn't able to pull two Ints out for x and y. Then you just have to define the instance for Series:

instance FromJSON Series where
    parseJSON (Object o) = do
        pts <- o .: "series"
        ptsList <- mapM parseJSON $ V.toList pts
        return $ Series ptsList
    parseJSON _ = mzero

Which, again, will cleanly fail if the data is malformed anywhere. To test:

> decode "{\"series\": [[1, 2], [3, 4]]}" :: Maybe Series
Just (Series [DataPoint 1 2, DataPoint 3 4])
> decode "{\"series\": [[1, 2], [3, {}]]}" :: Maybe Series
Nothing

So it looks like it works.


EDIT: As @maxtaldykin has pointed out, you can just take advantage of the FromJSON a => FromJSON [a] instance with

instance FromJSON DataPoint where
    parseJSON obj = do
        [x, y] <- parseJSON obj
        return $ DataPoint x y

instance FromJSON Series where
    parseJSON (Object o) = do
        pts <- o .: "series"
        fmap Series $ parseJSON pts
    parseJSON _ = mzero

Which is greatly simplified from my original answer. Kudos to Max.