Using Ecto for accepting key/value pairs in a user form

I have a form that is backed by a table. As part of the form I have a section where I accept a key, value, and data type from the user. The idea is to build up a JSON object but allow the user to add them one by one. On the backend I want to do some validation on these key/value pairs such as ensuring the input can be cast to the data type selected, and making sure they’re not blank. I have all that working.

The problem I’m running into now is that I’d like to do something like this:

defmodule Tree do
  use Ecto.Schema
  schema "tree" do
    embeds_one :leaf, Leaf

defmodule Leaf do
  use Ecto.Schema

  @primary_key false
  embedded_schema do
    field :leafs, :map, default: %{}
  def changeset(schema, %{"leafs" => leafs}) do
    |> cast(%{"leafs" => leafs}, [:leafs])
    |> validate_and_cast_custom_facts()
  @blacklist = ["apple", "orange"]
defp validate_and_cast_custom_facts(changeset) do 
    validate_change changeset, :leafs, fn :leafs, {k, v} ->
      if k in @blacklist do
        ["#{k}": "#{k} is a blacklisted key."]

The problem is that it involves converting use input (the key from the form) into an atom in order to add it to the changeset as an error. I can’t think of a better way to do this as I’m using Changesets throughout in API responses and forms for rendering errors. Is there another way I could do this?

The correct key for the error would be the field name, which is :leafs. So, I would do:

[leafs: "#{k} is a blacklisted key."]

Thanks to some help from @schrockwell on Slack, I ended up shoving some extra information into the meta data for an error like this:

add_error(changeset, :leafs, "#{k} is a blacklisted key", bad_key: k)

The problem was I needed to be able to tell which key was bad if I had something like this.

%Leaf{leafs: %{"good_key" => :a, "blacklisted_key" => :b}