MaybeJustJames/yaml/1.0.0

NOTE: the latest version is 2.1.7

Yaml.Decode

Turn YAML into Elm values. The library is structured a similar way to Json.Decode, so if you haven't worked with decoders before, reading through the guide may be helpful.

Table of Contents

type Decoder a

A value that knows how to decode YAML values.

There is a whole section in guide.elm-lang.org about decoders, so check it out for a more comprehensive introduction!

Run Decoders

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 ...
type alias Value =
Value

Represents a YAML tree.

type Error
= Parsing String
| Decoding String

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 strcutured error into a String that is nice for debugging.

Primitives

string : Decoder String

Decode a YAML string into an Elm String.

fromString string "true"      == Err ...
fromString string "'true'"    == Ok "true"
fromString string "42"        == Err ...
fromString string "'42'"      == Ok "42"
fromString string "hello"     == Ok "hello"
fromString string "hello: 42" == Err ...
bool : Decoder Bool

Decode a YAML boolean into an Elm Bool.

fromString bool "true"      == Ok True
fromString bool "'true'"    == Err ...
fromString bool "42"        == Err ...
fromString bool "hello"     == Err ...
fromString bool "hello: 42" == Err ...
int : Decoder Int

Decode a YAML number into an Elm Int.

fromString int "true"      == Err ...
fromString int "'true'"    == Err ...
fromString int "42"        == Ok 42
fromString int "'42'"      == Err ...
fromString int "3.14"      == Err ...
fromString int "hello"     == Err ...
fromString int "hello: 42" == Err ...
float : Decoder Float

Decode a YAML number into an Elm Float.

fromString float "true"      == Err ...
fromString float "'true'"    == Err ...
fromString float "42"        == Ok 42
fromString float "'42'"      == Err ...
fromString float "3.14"      == Ok 3.14
fromString float "hello"     == Err ...
fromString float "hello: 42" == Err ...
null : Decoder (Maybe a)

Decode a YAML null value into Nothing.

fromString null ""          == Ok Nothing
fromString null "null"      == Ok Nothing
fromString null "true"      == Err ...
fromString null "42"        == Err ...
fromString null "hello: 42" == Err ...

Data Structures

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 ...
fromString (nullable int) "null" == Ok Nothing
fromString (nullable int) "true" == Err ...
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]
dict : Decoder a -> Decoder (Dict String a)

Decode a YAML record into an Elm Dict.

fromString (dict int) "{ alice: 42, bob: 99 }"
  == Ok (Dict.fromList [("alice",42), ("bob",99)])

Record Primitives

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 ...
fromString (field "x" int) "{ y: 4 }"            == Err ...


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 map2 to see how to decode multiple fields!

at : List String -> Decoder a -> Decoder a

Decode a nested YAML record, requiring certain fields.

yaml = """{ person: { name: Tom, age: 42 } }"""


fromString (at ["person", "name"] string) yaml == Ok "Tom"
fromString (at ["person", "age"] int) yaml     == Ok 42

This is really just shorthand for field. Equivalent to saying things like:

field "person" (field "name" string) == at ["person", "name"] string

Inconsistent Structure

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.

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 = "{ 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 int)) == Ok Nothing
fromString (field "age" (maybe int))         == Ok (Just 27)

These two examples say you must have temperature and age fields and the content may be integers.

Maps

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.

(a -> b -> c -> d -> e)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e

Try four decoders and then combine the result.

(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.

(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.

(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.

(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.

Fancy Decoding

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 = "{ 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) "{ "   == Err ... -- this in not a valid YAML string
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:

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 " ++ toString version ++ " is not supported."


-- infoDecoder4 : Decoder Info
-- infoDecoder3 : Decoder Info