Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aeson and Lenses with error handling

I'm very new to the magic of lenses, so I'm having some trouble with this.

With reference to: https://www.fpcomplete.com/user/tel/lens-aeson-traversals-prisms

a JSON object can be traversed in the following way:

val ^? nth 0 . key "someObject" . key "version" . nth 2

for a JSON object that resembles:

"[{\"someObject\": {\"version\": [1, 0, 3]}}]"

The Maybe monad is used throughout, so if any 'accessor' fails, I get a Nothing.

I would like to propagate the failure too, so that I know what accessor failed.

The only way I can think of doing it would be pass an array of accessors, apply them sequentially, and return an error at any point that failed. Something like this:

import Data.Aeson
import Data.Text
import Data.Vector ((!?))
import qualified Data.HashMap.Strict  as HM

data MyAccessor = Nth Int | Key Text 

withFailure :: Value -> [MyAccessor] -> Either String Value
withFailure val [] =  Right val 
withFailure val (x:xs) = case x of
    Nth i -> case val of        
        (Array val') -> case (val' !? i) of
            Just e -> withFailure e xs
            _ -> Left $ "Could not get index " ++ (show i)
        _ -> Left $ "Expected JSON array for index " ++ (show i)
    Key k -> case val of  
        (Object val') -> case (HM.lookup k val') of
            Just e -> withFailure e xs
            _ -> Left $ "Could not get key " ++ (unpack k)
        _ -> Left $ "Expected JSON object for key " ++ (unpack k)

So with this:

-- val =  [[1,0,3], {"name" : "value"}]

> withFailure val [Nth 1, Key "name", Key "hello"]
Left "Expected JSON object for key hello"

> withFailure val [Nth 1, Key "name"]
Right (String "value")

Is there a more elegant way of doing this? Making an Either-ish monad for lens to use, that results in like what withFailure is?

like image 472
iamnat Avatar asked Jul 06 '14 16:07

iamnat


People also ask

Do I need Aeson libraries to work with JSON?

Since aeson is so widely used, there are a fair amount of libraries in the ecosystem that provide extra functionality on top of what is provided in aeson itself. You don’t needany of these libraries to work with JSON, but you might find them useful.

Is it possible to use Aeson for complex JSON parsing?

The autoderived parsers that aeson gives you won’t cut it for these sorts of situations. Fortunately, it ispossible to use aeson for any complicated JSON parsing solution you need, including the ones listed above, but it can be surprisingly nonobvious how to do so.

How do you handle a catch error in a service?

Inside the catchError you can handle the error and then use throwError to throw it to the service. We then register the Interceptor in the Providers array of the root module using the injection token HTTP_INTERCEPTORS.

Is there a cheat sheet for Aeson?

So here’s a cheatsheet for some common operations using aeson. Note that the later examples will make heavy use of monadic code; for the more complicated use cases of aeson, there’s really no way around it.


Video Answer


1 Answers

Another possibility is to use monadic folds from Control.Lens.Action.

Monadic folds let you pepper your fold with effectful actions, so that these actions are interelaved with the process of "exploring" the data structure.

Notice that this is different from something like mapMOf. Monadic folds let you do things like creating parts of the structure being explored by the fold "on the fly", for example by loading them from disk, or asking the user for input.

Normal folds are directly usable as monadic folds. You just have to run them with specialized operators like (^!!) and (^!?).

To introduce an effect into a monadic fold, use the act function.

We can create a monadic fold working in the Writer monad, and insert actions in the fold that "log" progress. Something like this:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad
import Control.Monad.Writer
import Control.Lens
import Data.Monoid
import Data.Aeson
import Data.Aeson.Lens

msg :: String -> IndexPreservingAction (Writer (Last String)) a a
msg str = act $ \a -> tell (Last . Just $ str) >> return a

main :: IO ()
main = do
    let str = "[{\"someObject\": {\"version\": [1, 0, 3]}}]"
        val = maybe (error "decode err") id . decode $ str :: Value
        monfol = nth 0 . msg "#1"
               . key "someObject" . msg "#2"
               . key "version" . msg "#3"
               . nth 2
        (mresult,message) = runWriter $ val ^!? monfol
    putStrLn $ case mresult of
        Just result -> show result
        Nothing -> maybe "no messages" id . getLast $ message

If you change the "version" key in the JSON to make the fold fail, the error message will be "#2".

It would be nice to use some kind of error monad like Either instead of Writer, to be able to pinpoint exactly the place of failure, instead of the last "checkpoint". But I'm not sure if this is possible, because the fold already represents failure by returning Nothing.

Module Control.Lens.Reified has the ReifiedMonadicFold newtype that offers some useful instances for monadic folds. ReifiedMonadicFolds behave a little like the Kleisli arrows of a monad which is an instance of MonadPlus.

like image 83
danidiaz Avatar answered Sep 27 '22 20:09

danidiaz