elixirelixir-poison

Poison: How to decode to structs when the json value could be an object, or a list of objects


I'm trying to decode a the following json strings using Poison

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\"}]"}

Doing a fetch(1) |> decode_response won't work although it is for param strictly sup to 1.

I do have the following error

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>}

My function is as below :

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

I then think for param equal to 1, function should be

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

I finally thought it was a good idea to count tuples within the string given by my fetch function and use a guard to choose which decode_response to use, but i don't know how.

May someone please point me on the right direction ?

Regards,

pierre


Solution

  • You can do this by first decoding to native map/list using Poison.decode!/1, then calculating the relevant value for as:, and then finally calling Poison.Decoder.decode/2 with the native data structure and the structure to decode into:

    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
    

    Demo:

    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
    

    Output:

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