はじめに
Elixirで、階層の深い構造からJSONPathやXPathのように簡単に値を取り出すことを目指していろいろ試していました。
結局のところあまりしっくりくるやり方がなく、自分でElixirのデータ構造に合った仕組みをつくってみました。
Elixpathという名前でHexにアップロードしています。初Hexだったので勉強になりました。
特長
Elixpathの利点(になるよう意識した点)を挙げていきます。
簡潔な記述で幅広い構造から要素を取得できる
生のElixirでも、keyがatomであれば下記のように簡潔に要素が取得できると思います。
# nested maps with atom key
iex> deep_map.key1.key2.key3
# nested keyword lists
iex> deep_keyword[:key1][:key2][:key3]
ただ、例えばどこかの階層にリストを含む場合は上記のように簡単な書き方はできません。
Elixpathを使うと、そのような場合にもきちんと対応できます。
# nested maps and lists
iex> Elixpath.get!(deep_map, ~S/.:atom_key."string_key"[0]/)
iex> Elixpath.get!(deep_map, ~S/.:atom_key."string_key"[-1]/)
# w/o Elixpath
iex> get_in(deep_map, [:atom_key, "string_key", Access.at(0)])
iex> deep_map.atom_key["string_key"] |> List.last
ワイルドカード対応
*
をワイルドカードとして使えます。
iex> users = [
...> john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
...> mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
...> ]
iex> Elixpath.query!(users, ".*.:languages[0]")
["Erlang", "Elixir"]
# w/o Elixpath
iex> Keyword.values(users) |> Enum.map(& (&1.languages |> Enum.at(0)))
["Erlang", "Elixir"]
子孫要素の取得
..子孫要素のkey
でそのkeyにマッチする子要素・子孫要素を取得できます。たとえば、どの階層でもよいのでとにかく:name
を取得したいときは下記のようにします。
iex> Elixpath.query!(users, "..:name")
["John", "Mary"]
感想
Elixirのライブラリをきちんと作ってHexにアップロードするのは初めてだったので、いろいろと学びがありました。
ライブラリとしての体裁を整える
きちんとしたライブラリっぽくしてHexにアップロードすることを目標にしていたので、
- きちんとドキュメントを書いてExDocで出力してみたり
- テストをきちんと書いてexcoverallsでカバレッジを確認してみたり
- Shields.ioでバッジを貼り付けてみたり
と頑張ってみました。他のライブラリを参考に見よう見まねでやってしまった部分もあり、要点を押さえきれていないかもしれませんが、経験を積んでいきたいです。
CIかんたん。べんり。
Elixirの複数バージョン対応の確認にTravis CIを使ってみました。
また、せっかくexcoverallsを入れたのでプルリクエストごとにテストカバレッジの変化を通知してくれるcodecovも設定してみましたが、これも便利そう。Coverallsも試しましたが、codecovのほうがすっきりしていて使いやすかったです。
どちらも自身で設定するのは未経験でしたが、公式ドキュメントで事足りる簡単さで驚きました。
パーサコンビネータ
パスの文字列をパースするのにNimbleParsecを使っています。慣れてからはとても便利だったのですが、きちんとパーサを書くのも初めてだったので慣れるまでは苦戦しました。別途記事にしたいかも。
まとめ
Elixpath、よかったら使ってみてください。
お気付きの点やアドバイスもお待ちしております!