Convert a nested struct into a nested map

Here is example working code:

# First of all minimal definition in order to make code compillable
defmodule Nadia.Model.Chat do
  defstruct [:first_name, :id, :last_name, :photo, :title, :type, :username]
end

defmodule Nadia.Model.User do
  defstruct [:first_name, :id, :last_name, :username]
end

defmodule Nadia.Model.Message do
  defstruct [
    :audio,
    :caption,
    :channel_chat_created,
    :chat,
    :contact,
    :date,
    :delete_chat_photo,
    :document,
    :edit_date,
    :entities,
    :forward_date,
    :forward_from,
    :forward_from_chat,
    :from,
    :group_chat_created,
    :left_chat_member,
    :location,
    :message_id,
    :migrate_from_chat_id,
    :migrate_to_chat_id,
    :new_chat_member,
    :new_chat_photo,
    :new_chat_title,
    :photo,
    :pinned_message,
    :reply_to_message,
    :sticker,
    :supergroup_chat_created,
    :text,
    :venue,
    :video,
    :voice
  ]
end

# Here is code which would work only for your specific case
defmodule Example do
  def sample(map) do
    message = ensure_map(map.message)
    chat = ensure_map(message.chat)
    from = ensure_map(message.from)
    updated_message = %{message | chat: chat, from: from}
    %{map | message: updated_message}
  end

  defp ensure_map(%{__struct__: _} = struct), do: Map.from_struct(struct)
  defp ensure_map(data), do: data
end

# Here is more dynamic way
# No matter how many maps and lists have you nested
# It will iterate over all map or list elements and finally ensure that they are not structs
# NOTE: If you want you can add extra guard to limit possible __struct__ value
# For example you probably do not want to create maps from Date, DateTime, NaiveDateTime and Time structs
defmodule ExampleDynamic do
  def sample(map), do: :maps.map(&do_sample/2, map)

  def do_sample(_key, value), do: ensure_nested_map(value)

  defp ensure_nested_map(list) when is_list(list), do: Enum.map(list, &ensure_nested_map/1)

  # NOTE: In pattern-matching order of function guards is important!
  # @structs [Date, DateTime, NaiveDateTime, Time]
  # defp ensure_nested_map(%{__struct__: struct} = data) when struct in @structs, do: data

  defp ensure_nested_map(%{__struct__: _} = struct) do
    map = Map.from_struct(struct)
    :maps.map(&do_sample/2, map)
  end

  defp ensure_nested_map(data), do: data
end

# Your example data which is needed for compilation
data = %{
  callback_query: nil,
  channel_post: nil,
  chosen_inline_result: nil,
  edited_message: nil,
  inline_query: nil,
  message: %Nadia.Model.Message{
    audio: nil,
    caption: nil,
    channel_chat_created: nil,
    chat: %Nadia.Model.Chat{
      first_name: "Christian",
      id: 543_211_234,
      last_name: "Tovar",
      photo: nil,
      title: nil,
      type: "private",
      username: "ChristianTovar"
    },
    contact: nil,
    date: 1_562_605_521,
    delete_chat_photo: nil,
    document: nil,
    edit_date: nil,
    entities: nil,
    forward_date: nil,
    forward_from: nil,
    forward_from_chat: nil,
    from: %Nadia.Model.User{
      first_name: "Christian",
      id: 543_211_234,
      last_name: "Tovar",
      username: "ChristianTovar"
    },
    group_chat_created: nil,
    left_chat_member: nil,
    location: nil,
    message_id: 714,
    migrate_from_chat_id: nil,
    migrate_to_chat_id: nil,
    new_chat_member: nil,
    new_chat_photo: [],
    new_chat_title: nil,
    photo: [],
    pinned_message: nil,
    reply_to_message: nil,
    sticker: nil,
    supergroup_chat_created: nil,
    text: "google meets",
    venue: nil,
    video: nil,
    voice: nil
  },
  update_id: 412_827_321
}

# Here we are ensuring that both ways gives exactly same results
Example.sample(data) == ExampleDynamic.sample(data)

Please let me know if you have any questions.

Related documentation:

  1. &:maps.map/2
  2. &Enum.map/2
  3. &Map.from_struct/1
  4. Guards
  5. Map (describes specific update syntax)
12 Likes