tovarchristian21

tovarchristian21

Convert a nested struct into a nested map

Hello guys, can you please help turn this map into a pure map. Some of the members of the map are still structs.

%{
  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: 543211234,
      last_name: "Tovar",
      photo: nil,
      title: nil,
      type: "private",
      username: "ChristianTovar"
    },
    contact: nil,
    date: 1562605521,
    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: 543211234,
      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: 412827321
}

Marked As Solved

Eiji

Eiji

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
Post #7

Also Liked

idi527

idi527

:wave:

Why do you want to convert these structs to maps? Maybe there’s an easier way to accomplish what you want … Personally, I don’t remember having problems with :nadia's structs.

ityonemo

ityonemo

Still feels a bit verbose

def convert(data) when is_struct(data) do
  data |> Map.from_struct |> convert
end

def convert(data) when is_map(data) do
  Map.new(data, fn {k, v} -> {k, convert(v)})
end

def convert(data) when is_list(data) do
  Enum.map(data, &convert/1)
end

def convert (data), do: data
NobbZ

NobbZ

You need to implement the nesting on your own. But that is a lot more expensive, as it needs to reconstruct the maps from scratch

NobbZ

NobbZ

No, Enum.each/2 returns :ok. Also you need to decide for each key in the map/struct if you want to convert it or not.

elielhaouzi

elielhaouzi

  def map_from_struct(%_{__meta__: %{__struct__: _}} = schema) when is_map(schema) do
    schema
    |> Map.from_struct()
    |> from_ecto_struct()
  end

  def map_from_struct(map) when is_map(map) do
    map
    |> Enum.reduce(%{}, fn {key, value}, acc ->
      value =
        case value do
          %_{__meta__: %{__struct__: _}} ->
            from_ecto_struct(value)

          value when is_list(value) ->
            value |> Enum.map(&from_ecto_struct/1)

          _ ->
            value
        end

      Map.put_new(acc, key, value)
    end)
  end

We write a lib of utils around ecto https://github.com/annatel/antl_utils_ecto

one of the functionality is
AntlUtilsEcto.map_from_struct(my_schema)

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement