Map JSON values in Elixir

Viewed 4360

I have parsed the following JSON using Posion.decode!

json = %{"color-Black|size:10" => 
    %{"attributes" => %{"color" => "Black","size" => "11"},    
      "isAvailable" => true,      
    "pricing" => %{"standard" => "$415.00", "sale" => 415}}, 
 "color|size-EU:9.5" => 
    %{"attributes" => %{"color" => "Black","size" => "11"},    
      "isAvailable" => true,      
    "pricing" => %{"standard" => "$415.00", "sale" => 415}}}

I want to map this and I'm unable to get JSON elements as the text in the node element changes. So far I've tried.

Enum.map(json , fn(item) ->
%{
  color: item.attributes["color"],                 
  size: item.attributes["size"],
  price: item.pricing["standard"] * 100,
  isAvailable: item.isAvailable
 }
end)

But this code gives some error related to accessing.

3 Answers

While mapping the map, the iterated key-value pairs come to the mapper as tuples {key, value}:

Enum.map(json, fn {_, %{"attributes" => attributes,
                        "isAvailable" => isAvailable,
                        "pricing" => pricing}} ->
  %{
    color: attributes["color"],
    size: attributes["size"],
    price: pricing["standard"],
    isAvailable: isAvailable
   }
end)

#⇒ [
#    %{color: "Black", isAvailable: true, price: "$415.00", size: "11"},
#    %{color: "Black", isAvailable: true, price: "$415.00", size: "11"}
# ]

Here we use an inplace pattern matching for values in mapper to simplify the code of matcher itself and make it less error-prone in a case of bad input.

Three things:

  1. You have a map so Enum.map will give you a tuple of key and value. You just want the value here, so do:

    fn {_, item} ->
    
  2. The keys in your map are strings. The dot syntax only works for atom keys. You need to do:

    item["attributes"]
    

    instead of

    item.attributes
    

    and similar for other keys.

  3. The price you have is a string. You'll need to convert it to a Float before you can multiply it. You can do it like this using String.trim_leading and String.to_float:

    iex(1)> "$123.45" |> String.trim_leading("$") |> String.to_float
    123.45
    

Enum.map(json, fn {_, item} ->
  %{
    color: item["attributes"]["color"],
    size: item["attributes"]["size"],
    price: item["pricing"]["standard"] |> String.trim_leading("$") |> String.to_float |> Kernel.*(100),
    isAvailable: item["isAvailable"]
  }
end)
|> IO.inspect

Output:

[%{color: "Black", isAvailable: true, price: 4.15e4, size: "11"},
 %{color: "Black", isAvailable: true, price: 4.15e4, size: "11"}]

You're getting access errors because you can only use the thing.property syntax if property is an atom and in your json map, the keys are strings.

One thing that will make this much easier is that Poison.decode can take a second argument of keys: :atoms to return a map with atom keys. Working with what you have here, this will solve the problem:

json_atoms = Poison.encode!(json) |> Poison.decode!(keys: :atoms)
convert_price = fn x -> 
  String.trim_leading(x, "$")
  |> String.to_float
  |> &(&1 * 100)
  |> trunc
end

Enum.map(json_atoms, fn {_k,v} -> %{
  color: v.attributes.color,
  size: v.attributes.size,
  price: convert_price.(v.pricing.standard),
  isAvailable: v.isAvailable
} end)
Related