如何在Elixir中移除结构体/映射及嵌套结构的__unknown_fields__
移除Elixir结构体中
__unknown_fields__字段的优化方案 问题场景
我有如下Elixir结构体,需要从它和嵌套的子结构体中移除所有__unknown_fields__: []字段:
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__: [] }
初始实现及痛点
我写了一段可运行的代码,但方案不够理想:
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
添加日期类型判断是因为递归处理时,DateTime这类日期结构体也会被转成普通映射,不符合预期。
重构后的代码
我已经重构了代码,但仍想寻求更优方案:
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
更简洁的优化方案
可以通过精准匹配需要处理的结构体,避免不必要的类型转换,同时保留DateTime等特殊结构体的原始类型。以下是两种更高效的实现:
方案一:用put_in保留结构体类型
def drop_unknown_fields(%{__unknown_fields__: _} = struct) when is_struct(struct) do struct |> Map.from_struct() |> Map.delete(:__unknown_fields__) |> Enum.reduce(struct, fn {key, value}, acc -> put_in(acc, [key], drop_unknown_fields(value)) end) end def drop_unknown_fields(%{} = map) do map |> Enum.reduce(%{}, fn {key, value}, acc -> Map.put(acc, key, drop_unknown_fields(value)) end) end def drop_unknown_fields(value), do: value
逻辑说明
- 第一个子句仅匹配包含
__unknown_fields__的结构体,避免误处理DateTime等其他结构体 - 转成映射删除目标字段后,用
put_in把处理后的嵌套值放回原结构体,保留原始结构体类型 - 递归处理所有嵌套的映射和目标结构体,非目标类型直接返回
方案二:用struct/2重建结构体
def drop_unknown_fields(%{__unknown_fields__: _} = struct) when is_struct(struct) do struct |> Map.from_struct() |> Map.delete(:__unknown_fields__) |> Enum.map(fn {key, value} -> {key, drop_unknown_fields(value)} end) |> then(&struct(struct, &1)) end def drop_unknown_fields(%{} = map) do map |> Enum.reduce(%{}, fn {key, value}, acc -> Map.put(acc, key, drop_unknown_fields(value)) end) end def drop_unknown_fields(value), do: value
逻辑说明
- 核心思路和方案一一致,只是用
struct/2直接通过处理后的键值对重建结构体,代码更直观 - 同样只处理包含目标字段的结构体,完全避免了对DateTime等类型的误转换
内容的提问来源于stack exchange,提问作者Junaid Farooq
相关产品推荐
相关产品推荐

