LoginSignup
4

More than 5 years have passed since last update.

ElixirのMapについて。また、階層の深いMapを処理する方法。

Last updated at Posted at 2018-12-17

この記事は「Elixir Advent Calendar 2018」の18日目です!
昨日の記事は @takasehideki さんの「ElixirでIoT#3.1:ESP32やSTM32でElixirが動く!AtomVMという選択肢」でした。

今年は何か記事を書こうかなと思いElixirを選択しましたが、みなさん内容の濃い記事を書かれていて何を書くか凄く悩みましたが、今回はMapについて書こうと思います。初めてElixirを使う方たちに参考になればと思います!

Mapを処理する

今回は、僕がElixirを使い始めて一番最初につまずいた、Mapの使用方法、処理方法をいくつか紹介したいと思います。どれを使用するのが良いのかは、使用するデータに応じて変えるのが良いと思います。

まず、この様なMap用意します。
map = %{message: "Hello World", user: "example"}

方法1:パターンマッチング

# 正確なkeyの場合
%{message: message, user: user} = map

IO.puts message
> "Hello World"
IO.puts user
> "example"

# 無効なkeyの場合
%{hello: hello} = map
> ** (MatchError) no match of right hand side value

これは関数の引数を取得する時によく使いますね。

def print_message(%{message: message}) do
    IO.puts message
end

print_message(map)
> "Hello World"

方法2:map.keyで取得(名前は知りません、誰か教えてください!)

# 正確なkeyの場合
IO.puts map.message
> "Hello World"

# 無効なkeyの場合
IO.puts map.hello
> ** (KeyError) key :hello not found in: %{message: "Hello World", user: "example"}.

方法3:map[key]で取得(名前知りません。)

# 正確なkeyの場合
IO.puts map[:message]
> "Hello World"

# 無効なkeyの場合
IO.inspect map[:hello]
> nil

方法4:get_in()Map.getで取得

階層が深く無い時、入れ子になってない時は、Map.getもしくは、Map.fetchを使うのが良いと思います!

# 正確なkeyの場合
IO.puts get_in(map, [:message])
> "Hello World"

IO.puts Map.get(map, :message)
> "Hello World"

# 無効なkeyの場合
IO.inspect get_in(map, [:hello])
> nil

IO.puts Map.get(map, :hello)
> nil

どの方法をどのタイミングで使用するか。

ここからは、僕が個人的にこのような時にこの関数を使うと言うことを紹介していきたいと思います!

1: JSON APIを作成する時の、Controllerの関数。

皆さん、この様なコードを良く見ると思います。

def show(conn, %{"id" => id}) do
    user = Accounts.get_user_by_otaku_id!(id)
    render(conn, "show.json", user: user)
end

ここはパラメーターにidがあること前提で走らせる処理が書かれています。そこに、${"apple" => "pie"}という値が来たらどうなるでしょうか? MatchErrorになります。ControllerでMatchErrorになるとActionClauseErrorを生成します。また、PhoenixはActionClauseErrorが生成されるとステータスコード400を返す様な仕組みになっています。エンドポイントに対して不正なデータを送っているため、ステータスコード400と言うのは正解です。

もちろん、間違えやすいエンドポイントだった場合パラメーターにidが有るかどうか確認して、手動で分かりやすいエラー文と共にステータスを返す様な仕様にしても良いですが、怠け者の私はそんな事はしません。

2: 値の有無を確認したい時。

あたいの有無を確認したい時、例えばパースしたJsonデータにBirthdayと言うデータが入っているか確認したい時などはパターンマッチングで値を取得してしまうとその値がなかった時にMatchErrorで処理が止まってしまいます。なので、そんな時はmap[key]もしくはMap.fetchなどを使用してnilチェックを行うのが良いと思います。

階層の深いMapを処理する

古〜〜〜〜〜いAPIを叩いた時に、Schemaと違うじゃん!!みたいなことありますでしょうか?例え古くなくても、クローラーが走ってるAPIなどでは返ってくるデータが必ずしも一定じゃない事は良くあります。しかも、階層が何層にも深くなってて1つの値の配列が欲しいのにどう処理すれば良いのか分からなくなることがあります。そんな時に、この方法を使えば良いのでは無いのかと言うのを紹介します!いや、こう言う簡単な方法が有るじゃん。と言う方教えてくださると助かります。

まず、モックのデータです。

map = %{
    "description": "これは説明。",
    "properties": %{
        "access_token": %{...},
        "authenticated_user": %{...},
        "comment": %{...},
        "item": %{
            "description": "アイテム",
            "links": [
                %{
                    "type": "リンク種類",
                    "description": "リンクの説明",
                    "href": "リンク",
                    "method": "GET",
                    "rel": "リファレンス",
                    "title": "タイトル"
                }
            ]
        }
    }
}

access_tokenのデータが欲しい

# access_tokenが有る場合
get_in(map, ["properties", "access_token"])
> %{...}

# access_tokenが無い場合
get_in(map, ["properties", "access_token"])
> nil

linkstypeaの物だけ欲しい。

get_inの引数にある配列には、key以外に、

  • :get
  • アクセスするデータ
  • この関数の次に実行される関数

の3つを引数とした関数も渡すことができます。例としてlinkstypeaの物だけ欲しい場合

# データをEnum.filterでtypeがaの物だけにfilterしています。
# また、filterされた配列の要素全てに対して、渡された次の関数を実行しています。
justA = fn :get, data, next ->
  data
  |> Enum.filter(fn x -> x["type"] == "a" end)
  |> Enum.map(next)
end

get_in(map, ["properties", "item", "links", justA])
>[
    %{
        "type": "a",
        ...
    },
    %{
        "type": "a",
        ...
    }
]

モックデータにはtypeaの物はありませんでしたが、この様な配列が出力されるはずです。
また、配列の最後に"href"と入れると、typeahrefだけを取得できます。

get_in(map, ["properties", "item", "links", justA, "href"])
>[ "href", "href", "href" ]

データが不親切だと言うのはスルーしてください笑 Jsonデータ以外でも使用できるのでぜひ使ってみてください!

最後に

今回はMapの処理方法をいくつか紹介しました!知っている方にとっては面白く無い内容だったかもしれないですし、たくさんElixirのコードを触っているわけでも無いので、間違っている部分やもっと簡単にできる部分があったかもしれません、その場合はぜひコメントで教えてください!すごく助かります!

明日の「Elixir Advent Calendar 2018」@ma2geさんです!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4