Joseph Kain bio photo

Joseph Kain

Professional Software Engineer learning Elixir.

Twitter LinkedIn Github

I’ve been writing about using Dialyzer with Elixir lately. But, I’ve started working on a new side project and had a need to parse YAML files. So, this is a post of opportunity in which I’ll share my experineces learning to use yamerl. Expect more such posts in the future as I work on this new project.

Yamerl is an Erlang module for parsing YAML files. I didn’t find any good resources on using yamerl with Elixir. So I’m writing up what I’ve learned in this post. yamerl is available on github and has a README that gives good instructions for Erlang. I’ll try to translate some of that into Elixr.

I wrote this post about a week ago and after writing it I cam across Parsing YAML in Elixir from Alexander Panek. The post goes into good detail on both yamerl and yamler. Definitely worth a read.

Installation:

The instructions say to use rebar but my Elixir project uses mix. Fortunately, mix has support for rebar based projects and it works with yamerl. I was able to simply include yamerl as a github based dependency:

defp deps do
  [
    {:yamerl, github: "yakaz/yamerl"}
  ]
end

and run

mix deps.get
mix deps.compile

Setup

The yamerl process needs to be started. In an Elixir / Mix application this is as simple as adding yamerl to the applications section of mix.exs:

def application do
  [applications: [:logger, :yamerl]]
end

Parsing a YAML file in Elixir

The yamerl README has the following test YAML data

# applications.yaml
- application: kernel
  version:     2.15.3
  path:        /usr/local/lib/erlang/lib/kernel-2.15.3
- application: stdlib
  version:     1.18.3
  path:        /usr/local/lib/erlang/lib/stdlib-1.18.3
- application: sasl
  version:     2.2.1
  path:        /usr/local/lib/erlang/lib/sasl-2.2.1

I’ve saved this data in a file called applications.yaml so I can test load it. Then I started up iex

$ iex -S mix

In iex I loaded up the YAML file like this:

iex(1)> :yamerl_constr.file("applications.yaml")
[[[{'application', 'kernel'}, {'version', '2.15.3'},
   {'path', '/usr/local/lib/erlang/lib/kernel-2.15.3'}],
  [{'application', 'stdlib'}, {'version', '1.18.3'},
   {'path', '/usr/local/lib/erlang/lib/stdlib-1.18.3'}],
  [{'application', 'sasl'}, {'version', '2.2.1'},
   {'path', '/usr/local/lib/erlang/lib/sasl-2.2.1'}]]]

Extracting the data

First of all we need to know how the parsed data is formatted. This is a direct quote from the README:

% List of documents; again, only one document here.
[
 % List of mappings.
 [
  % Mapping, represented as a proplist: each entry has the form {Key, Value}.
  [
   {"application", "kernel"},
   {"version", "2.15.3"},
   {"path", "/usr/local/lib/erlang/lib/kernel-2.15.3"}
  ], [
   {"application", "stdlib"},
   {"version", "1.18.3"},
   {"path", "/usr/local/lib/erlang/lib/stdlib-1.18.3"}
  ], [
   {"application", "sasl"},
   {"version", "2.2.1"},
   {"path", "/usr/local/lib/erlang/lib/sasl-2.2.1"}
  ]
 ]
]

Ok, this is very general especially in that it provides support for multiple documents. For my needs (just like the example), I have only one document. I can extract the first document from the list like this:

iex(10)> [ document | _ ] = :yamerl_constr.file("applications.yaml")
# Entire structure omitted
iex(11)> document
[[{'application', 'kernel'}, {'version', '2.15.3'},
  {'path', '/usr/local/lib/erlang/lib/kernel-2.15.3'}],
 [{'application', 'stdlib'}, {'version', '1.18.3'},
  {'path', '/usr/local/lib/erlang/lib/stdlib-1.18.3'}],
 [{'application', 'sasl'}, {'version', '2.2.1'},
  {'path', '/usr/local/lib/erlang/lib/sasl-2.2.1'}]]

Now, I’ll look at the first mapping which coresponds to the kernel:

iex(12)> [ kernel | _ ] = document
# Entire document omitted
iex(13)> kernel
[{'application', 'kernel'}, {'version', '2.15.3'},
 {'path', '/usr/local/lib/erlang/lib/kernel-2.15.3'}]

Before I went back and read the README carefully I thought this was a Keyword list but it turns out that it isn’t. I was very confused why the Keyword module couldn’t look up any of the values in the data structure. After I went back and read the README more diligently I realized that the data structure is an Erlang proplist which can be accessed with the :proplists module like this:

iex(14)> :proplists.get_value('version', kernel)
'2.15.3'
iex(15)> :proplists.get_value('path', kernel)
'/usr/local/lib/erlang/lib/kernel-2.15.3'
iex(16)> :proplists.get_value('application', kernel)
'kernel'

It is worth noting that the keys and values in the proplist are stored as Erlang style strings which are given the type char_list in Elixir. To convert the values to Elixir String we can use List.to_string/1 like this:

iex(17)> :proplists.get_value('version', kernel) |> List.to_string
"2.15.3"
iex(18)> :proplists.get_value('path', kernel) |> List.to_string
"/usr/local/lib/erlang/lib/kernel-2.15.3"
iex(19)> :proplists.get_value('application', kernel) |> List.to_string
"kernel"

In my case I’ll be using literals for the keys so I can just use single quotes to make char_list literals. But, if you have String variables in Elixir and want to use them as keys when getting values from the proplist then you can use String.to_char_list/1 to convert.