csvparsinghaskellattoparsec

Parsing custom fields with Cassava and Attoparsec


I have a CSV with fields in it which contain unit values which I have to parse out. As a simple example:

data EValue = Farads Double | MicroFarads Double | PicoFarads Double

Thus I need to parse something like the following:

parseEValue = farads <|> micro <|> pico
  where farads = Farads <$> double <* string "F"
        micro  = MicroFarads <$> double <* string "µF"
        pico   = PicoFarads <$> double <* string "pF"

How do I include this in an instance definition for FromField for Cassava?

instance FromField EValue where
  parseField = ???

Solution

  • You just need to run attoparsec on the Field you get and then put the result in the Parser monad, like this: parseField = either fail pure . parseOnly parseEValue.

    For completeness, here's everything put together:

    {-# LANGUAGE OverloadedStrings #-}
    
    import Control.Applicative
    import Data.Attoparsec.ByteString.Char8
    import Data.Csv
    
    data EValue = Farads Double | MicroFarads Double | PicoFarads Double
    
    parseEValue = farads <|> micro <|> pico
      where farads = Farads <$> double <* string "F"
            micro  = MicroFarads <$> double <* string "µF"
            pico   = PicoFarads <$> double <* string "pF"
    
    instance FromField EValue where
      parseField = either fail pure . parseOnly parseEValue
    

    On an unrelated note, string "µF" is unlikely to do what you want (except during testing with string literals as input). You probably want something like import qualified Data.Text.Encoding as T and string (T.encodeUtf8 "µF") instead. See Surprising behavior of ByteString literals via IsString for details.