Rasanjana N
Rasanjana N

Reputation: 1400

Map JSON values in Elixir

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.

Upvotes: 4

Views: 5152

Answers (3)

Mark Wilbur
Mark Wilbur

Reputation: 2925

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)

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

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.

Upvotes: 5

Dogbert
Dogbert

Reputation: 222428

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

Upvotes: 2

Related Questions