Search code examples
jsonhaskellhaskell-lensaeson

Getting a collection of values from a JSON ByteString using lens-aeson


Say I have a JSON ByteString that looks something like

{
    messages: [
        {...},
        {...}
    ]
}

I'd like to use lens to get a list/vector of messages out of it. I have a function toMessage that can turn a Value into a Maybe Message.

I've tried this composition key "messages" . values . to toMessage (to is from Control.Lens.Getter but the result is Maybe Message and it simply becomes Nothing.

Currently I'm doing this

msgsJson <- c ^? key "messages"
let msgs = toList $ mapMaybe message $ msgsJson ^.. values

(mapMaybe is from witherable, toList is to convert the Vector into a list) but I'd like to know if there's a way to compose various lenses to get a single lens that does this.


Solution

  • Hmm, this works for me:

    {-# LANGUAGE OverloadedStrings #-}
    
    module Main where
    
    import Data.ByteString (ByteString)
    
    import Control.Lens
    import Data.Aeson
    import Data.Aeson.Lens
    
    newtype Message =
      Message Integer
      deriving (Show)
    
    toMessage :: Value -> Maybe Message
    toMessage json_ = do
      i <- json_ ^? key "a" . _Integer
      return (Message i)
    
    input :: ByteString
    input = "{\"messages\":[{\"a\":1},{\"a\":2},{\"a\":3}]}"
    
    main :: IO ()
    main =
      print (input ^.. (key "messages" . values . to toMessage))
    
    λ> main
    [Just (Message 1),Just (Message 2),Just (Message 3)]
    

    If you're getting Nothing, it could mean that your JSON is invalid (you can test it with decode json :: Maybe Value). Or that any other optic in the composition is failing. With long dotted optics it's sometimes hard to tell exactly which one is failing except by lopping parts off the end and retrying. But your composed optic key "messages" . values . to toMessage should Just Work™.

    By the way:

    msgJson <- c ^? key "messages"
    let msgs = toList $ mapMaybe message $ msgsJson ^.. values
    

    should be the same as

    msgJson <- c ^? key "messages"
    let msgs = toList $ msgsJson ^.. (values . to message . _Just)
    

    and

    msgJson <- c ^? key "messages"
    let msgs = msgsJson ^.. (values . to message . _Just)
    

    and

    let msgs = c ^.. (key "messages" . values . to message . _Just)