【问题标题】:Poison: How to decode to structs when the json value could be an object, or a list of objects毒药:当 json 值可能是对象或对象列表时如何解码为结构
【发布时间】:2021-07-09 17:37:34
【问题描述】:

我正在尝试使用 Poison 解码以下 json 字符串

iex(1)> fetch(1)
{:ok,
 "{\"name\":\"Anabela\",\"surname\":\"Neagu\",\"gender\":\"female\",\"region\":\"Romania\"}"}
iex(2)> fetch(2)
{:ok,
 "[{\"name\":\"Juana\",\"surname\":\"Su├írez\",\"gender\":\"female\",\"region\":\"Argentina\"},{\"name\":\"ðíðÁÐÇð│ðÁð╣\",\"surname\":\"ðƒð╗ð¥Ðéð¢ð©ð║ð¥ð▓\",\"gender\":\"male\",\"region\":\"Russia\"}]"}

执行fetch(1) |> decode_response 将不起作用,尽管它是针对严格限制为 1 的参数。

我确实有以下错误

10:07:52.663 [error] GenServer Newsequence.Server terminating
** (BadMapError) expected a map, got: {"gender", "female"}
    (stdlib) :maps.find("gender", {"gender", "female"})
    (elixir) lib/map.ex:145: Map.get/3
    lib/poison/decoder.ex:49: anonymous fn/3 in Poison.Decode.transform_struct/4
    (stdlib) lists.erl:1262: :lists.foldl/3
    lib/poison/decoder.ex:48: Poison.Decode.transform_struct/4
    lib/poison/decoder.ex:24: anonymous fn/5 in Poison.Decode.transform/4
    (stdlib) lists.erl:1262: :lists.foldl/3
    lib/poison/decoder.ex:24: Poison.Decode.transform/4 Last message: {:getpeople, 1} State: {[], #PID<0.144.0>}

我的功能如下:

def decode_response({:ok, body}) do
    Poison.decode!(body, as: [%Personne{}])
end

然后我认为参数等于1,函数应该是

   def decode_response({:ok, body}) do
        Poison.decode!(body, as: %Personne{})
    end

我终于认为在我的 fetch 函数给出的字符串中计算元组并使用保护来选择要使用的 decode_response 是个好主意,但我不知道如何。

有人请指点我正确的方向吗?

问候,

皮埃尔

【问题讨论】:

    标签: elixir elixir-poison


    【解决方案1】:

    您可以通过首先使用Poison.decode!/1 解码为本机地图/列表,然后计算as: 的相关值,最后使用本机数据结构和要解码的结构调用Poison.Decoder.decode/2 来做到这一点:

    def decode_response({:ok, body}) do
      parsed = Poison.decode!(body)
      as =
        cond do
          is_map(parsed) -> %Personne{}
          is_list(parsed) -> [%Personne{}]
        end
      Poison.Decoder.decode(parsed, as: as)
    end
    

    演示:

    defmodule Personne do
      defstruct [:name, :surname, :gender, :region]
    end
    
    defmodule Main do
      def main do
        f1 = {:ok, "{\"name\":\"Anabela\",\"surname\":\"Neagu\",\"gender\":\"female\",\"region\":\"Romania\"}"}
        f2 = {:ok, "[{\"name\":\"Juana\",\"surname\":\"Su├írez\",\"gender\":\"female\",\"region\":\"Argentina\"},{\"name\":\"ðíðÁÐÇð│ðÁð╣\",\"surname\":\"ðƒð╗ð¥Ðéð¢ð©ð║ð¥ð▓\",\"gender\":\"male\",\"region\":\"Russia\"}]"}
    
        f1 |> decode_response |> IO.inspect
        f2 |> decode_response |> IO.inspect
      end
    
      def decode_response({:ok, body}) do
        parsed = Poison.decode!(body)
        as =
          cond do
            is_map(parsed) -> %Personne{}
            is_list(parsed) -> [%Personne{}]
          end
        Poison.Decoder.decode(parsed, as: as)
      end
    end
    
    Main.main
    

    输出:

    %{"gender" => "female", "name" => "Anabela", "region" => "Romania",
      "surname" => "Neagu"}
    [%{"gender" => "female", "name" => "Juana", "region" => "Argentina",
       "surname" => "Suárez"},
     %{"gender" => "male", "name" => "ðíðÁÐÇð│ðÁð╣",
       "region" => "Russia",
       "surname" => "ðƒð╗ð¥Ðéð¢ð©ð║ð¥ð▓"}]
    

    【讨论】:

      【解决方案2】:

      这有点幼稚,但它会省去对 JSON 的第二次解析:

      if (body |> String.trim_leading() |> String.starts_with?("[")) do
        Poison.decode!(body, as: [%Personne{}])                    
      else                                                       
        Poison.decode!(body, as: %Personne{})                      
      end
      

      注意,如果您使用的是 Elixir 1.2 或更低版本,那么您需要使用 String.lstrip/1 而不是 String.trim_leading/1

      另一种方法是将其解码为映射,使用String.to_existing_atom/1 转换键,然后使用Kernel.struct/2 转换为结构。

        def decode_response({:ok, body}) do
          body |> Poison.decode!() |> string_map_struct(Personne)
        end
      
        defp string_map_struct(entry, module) do
          params =
            entry
            |> Enum.map(fn {key, val} ->
              key = to_existing_atom(key)
              {key, val}
            end)
            |> Enum.into(%{})
          struct(module, params)
        end
      
        defp to_existing_atom(key) do
          try do
            String.to_existing_atom(key)
          rescue
            ArgumentError -> key
          end
        end
      

      【讨论】:

      • 一直在寻找更简单的东西(比如下面的 Dogbert 解决方案),但我会仔细看看你的。看起来真的很有趣。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-21
      • 2011-04-24
      • 1970-01-01
      • 2019-11-26
      • 2019-12-09
      相关资源
      最近更新 更多