Turn YAML into Elm values. The library is structured in a similar way
to Json.Decode
, so if you haven't worked with decoders before, reading through
the guide may be helpful.
A value that knows how to decode YAML.
There is a whole section in guide.elm-lang.org about decoders, so check it out for a more comprehensive introduction!
fromString : Decoder a -> String -> Result Error a
Decode a given string into an Elm value based on the
provided Decoder
. This will fail if the string is not
well-formed YAML or if the Decoder
doesn't match the
input.
fromString int "4" --> Ok 4
fromString int "hello" --> Err (Decoding "Expected int, got: \"hello\"")
Yaml.Parser.Value
Represents a YAML tree.
A structured error describing how a decoder failed.
fromValue : Decoder a -> Value -> Result Error a
Run a Decoder
on a Yaml Value
.
errorToString : Error -> String
Convert a structured error into a String
that is nice for debugging.
string : Decoder String
Decode a YAML string into an Elm String
.
fromString string "true"
--> Err (Decoding "Expected string, got: True (bool)")
fromString string "'true'" --> Ok "true"
fromString string "42"
--> Err (Decoding "Expected string, got: 42 (int)")
fromString string "'42'" --> Ok "42"
fromString string "hello" --> Ok "hello"
fromString string "hello: 42"
--> Err (Decoding "Expected string, got: { hello: 42 (int) }")
bool : Decoder Basics.Bool
Decode a YAML boolean into an Elm Bool
.
fromString bool "true" --> Ok True
fromString bool "'true'"
--> Err (Decoding "Expected bool, got: \"true\"")
fromString bool "42"
--> Err (Decoding "Expected bool, got: 42 (int)")
fromString bool "hello"
--> Err (Decoding "Expected bool, got: \"hello\"")
fromString bool "hello: 42"
--> Err (Decoding "Expected bool, got: { hello: 42 (int) }")
int : Decoder Basics.Int
Decode a YAML number into an Elm Int
.
fromString int "true"
--> Err (Decoding "Expected int, got: True (bool)")
fromString int "'true'"
--> Err (Decoding "Expected int, got: \"true\"")
fromString int "42" --> Ok 42
fromString int "'42'"
--> Err (Decoding "Expected int, got: \"42\"")
fromString int "3.14"
--> Err (Decoding "Expected int, got: 3.14 (float)")
fromString int "hello"
--> Err (Decoding "Expected int, got: \"hello\"")
fromString int "hello: 42"
--> Err (Decoding "Expected int, got: { hello: 42 (int) }")
float : Decoder Basics.Float
Decode a YAML number into an Elm Float
.
fromString float "true"
--> Err (Decoding "Expected float, got: True (bool)")
fromString float "'true'"
--> Err (Decoding "Expected float, got: \"true\"")
fromString float "42" --> Ok 42
fromString float "'42'"
--> Err (Decoding "Expected float, got: \"42\"")
fromString float "3.14" --> Ok 3.14
fromString float "hello"
--> Err (Decoding "Expected float, got: \"hello\"")
fromString float "hello: 42"
--> Err (Decoding "Expected float, got: { hello: 42 (int) }")
null : Decoder (Maybe a)
Decode a YAML null value into Nothing.
fromString null "" --> Ok Nothing
fromString null "null" --> Ok Nothing
fromString null "true"
--> Err (Decoding "Expected null, got: True (bool)")
fromString null "42"
--> Err (Decoding "Expected null, got: 42 (int)")
fromString null "hello: 42"
--> Err (Decoding "Expected null, got: { hello: 42 (int) }")
nullable : Decoder a -> Decoder (Maybe a)
Decode a nullable YAML value into an Elm value.
fromString (nullable int) "42" --> Ok (Just 42)
fromString (nullable int) "3.14"
--> Err (Decoding "Expected int, got: 3.14 (float)")
fromString (nullable int) "null" --> Ok Nothing
fromString (nullable int) "true"
--> Err (Decoding "Expected int, got: True (bool)")
list : Decoder a -> Decoder (List a)
Decode a YAML array into an Elm List
.
fromString (list int) "[1,2,3]" --> Ok [ 1, 2, 3 ]
fromString (list bool) "[ true, false ]" --> Ok [ True, False ]
fromString (list float) "true"
--> Err (Decoding "Expected list, got: True (bool)")
dict : Decoder a -> Decoder (Dict String a)
Decode a YAML record into an Elm Dict
.
import Dict exposing (Dict)
fromString (dict int) "{ alice: 42, bob: 99 }"
--> Ok (Dict.fromList [ ( "alice", 42 ), ( "bob", 99 ) ])
fromString (dict string) "42"
--> Err (Decoding "Expected record, got: 42 (int)")
field : String -> Decoder a -> Decoder a
Decode a YAML record, requiring a particular field.
fromString (field "x" int) "{ x: 3 }" --> Ok 3
fromString (field "x" int) "{ x: 3, y: 4 }" --> Ok 3
fromString (field "x" int) "{ x: true }" --> Err (Decoding "Expected int, got: True (bool)")
fromString (field "x" int) "{ y: 4 }" --> Err (Decoding "Expected property: x")
fromString (field "name" string) "{ name: Tom }" --> Ok "Tom"
The record can have other fields. Lots of them! The only thing this decoder
cares about is if x
is present and that the value there can be decoded.
Check out mapN
and andMap
to see how to decode multiple fields!
at : List String -> Decoder a -> Decoder a
Decode a nested YAML record, requiring certain fields.
yaml : String
yaml = """{ person: { name: Tom, age: 42 } }"""
fromString (at ["person", "name"] string) yaml --> Ok "Tom"
fromString (at ["person", "age"] int) yaml --> Ok 42
fromString (at ["not", "here"] int) yaml --> Err (Decoding "Expected property: not")
This is really just shorthand for field
. Equivalent to
saying things like:
fromString (field "person" (field "name" string)) yaml
--> fromString (at [ "person", "name" ] string) yaml
oneOf : List (Decoder a) -> Decoder a
Try a list of different decoders. Pick the first working one.
This can be useful if the YAML comes in different formats. For
example, if you want to read an array of numbers but some of them
are null
.
fromString (oneOf [ field "a" int, field "b" int ]) "{a: 42}" --> Ok 42
fromString (oneOf [ field "a" int, field "b" int ]) "{b: 3}" --> Ok 3
fromString (oneOf [ field "a" int, field "b" int ]) "true" --> Err (Decoding "Empty")
maybe : Decoder a -> Decoder (Maybe a)
Makes its argument optional.
A decoder which returns Nothing
when it fails.
Helpful when dealing with optional fields: you probably want to
use maybe
outside field
or at
. Here are a few examples:
yaml : String
yaml = "{ name: Stacy, age: 27, temperature: 37.6 }"
fromString (maybe (field "age" int)) yaml --> Ok (Just 27)
fromString (maybe (field "height" float)) yaml --> Ok Nothing
These two examples decode to Nothing
if a field does not exist.
They say there may be an age
field, if it exists it must
be an integer. And there may be a height
field, if it exists
it must be a Float
.
You can also decode to Nothing
if a field is a different type:
fromString (field "temperature" (maybe string)) yaml --> Ok Nothing
fromString (field "age" (maybe int)) yaml --> Ok (Just 27)
These two examples say you must have temperature
and
age
fields and the content may be integers.
map : (a -> b) -> Decoder a -> Decoder b
Transform the result of a decoder. For example, get the length of a string:
stringLength : Decoder Int
stringLength =
map String.length string
fromString stringLength "hello" --> Ok 5
map
runs the decoder (string
in the example above) and
gives the result to the function (String.length
in the
example above).
map2 : (a -> b -> c) -> Decoder a -> Decoder b -> Decoder c
Try two decoders and then combine the result. You can use this to decode records with 2 fields:
type alias Point = { x : Float, y : Float }
point : Decoder Point
point =
map2 Point
(field "x" float)
(field "y" float)
fromString point "{x: 1.2, y: 2.5}" --> Ok { x = 1.2, y = 2.5 }
map2
runs each decoder in order and privides the results to the
function (taking 2 arguments; the Point
constructor in the example
above).
map3 : (a -> b -> c -> d) -> Decoder a -> Decoder b -> Decoder c -> Decoder d
Try three decoders and then combine the result.
map4 : (a -> b -> c -> d -> e) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e
Try four decoders and then combine the result.
map5 : (a -> b -> c -> d -> e -> f) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f
Try five decoders and then combine the result.
map6 : (a -> b -> c -> d -> e -> f -> g) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g
Try six decoders and then combine the result.
map7 : (a -> b -> c -> d -> e -> f -> g -> h) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h
Try seven decoders and then combine the result.
map8 : (a -> b -> c -> d -> e -> f -> g -> h -> i) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder i
Try eight decoders and then combine the result.
andMap : Decoder a -> Decoder (a -> b) -> Decoder b
Can be useful when decoding large objects incrementally.
For example, this can be used to to mapN
for any number of arguments. See the
documentation for Json.Decode.Extra.andMap
for a
thorough explanation of how to use andMap
.
-- map4
sumFields : Decoder Int
sumFields =
succeed (\a b c d -> a + b + c + d)
|> andMap (field "a" int)
|> andMap (field "b" int)
|> andMap (field "c" int)
|> andMap (field "d" int)
fromString sumFields "{a: 2, b: 5, c: -3, d: 9}" --> Ok 13
lazy : (() -> Decoder a) -> Decoder a
Decode YAML with a recursive (nested) structure.
An example is nested comments:
type alias Comment =
{ comment : String
, responses : Responses
}
type Responses = Responses (List Comment)
comment : Decoder Comment
comment =
map2 Comment
(field "comment" string)
(field "responses" (map Responses (list (lazy (\_ -> comment)))))
yaml : String
yaml = "{ comment: 'hello world', responses: [ {comment: 'hello', responses: [] } ] }"
fromString comment yaml
--> Ok { comment = "'hello world'", responses = Responses [{ comment = "'hello'", responses = Responses [] }] }
By using lazy
you make sure that the decoder only expands
to be as deep as the YAML structure. You can read more about
recursive data structures
here.
value : Decoder Value
Do not do anything with a YAML value, just bring it into
Elm as a Value
. This can be useful if you have particularly
complex data that you would like to deal with later. Or if you
are going to send it out of a port and do not care about its
structure.
fail : String -> Decoder a
Ignore the YAML and make the decoder fail. This is handy
when used with oneOf
or andThen
where you want to give a
custom error message in some case.
See the andThen docs for an example.
succeed : a -> Decoder a
Ignore the YAML and produce a given Elm value.
fromString (succeed 42) "true" --> Ok 42
fromString (succeed 42) "[]" --> Ok 42
fromString (succeed 42) "{ " |> Result.toMaybe --> Nothing -- Long parsing error
andThen : (a -> Decoder b) -> Decoder a -> Decoder b
Create decoders that depend on previous results.
For example, if you decoding depends on a version
field:
type alias Info =
{ data: Int }
info : Decoder Info
info =
field "version" int
|> andThen infoHelp
-- infoHelp takes the "version" integer as its argument
infoHelp : Int -> Decoder Info
infoHelp version =
case version of
4 ->
infoDecoder4
3 ->
infoDecoder3
_ ->
fail ("Version "
++ String.fromInt version
++ " is not supported.")
infoDecoder4 : Decoder Info
infoDecoder4 =
map Info (field "v4data" int)
infoDecoder3 : Decoder Info
infoDecoder3 =
map (Info << String.length) (field "v3data" string)
fromString info "{ version: 3, v3data: test }"
--> Ok (Info 4)
fromString info "{ version: 4, v4data: 5 }"
--> Ok (Info 5)
fromString info "{ version: 1 }"
--> Err (Decoding "Version 1 is not supported.")
fromResult : Result String a -> Decoder a
Convert a Result into a decoder.
This can be useful when you want to use functions that operate on Result
in decoders,
in combination with andThen
.
validate : String -> Result String String
validate s =
case s of
"" -> Err "is empty"
_ -> Ok s
fromString (string |> andThen (validate >> fromResult)) "\"not empty\"" --> Ok "not empty"
fromString (string |> andThen (validate >> fromResult)) "" --> Err (Decoding "is empty")
fromMaybe : String -> Maybe a -> Decoder a
Convert a Maybe into a decoder.
This can be useful when you want to use functions that operate on Maybe
in decoders,
in combination with andThen
.
fromString (string |> andThen (String.toInt >> fromMaybe "Not an int")) "\"42\"" --> Ok 42
fromString (string |> andThen (String.toInt >> fromMaybe "Not an int")) "" --> Err (Decoding "Not an int")