wfgilman

wfgilman

Pattern matching encoded JSON

I have a nested JSON HTTP response I want to map to my structs. All responses are formatted: {"_embedded": {"entity": [entity-specific json]}}. I want to pattern match on the entity to map the entity data to the correct struct.

Example decoded HTTP response:

%{"_embedded" => 
  %{"customers" => 
    %{"id" => 1, "name" => "Joe Shmo", "addresses" => [
        %{"city" => "San Francisco", "state" => "CA"},
        %{"city" => "Lake Tahoe", "state" => "NV"}
      ]
     }
   }
}

My structs:

%Customer{:id, :name, :addresses}
%Address{:city, :state}

Right now I’m doing something like this to pattern match on the decoded response:

def map_response(%{"_embedded" => %{"customers" => customers}}) do
  customers |> Poison.encode! |> Poison.decode!(as: %Customer{addresses: [%Address{}]})
end

This works great, but I don’t like that I’m having to re-encode the HTTP response into order to utilize the decode!/2 function. I don’t know how to pattern match on the first part of the HTTP response without first decoding the response.

Is there a way around this?

Marked As Solved

wfgilman

wfgilman

I figured it out using Poison.

You can put complex maps into structs by using the Poison.Decode.decode/2 function. You can create the map from an encoded JSON by using Poison.Parser.parse/1. Poison.decode/2 combines these steps.

If you’re making HTTP calls using HTTPoison, you can put in this override function which will parse the response body into a map:

  def process_response_body(body) do
    Poison.Parser.parse!(body)
  end

Then pattern match on the response body and map to a struct as follows:

def map_response_body(%{"_embedded" => %{"customers" => customers}}) do
  Poison.Decode.decode(customers, as: %Customer{addresses: [%Address{}]})
end

No re-encoding and decoding needed!

Also Liked

jwarlander

jwarlander

If the situation really IS anywhere near that simple, I wouldn’t reach for a dependency…

defmodule MyApp do
  defmodule Customer do
    defstruct [id: nil, name: nil, addresses: []]
  end

  defmodule Address do
    defstruct [city: nil, state: nil]
  end

  def map_response(%{"_embedded" => %{"customers" => customers}}) do
    customers |> Enum.map(&make_customer/1)
  end
  def map_response(%{"_embedded" => %{"suppliers" => _suppliers}}), do: {:error, :not_implemented}
  # ..and so on, of course

  def make_customer(%{"id" => id, "name" => name, "addresses" => addresses}) do
    %Customer{id: id, name: name, addresses: Enum.map(addresses, &make_address/1)}
  end

  def make_address(%{"city" => city, "state" => state}) do
    %Address{city: city, state: state}
  end
end

response = 
%{"_embedded" => 
  %{"customers" => [
    %{"id" => 1, "name" => "Joe Shmo", "addresses" => [
        %{"city" => "San Francisco", "state" => "CA"},
        %{"city" => "Lake Tahoe", "state" => "NV"}
      ]
     }
    ]
   }
}

IO.inspect MyApp.map_response(response)
# [%MyApp.Customer{addresses: [%MyApp.Address{city: "San Francisco", state: "CA"},
#    %MyApp.Address{city: "Lake Tahoe", state: "NV"}], id: 1, name: "Joe Shmo"}]

However, if you need something generic for quite a few more use cases, and see it becoming hard to maintain, then yeah… do check out that library :slight_smile:

bbense

bbense

customers -> %Customer requires 3 things.

  1. Remove any keys that are not in the Customer struct.

    good_keys =  Map.keys(Map.from_struct(Customer)) |> Enum.map(&Atom.to_string/1)
    {step1, _bad_keys } = customers |> Map.get("customers") |> Map.split(good_keys)
    
  2. Convert all the string keys to atoms.

     step1 |> Enum.map( fn {k, v} -> {String.to_existing_atom(k), v} end ) |> Enum.into(%{})
    
  3. Add a :__struct__ key to the Map with Customer as the Module.

     new_customer = Map.put(step1, :__struct__, Customer)
    

Then you need to pull out the address list and apply the same kind of transformation for each
map in the list.

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

We're in Beta

About us Mission Statement