Using lens-aeson to implement FromJSON
At work I sometimes need to deal with large and deep JSON objects where I'm only
interested in a few of the values. If all the interesting values are on the top
level, then aeson have functions that make it easy to implement FromJSON
's
parseJSON
(Constructors and accessors), but if the values are spread out then
the functions in aeson come up a bit short. That's when I reach for lens-aeson,
as lenses make it very easy to work with large structures. However, I've found
that using its lenses to implement parseJSON
become a lot easier with a few
helper functions.
Many of the lenses produces results wrapped in Maybe
, so the first function is
one that transforms a Maybe a
to a Parser a
. Here I make use of Parser
implementing MonadFail
.
infixl 8 <!> (<!>) :: (MonadFail m) => Maybe a -> String -> m a (<!>) mv err = maybe (fail err) pure mv
In some code I wrote this week I used it to extract the user name out of a JWT produced by Keycloak:
instance FromJSON OurClaimsSet where parseJSON = ... $ \o -> do cs <- parseJSON o n <- o ^? key "preferred_username" . _String <!> "preferre username missing" ... pure $ OurClaimsSet cs n ...
Also, all the lenses start with a Value
and that makes the withX
functions
in aeson to not be a perfect fit. So I define variations of the withX
functions, e.g.
withObjectV :: String -> (Value -> Parser a) -> Value -> Parser a withObjectV s f = withObject s (f . Object)
That makes the full FromJSON
instance for OurClaimsSet
looks like this
instance FromJSON OurClaimsSet where parseJSON = withObjectV "OurClaimsSet" $ \o -> do cs <- parseJSON o n <- o ^? key "preferred_username" . _String <!> "name" let rs = o ^.. key "resource_access" . members . key "roles" . _Array . traverse . _String pure $ OurClaimsSet cs n rs