简体   繁体   中英

Haskell cassava (Data.Csv): Ignore missing columns/fields

How can I set up cassava to ignore missing columns/fields and fill the respective data type with a default value? Consider this example:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

import           Data.ByteString.Lazy.Char8
import           Data.Csv
import           Data.Vector
import           GHC.Generics

data Foo = Foo { 
      a :: String
    , b :: Int
    } deriving (Eq, Show, Generic)
    
instance FromNamedRecord Foo

decodeAndPrint :: ByteString -> IO ()
decodeAndPrint csv = do
    print $ (decodeByName csv :: Either String (Header, Vector Foo))

main :: IO ()
main = do
    decodeAndPrint "a,b,ignore\nhu,1,pu"  -- [1]
    decodeAndPrint "ignore,b,a\npu,1,hu"  -- [2]
    decodeAndPrint "ignore,b\npu,1"     -- [3]

[1] and [2] work perfectly fine, but [3] fails with

Left "parse error (Failed reading: conversion error: no field named \"a\") at \"\""

How could I make decodeAndPrint capable of handling this incomplete input?

I could of course manipulate the input bytestring, but maybe there is a more elegant solution.


A Solution thanks to the input of Daniel Wagner below:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

import           Control.Applicative
import           Data.ByteString.Lazy.Char8
import           Data.Csv
import           Data.Vector
import           GHC.Generics

data Foo = Foo { 
      a :: Maybe String
    , b :: Maybe Int
    } deriving (Eq, Show, Generic)
    
instance FromNamedRecord Foo where
    parseNamedRecord rec = pure Foo
        <*> ((Just <$> Data.Csv.lookup rec "a") <|> pure Nothing)
        <*> ((Just <$> Data.Csv.lookup rec "b") <|> pure Nothing)

decodeAndPrint :: ByteString -> IO ()
decodeAndPrint csv = do
    print $ (decodeByName csv :: Either String (Header, Vector Foo))

main :: IO ()
main = do
    decodeAndPrint "a,b,ignore\nhu,1,pu"  -- [1]
    decodeAndPrint "ignore,b,a\npu,1,hu"  -- [2]
    decodeAndPrint "ignore,b\npu,1"       -- [3]

(Warning: completely untested, Code is for idea transmission only, not suitable for any use. etc. etc.)

The Parser type demanded by FromNamedRecord is an Alternative , so just toss a default on with (<|>) .

instance FromNamedRecord Foo where
    parseNamedRecord rec = pure Foo
        <*> (lookup rec "a" <|> pure "missing")
        <*> (lookup rec "b" <|> pure 0)

If you want to know later whether the field was there or not, make your fields rich enough to record that:

data RichFoo = RichFoo
    { a :: Maybe String
    , b :: Maybe Int
    }

instance FromNamedRecord Foo where
    parseNamedRecord rec = pure RichFoo
        <*> ((Just <$> lookup rec "a") <|> pure Nothing)
        <*> ((Just <$> lookup rec "b") <|> pure Nothing)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM