9

I'm trying to write a FromJSON function for Aeson.

The JSON:

{
  "total": 1,
  "movies": [
    {
      "id": "771315522",
      "title": "Harry Potter and the Philosophers Stone (Wizard's Collection)",
      "posters": {
        "thumbnail": "http://content7.flixster.com/movie/11/16/66/11166609_mob.jpg",
        "profile": "http://content7.flixster.com/movie/11/16/66/11166609_pro.jpg",
        "detailed": "http://content7.flixster.com/movie/11/16/66/11166609_det.jpg",
        "original": "http://content7.flixster.com/movie/11/16/66/11166609_ori.jpg"
      }
    }
  ]
}

The ADT: data Movie = Movie {id::String, title::String}

My attempt:

instance FromJSON Movie where
    parseJSON (Object o) = do
       movies <- parseJSON =<< (o .: "movies") :: Parser Array
       v <- head $ decode movies
       return $ Movie <$>
           (v .: "movies" >>= (.: "id") ) <*>
           (v .: "movies" >>= (.: "title") )
    parseJSON _ = mzero

This gives Couldn't match expected type 'Parser t0' with actual type 'Maybe a0' In the first argument of 'head'.

As you can see, I'm trying to pick the first of the movies in the Array, but I wouldn't mind getting a list of Movies either (in case there are several in the Array).

2 Answers 2

11

If you really want to parse a single Movie from a JSON array of movies, you can do something like this:

instance FromJSON Movie where
    parseJSON (Object o) = do
        movieValue <- head <$> o .: "movies"
        Movie <$> movieValue .: "id" <*> movieValue .: "title"
    parseJSON _ = mzero

But the safer route would be to parse a [Movie] via newtype wrapper:

main = print $ movieList <$> decode "{\"total\":1,\"movies\":[ {\"id\":\"771315522\",\"title\":\"Harry Potter and the Philosophers Stone (Wizard's Collection)\",\"posters\":{\"thumbnail\":\"http://content7.flixster.com/movie/11/16/66/11166609_mob.jpg\",\"profile\":\"http://content7.flixster.com/movie/11/16/66/11166609_pro.jpg\",\"detailed\":\"http://content7.flixster.com/movie/11/16/66/11166609_det.jpg\",\"original\":\"http://content7.flixster.com/movie/11/16/66/11166609_ori.jpg\"}}]}"

newtype MovieList = MovieList {movieList :: [Movie]}

instance FromJSON MovieList where
    parseJSON (Object o) = MovieList <$> o .: "movies"
    parseJSON _ = mzero

data Movie = Movie {id :: String, title :: String}

instance FromJSON Movie where
    parseJSON (Object o) = Movie <$> o .: "id" <*> o .: "title"
    parseJSON _ = mzero
Sign up to request clarification or add additional context in comments.

Comments

8

It's usually easiest to match the structure of your ADTs and instances to the structure of your JSON.

Here, I've added a newtype MovieList to deal with the outermost object so that the instance for Movie only has to deal with a single movie. This also gives you multiple movies for free via the FromJSON instance for lists.

data Movie = Movie { id :: String, title :: String }

newtype MovieList = MovieList [Movie]

instance FromJSON MovieList where
  parseJSON (Object o) =
    MovieList <$> (o .: "movies")
  parseJSON _ = mzero

instance FromJSON Movie where
  parseJSON (Object o) =
    Movie <$> (o .: "id")
          <*> (o .: "title")
  parseJSON _ = mzero

2 Comments

thanks! I didn't think of introducing another type. May I ask a quick follow up? If I'd like to extend the Movie type to include some more fields like filePath or myRating, would you recommend adding a new type myMovie, or introducing a few Maybe fields to the Movie type and fill them up after the decode? (I guess filling up would actually mean making a new instance with all fields, since ADT are immutable..)
@mb21: Either approach will work fine. It depends on the rest of your application. If those fields are always added immediately after decoding, it might make sense to make a new type so that the rest of your functions don't have to deal with a Maybe that should always be Just. On the other hand, if those fields are optional it makes sense to keep them in a Maybe.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.