LoginSignup
7
2

Elixirでネストした構造体を全部Mapに変換する方法

Last updated at Posted at 2021-09-21

Elixir SlackElixir JP Slackを毎日眺めるのを日課にしています
そこでいろんなアイデアを得ることがあるのですが、今日は一つご紹介します

やりたいこと

構造体とMapが深いネストで同居していている場合に、全てMapにしたい

架空の構造体

こういう構造体があったとします

defmodule Hello, do: defstruct [:x]

最上階層のみを構造体からMapへ変換

iex> s = %Hello{x: %Hello{x: 1}}

iex> Map.from_struct(s)
%{x: %Hello{x: 1}}

ネスト構造の中のすべての構造体をMapへ変換

defmodule Mnishiguchi.Map do
  def unstruct(%NaiveDateTime{} = encodable_struct), do: encodable_struct
  def unstruct(%DateTime{} = encodable_struct), do: encodable_struct
  def unstruct(%Time{} = encodable_struct), do: encodable_struct
  def unstruct(%Date{} = encodable_struct), do: encodable_struct

  def unstruct(struct_or_map) when is_map(struct_or_map) do
    struct_or_map
    |> Map.delete(:__struct__)
    |> Map.delete(:__meta__)
    |> Map.new(fn
      {k, v} when is_map(v) -> {k, unstruct(v)}
      {k, v} when is_list(v) -> {k, Enum.map(v, &unstruct/1)}
      kv -> kv
    end)
  end

  def unstruct(not_struct_or_map) do
    not_struct_or_map
  end
end

iex> s = %Hello{x: %Hello{x: %Hello{x: [%Hello{x: 1}, %Hello{x: %Hello{x: 1}}]}}}

iex> Mnishiguchi.Map.unstruct(s)
%{x: %{x: %{x: [%{x: 1}, %{x: %{x: 1}}]}}}

Mapと構造体は若干異なる

ElixirのMapと構造体はほぼ同じなのですが、挙動が若干異なります

違いの一つにAccess behaviourが挙げられます
構造体はAccess behaviourを実装していないので、[]でアクセスしようとするとUndefinedFunctionErrorになります

# Map
iex> m = %{x: 1}

iex> m.x
1

iex> m[:x]
1
# 構造体
iex> s = %Hello{x: 1}

iex> s.x
1

iex> s[:x]
** (UndefinedFunctionError) function Hello.fetch/2 is undefined (Hello does not implement the Access behaviour)
    Hello.fetch(%Hello{x: 1}, :x)
    (elixir 1.12.2) lib/access.ex:285: Access.get/3
7
2
0

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
7
2