4

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.

RN92
  • 1,380
  • 1
  • 13
  • 32

3 Answers3

5

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.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
2

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"}]
Dogbert
  • 212,659
  • 41
  • 396
  • 397
1

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)
Mark Wilbur
  • 2,809
  • 2
  • 23
  • 22
  • Word of warning: atoms don't get garbage collected, so if you parse a lot of JSON with different keys, this could become a problem. https://github.com/devinus/poison#parser – mroach Aug 03 '18 at 19:55
  • There is an atom limit and they're not garbage collected (yet), but in this case, the JSON has a consistent shape. So the only atoms created would be :size, :color, :price and :is_available. I've never seen an app get anywhere near the limit FWIW. – Mark Wilbur Aug 05 '18 at 03:26