elixir

how to remove a key from structs/maps / nested structs/maps in Elixir


Hello I have this struct

  request = %Model{
    id: 4_269_615,
    site_id: 3,
    user_id: 183_317,
    program_id: 174,
    type: %ProgramType{
      value: :CASHBACK_PROGRAM,
      __unknown_fields__: []
    },
    type_data: %ProgressProgramUser{
      id: 1_336_676,
      valid_stake: nil,
      level: nil,
      theo_ggr: nil,
      __unknown_fields__: []
    },
    start_at: ~U[2024-09-05 13:27:20.391727Z],
    end_at: nil,
    inserted_at: ~U[2024-09-05 13:27:24.152602Z],
    program_name: "test cb program 20 min GOLD NFT",
    __unknown_fields__: []
  }

i am trying to remove the __unknown_fields__: [] from struct as well as nested structs.

the code I am using is working

  def drop_unknown_fields(struct) when is_struct(struct) do
    struct
    |> Map.from_struct()
    |> drop_unknown_fields()
  end

  def drop_unknown_fields(%{} = map) when is_map(map) do
    map
    |> Enum.reduce(%{}, fn {key, value}, acc ->
      updated_value =
        case date?(value) do
          true -> value
          false -> drop_unknown_fields(value)
        end

      Map.put(acc, key, updated_value)
    end)
    |> Map.delete(:__unknown_fields__)
  end

  def drop_unknown_fields(value), do: value

  def date?(%DateTime{}), do: true
  def date?(%Date{}), do: true
  def date?(%NaiveDateTime{}), do: true
  def date?(_), do: false

but I think its not right approach to do it. The reason of checking date is: When I try to do nested structs, datatime is a struct as well and it get resolved into a map, which Is not required. So Can you please guide me some optimal way to clean this struct and remove :__unknown_fields__ thank you

Update: I refactored it with code below, but still looking for suggestions.

  def drop_unknown_fields(struct) when is_struct(struct) do
    struct
    |> Map.from_struct()
    |> only_consider_unknown_fields(struct)
    |> drop_unknown_fields()
  end

  def drop_unknown_fields(%{} = map) when is_map(map) do
    map
    |> Enum.reduce(%{}, fn {key, value}, acc ->
      updated_value =  drop_unknown_fields(value)

      Map.put(acc, key, updated_value)
    end)
    |> Map.delete(:__unknown_fields__)
  end

  def drop_unknown_fields({:consider, map}), do: drop_unknown_fields(map)

  def drop_unknown_fields({:ignore, struct}), do: struct

  def drop_unknown_fields(value), do: value

  defp only_consider_unknown_fields(map, struct) do
    case Map.has_key?(map, :__unknown_fields__) do
      true -> {:consider, map}

      false -> {:ignore, struct}
    end
  end

Solution

  • Here's an implementation that I think works like the way you want.

    1. Structs which have that specific key are converted to maps, that specific key is removed, and their values are recursively processed.

    2. Other structs are left as-is.

    3. Non-struct maps then have their values processed and converted back into a map.

    4. Any other value is left as-is.

      def drop_unknown_fields(%_{__unknown_fields__: _} = struct) do
        struct |> Map.from_struct() |> Map.delete(:__unknown_fields__) |> drop_unknown_fields()
      end
    
      def drop_unknown_fields(%_{} = struct), do: struct
    
      def drop_unknown_fields(%{} = map) do
        for {k, v} <- map, into: %{}, do: {k, drop_unknown_fields(v)}
      end
    
      def drop_unknown_fields(value), do: value
    

    Output:

    %{
      id: 4269615,
      type: %{value: :CASHBACK_PROGRAM},
      site_id: 3,
      user_id: 183317,
      program_id: 174,
      type_data: %{id: 1336676, level: nil, valid_stake: nil, theo_ggr: nil},
      start_at: ~U[2024-09-05 13:27:20.391727Z],
      end_at: nil,
      inserted_at: ~U[2024-09-05 13:27:24.152602Z],
      program_name: "test cb program 20 min GOLD NFT"
    }
    

    Full code:

    defmodule Model do
      defstruct [
        :id,
        :site_id,
        :user_id,
        :program_id,
        :type,
        :type_data,
        :start_at,
        :end_at,
        :inserted_at,
        :program_name,
        :__unknown_fields__
      ]
    end
    
    defmodule ProgramType do
      defstruct [
        :value,
        :__unknown_fields__
      ]
    end
    
    defmodule ProgressProgramUser do
      defstruct [
        :id,
        :valid_stake,
        :level,
        :theo_ggr,
        :__unknown_fields__
      ]
    end
    
    defmodule Main do
      def main do
        request = %Model{
          id: 4_269_615,
          site_id: 3,
          user_id: 183_317,
          program_id: 174,
          type: %ProgramType{
            value: :CASHBACK_PROGRAM,
            __unknown_fields__: []
          },
          type_data: %ProgressProgramUser{
            id: 1_336_676,
            valid_stake: nil,
            level: nil,
            theo_ggr: nil,
            __unknown_fields__: []
          },
          start_at: ~U[2024-09-05 13:27:20.391727Z],
          end_at: nil,
          inserted_at: ~U[2024-09-05 13:27:24.152602Z],
          program_name: "test cb program 20 min GOLD NFT",
          __unknown_fields__: []
        }
    
        request |> drop_unknown_fields() |> IO.inspect()
      end
    
      def drop_unknown_fields(%_{__unknown_fields__: _} = struct) do
        struct |> Map.from_struct() |> Map.delete(:__unknown_fields__) |> drop_unknown_fields()
      end
    
      def drop_unknown_fields(%_{} = struct), do: struct
    
      def drop_unknown_fields(%{} = map) do
        for {k, v} <- map, into: %{}, do: {k, drop_unknown_fields(v)}
      end
    
      def drop_unknown_fields(value), do: value
    end
    
    Main.main()