Transforming list of structs to map

Hi,
Is there an easier way to convert a list of structs to map. Extracting fields from the struct and in the nested struct and make it a map.

The list of struct:

[
  %Kaiwa.Chat.Message{
    __meta__: #Ecto.Schema.Metadata<:loaded, "messages">,
    id: 1,
    inserted_at: ~N[2020-04-03 11:46:24],
    room: #Ecto.Association.NotLoaded<association :room is not loaded>,
    room_id: 1,
    text: "this is a test message",
    updated_at: ~N[2020-04-03 11:46:24],
    user: %Kaiwa.Accounts.User{
      __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
      id: 1,
      inserted_at: ~N[2020-04-02 04:10:00],
      messages: #Ecto.Association.NotLoaded<association :messages is not loaded>,
      name: "Mr Testing",
      password: nil,
      password_hash: "$argon2id$v=19$m=131072,t=8,p=4$26a9JgOy+rmuhjQ7WZD1bg$ZJe55e9UdfrWqm0E4rMnGFG7mA/Wyr7fWdi1f1tZP+8",
      rooms: #Ecto.Association.NotLoaded<association :rooms is not loaded>,
      updated_at: ~N[2020-04-02 04:10:00],
      username: "test" 
    },
    user_id: 1
  }
] 

My attempt:

Enum.map(messages, fn message ->
  user = Map.from_struct(message.user)

  %{
    message_id: message.id,
    text: message.text,
    created_at: message.inserted_at,
    user_id: user.id,
    name: user.name
  }
end)

Expected result:

[
  %{
    created_at: ~N[2020-04-03 11:46:24],
    message_id: 1,
    name: "Mr Testing",
    text: "this is a test message", 
    user_id: 1
  }
]

Is there a better way to achieve the expected result or my attempt is okay?

I think your version is ok

This is going to include a lot of keys you don’t want like __meta__ and password_hash (very bad). If you’re trying to create a JSON response for an API I recommend just explicitly making maps for each of the things you want to expose.

Thank you for the advised.

I’m wondering if there’s an easy way to picking out the values you needed in a struct directly because I just convert the struct to map so I can easily access the fields I want to encode.

user_map = Map.take(user, [:name, :id, :username])

Thank you.

I would be more comfortable with something that asserts the shape of the data it receives so I can catch bugs early:

    Enum.map(
      messages,
      fn %{
           id: message_id,
           text: text,
           inserted_at: created_at,
           user: %{id: user_id, name: name}
         } ->
        %{
          message_id: message_id,
          text: text,
          created_at: created_at,
          user_id: user_id,
          name: name
        }
      end
    )

If even one message doesn’t comply with the expected structure you’ll get an appropriate error. Although to be fair, your code would also blow up but IMO in a slightly less intuitive fashion. I’d prefer mine because it also makes intent clearer.