Turn CSV Values into Elm values. Inspired by elm/json
, so make sure to check out this intro to
JSON decoders to get a feel for how this library works!
Note this library does not include an underlying CSV parser. It assumes you are using something like periodic/elm-csv
to get from String
to Csv
, where Csv
is:
type alias Csv =
{ headers : List String
, records : List (List String)
}
This library gets you the rest of the way, to a list of your own types.
In the examples we make use of a decodeString
function. Which is defined as
decodeString : Decoder a -> String -> Result Error (List a)
decodeString decoder input =
input
|> (++) "\n"
|> Csv.parse
|> Result.mapError (\_ -> CsvParseError)
|> Result.andThen (decode decoder)
A value that knows how to decode CSV values.
{ headers : List String
, records : List (List String)
}
The raw CSV data structure.
A structured error describing exactly how the decoder failed. You can use this to create more elaborate visualizations of a decoder problem. For example, you could show the entire CSV record and show the part causing the failure in red.
Kind determines what type a decoder expects. Used in combination with the Not
error.
string : Decoder String
Decode a CSV string into an Elm String
.
decodeString string "true" == Ok [ "true" ]
decodeString string "42" == Ok [ "42" ]
decodeString string "3.14" == Ok [ "3.14" ]
decodeString string "hello" == Ok [ "hello" ]
int : Decoder Basics.Int
Decode a CSV number into an Elm Int
.
decodeString int "true" == Err ...
decodeString int "42" == Ok [ 42 ]
decodeString int "3.14" == Err ...
decodeString int "hello" == Err ...
float : Decoder Basics.Float
Decode a CSV number into an Elm Float
.
decodeString float "true" == Err ..
decodeString float "42" == Ok [ 42 ]
decodeString float "3.14" == Ok [ 3.14 ]
decodeString float "hello" == Err ...
bool : Decoder Basics.Bool
Decode a CSV boolean into an Elm Bool
.
decodeString bool "true" == Ok [ True ]
decodeString bool "42" == Err ...
decodeString bool "3.14" == Err ...
decodeString bool "hello" == Err ...
decode : Decoder a -> Csv -> Result Error (List a)
Decode the given Csv
into a list of custom value by running Decoder
on it.
This will fail if any of the records can not be decoded by the Decoder
for some reason.
map : (a -> value) -> Decoder a -> Decoder value
Transform a decoder. Maybe you just want to know the length of a string:
import String
stringLength : Decoder Int
stringLength =
map String.length string
It is often helpful to use map
with oneOf
, like when defining maybe
:
maybe : Decoder a -> Decoder (Maybe a)
maybe decoder =
oneOf
[ map Just decoder
, succeed Nothing
]
map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value
Try two decoders and then combine the result. We can use this to decode objects with many fields:
type alias Point =
{ x : Float, y : Float }
point : Decoder Point
point =
map2 Point
float
float
-- decodeString point "3,4" == Ok { x = 3, y = 4 }
It tries each individual decoder and puts the result together with the Point
constructor.
map3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value
Try three decoders and then combine the result. We can use this to decode objects with many fields:
type alias Person =
{ name : String, age : Int, height : Float }
person : Decoder Person
person =
map3 Person
string
int
float
-- csv = "tom,42,1.8"
-- decodeString person csv == Ok { name = "tom", age = 42, height = 1.8 }
Like map2
it tries each decoder in order and then give the results to the
Person
constructor. That can be any function though!
succeed : a -> Decoder a
Ignore the CSV and produce a certain Elm value.
decodeString (succeed 42) "true" == Ok [ 42 ]
decodeString (succeed 42) "1,2,3" == Ok [ 42 ]
decodeString (succeed 42) "hello" == Ok [ 42 ]
This is handy when used with oneOf
.
fail : String -> Decoder a
Ignore the CSV and make the decoder fail. This is handy when used with
oneOf
where you want to give a custom error message in some
case.
maybe : Decoder a -> Decoder (Maybe a)
Helpful for dealing with optional fields. Here are a few slightly different examples:
decodeString (maybe int) "42" == Ok [ Just 42 ]
decodeString (maybe int) ",42" == Ok [ Nothing ]
oneOf : List (Decoder a) -> Decoder a
Try a bunch of different decoders. This can be useful if the CSV may come
in a couple different formats. For example, say you want to read an array of
numbers, but some of them are null
.
import String
badInt : Decoder Int
badInt =
oneOf [ int, null 0 ]
-- decodeString (list badInt) "[1,2,null,4]" == Ok [1,2,0,4]
Why would someone generate CSV like this? Questions like this are not good
for your health. The point is that you can use oneOf
to handle situations
like this!