Is there way to create map and conditionally exclude some keys?

Example if I’m creating this map

map = %{
  field1: "value1",
  field2: "value2"
}

is it possible conditionally not include field2? Now in my code I’ll use Map.drop to remove fields afterwards. But is there anything I could use during creation to achieve this?

Why not just:

map = %{field1: "value1"}
map = if(somecond, do: Map.put_new(map, field2, "value2"), else: map)

And you can chain it on down as you wish?

1 Like

If you’re transforming some input into a structured output, perhaps this is a good use case for a struct? If you struct(MyStruct, map_or_list) then you will only get map elements that are defined for a struct.

…sound like http://xyproblem.info/ ?

Why do you need to build everything, and then drop something by condition?

Why don’t you create common fields, and then add more as needed, instead?

Perhaps

map = conditionally_add_value(%{field1: "value1"}, color == :green, :field2, "value2")

Whereas conditionally_add_value/3 could be implemented as:

def conditionally_add_value(map, cond, _key, _value) when cond in [nil, false], do: map
def conditionally_add_value(map, _cond, key, value), do: Map.put(map, key, value)

edit

If calculating the value of the key or value though is computationally expensive, I’d use an if instead as @OvermindDL1 suggested. My version will always calculate those values, whether they are needed or not. Besides of using if one could also avoid that by using a macro, but that one I’m not able to write free hand right now, still undercoffeeinated :wink:

1 Like

Thanks I think I’ll use this instead of dropping them afterwards.

I need to creare result from Phoenix view that is then serialized to JSON. Sometimes returned object has more fields depenpending if user is creator of some data.

You are right I should have added that information to my question.

Your answer is same as very first answer to this thread by OvermindDL1, just not as complete one and doesn’t really add anything.

Thanks for this snippet! I’m sure it’s going to be helpful. In my case I have those values ready so no computation needed.

I’m making my first ever larger Elixir project that will mirror one of our company’s apps API in my free time to convince them we should use Elixir instead of C#.

1 Like

Could use

https://hexdocs.pm/morphix/Morphix.html#atomorphify/2

Or

https://hexdocs.pm/morphix/Morphix.html#stringmorphiform!/2

Seems like you put the logic in the controller, after getting the json from the view - that’s why you need to “post-process” the map.

Think about which module should own 1) get the context (get the creator) and 2) own the logic for the context (add or remove based on the creator).

In general, I’d use controller to get and pass context, and then let other modules own logic. For example in that case I’d pass that context information to the view (or any “serializer” module), and handle that logic there.

But I see in some cases it’s easier to create everything and then drop it later. That’s fine and you can do what you’re doing. There is no “creating an object” - so just transform as needed.

I’m not touching data that comes from the view. What I meant by object was JSON object. My previous sentence talked about JSON so I tought it was clear. But I should have changed object to JSON object to make it more clear.

For clarification: what you actually get and process in the Elixir app is a map representing a JSON object, not JSON object. Later, phoenix encodes the map to JSON. So you handles a map, not a json object. This actually matters, as you seems think the map only for json output, not internal form of data for data transformation.

Just to make it clear - I just wanted to let you 1) think why you probably don’t need “remove later” and 2) get familiar with how data transformation, which replaces object manipulation in other OOP.

Please note that nobody actually “answered to” your original question anyway, since that doesn’t applicable in Elixir :slight_smile:


For actual code: I prefer to have more domain specific func like this (no phoenix, assume a post and its creator)

output = PostView.build_for(post, user)

def build_for(%Post{creator_id: user_id}, %User{id: user_id} = user), do: build(post) |> put_creator_attrs(user)

def build_for(post, _user), do: build(post)

defp build(post) do
  %{
    id: post.id
    # more common field
  }
end

# add post to args if needed
defp put_creator_attrs(map, user)

I prefer to use explicit func than better “utility function” since 1) it captures domain logic clearly at func level (not buried in some where in the conditional value passed to a utility func) and 2) it’s usually easy to write them with pattern matching since all are essentially “data”

BTW as Elixir is functional language… note that you can easily refactor (move) them - you can put them in controller for now, but you can move it to view, or even context domain holding access control of API response fields later. It’s pretty different experience from OOP (in which any change on public method would trigger cascading changes)

1 Like

What I meant is that I have JSON API and calling that API it sometimes doesn’t return all fields in JSON objects.

Thanks for giving me ideas. I didn’t know you could pattern match user_id that way!

I’ll probably refactor my code to use piping and split it multiple functions that have clear names what they do to make my code more readable.