Using periodic/elm-csv
(which returns a Result (List String) Csv
):
Csv.parse rawData |> Csv.Decode.decode myDecoder
Using lovasoa/elm-csv
(which returns a plain Csv
):
Csv.parse rawData |> Csv.Decode.decodeCsv myDecoder
You can define decoders based on field position or on header name. See examples below.
{ headers : List String
, records : List (List String)
}
The raw CSV data structure.
A value that encapsulates how to decode CSV records (List String
)
decode : Decoder (a -> a) a -> Result (List String) Csv -> Result Errors (List a)
Decode the raw result of CSV parsing.
Typically you chain them together like this (using periodic/elm-csv
):
Csv.parse rawData |> Csv.Decode.decode myDecoder
decodeCsv : Decoder (a -> a) a -> Csv -> Result Errors (List a)
Decode raw CSV data.
This is useful if you already have a Csv
structure not
wrapped in a Result
, for instance lovasoa/elm-csv
parses CSV strings this
way.
next : (String -> Result String a) -> Decoder (a -> b) b
Decode the next field from the input: positional decoding.
Use this when you are certain of the order of the fields. It is faster than header-based decoding.
type alias Coordinates =
{ x : Float, y : Float, z : Float }
decodeCoordinates : Decoder (Coordinates -> a) a
decodeCoordinates =
map Coordinates
( next String.toFloat
|> andMap (next String.toFloat)
|> andMap (next String.toFloat)
)
field : String -> (String -> Result String a) -> Decoder (a -> b) b
Decode the named field from the input: header-based decoding.
Use this when you do not want to rely on the order of the fields, or when your source fields map to more than one target field.
type alias Nutrition =
{ name : String, calories : Int, protein : Float }
decodeNutrition : Decoder (Nutrition -> a) a
decodeNutrition =
map Nutrition
( field "name" Ok
|> andMap (field "calories" String.toInt)
|> andMap (field "protein" String.toFloat)
)
Note that position- and header-based decoding can be combined, but it is not generally recommended.
assertNext : String -> Decoder a a
Decode the next field if it matches the given string.
This can be useful to decode a union type based on a field of the CSV. For example:
type Mailing
= Letter Float
| Parcel Float Dimensions
decodeMailing : Decoder (Mailing -> a) a
decodeMailing =
oneOf
[ map Letter
( assertNext "LETTER"
|> andMap (next String.toFloat)
)
, map Parcel
( assertNext "PARCEL"
|> andMap (next String.toFloat)
|> andMap (next parseDimensions)
)
]
(Note: If you are familiar with the url-parser
library, this is structurally
similar to the s
function.)
assertField : String -> String -> Decoder a a
Decode a named field if it matches the given string.
The same example above, but for header-based decoding:
type Mailing
= Letter Float
| Parcel Float Dimensions
decodeMailing : Decoder (Mailing -> a) a
decodeMailing =
oneOf
[ map Letter
( assertField "type" "LETTER"
|> andMap (field "weight" String.toFloat)
)
, map Parcel
( assertField "type" "PARCEL"
|> andMap (field "weight" String.toFloat)
|> andMap (field "dimensions" parseDimensions)
)
]
maybe : (String -> Result String a) -> String -> Result String (Maybe a)
A convenience function for converting empty strings to Nothing
.
Useful when you have optional fields.
type alias Letter =
{ weight = Float
, insurance = Maybe CurrencyAmount
}
decodeLetter : Decoder (Letter -> a) a
decodeLetter =
map Letter
( field "weight" String.toFloat
|> andMap (field "insurance" (maybe parseCurrencyAmount))
)
andMap : Decoder b c -> Decoder a b -> Decoder a c
Decode multiple fields.
decodeCsv
(assertField "site" "blog"
|> andMap (field "id" String.toInt)
)
data
-- { headers = [ "site", "id" ]
-- , records = [["blog","35"]]
-- } ==> Ok [35]
oneOf : List (Decoder a b) -> Decoder a b
Try a bunch of different decoders, using the first one that succeeds.
type IntOrFloat
= Int_ Int
| Float_ Float
decode : Decoder (IntOrFloat -> a) a
decode =
oneOf
[ map Int_ <| next String.toInt
, map Float_ <| next String.toFloat
]
map : a -> Decoder a b -> Decoder (b -> c) c
Transform a decoder.
Typically used to feed a bunch of parsed state into a type constructor.
type alias Comment = { author : String, id : Int }
decodeRawComment : Decoder (String -> Int -> a) a
decodeRawComment =
field "author" Ok |> andMap (field "id" String.toInt)
decodeComment : Decoder (Comment -> a) a
decodeComment =
map Comment decodeRawComment
Errors can either be
CsvErrors
), orDecodeErrors
)Note that the latter reports the record index together with the error message.