3
votes

I am trying to write a CLI client in Elixir for an API so that I can login to the API system, fetch the data I need for my calculation and then logout. I have defined a Packet.Login struct that supposed to be my internal data structure that I end up with after parsing the JSON I receive.

I am using Poison to parse the JSON. The problem is that it seems like, because of the API returning capitalised properties, I can't match them when printing or parsing, as Poison will return a map with these capitalized keys. The problem is that it seems impossible for me to use the alias like this. If I try to use another syntax,

packet[:Token]

it still does not work and instead gives me an error. But this time about Packet.Login not implementing the Access behaviour. I can understand that part, but not the first issue. And I'm trying to keep the code stupid simple.

defmodule Packet.Login do
  defstruct [:Data, :Token]
end

defimpl String.Chars, for: Packet.Login do
  def to_string(packet) do
    "Packet:\n---Token:\t\t#{packet.Token}\n---Data:\t#{packet.Data}"
  end
end

loginPacket = Poison.decode!(json, as: %Packet.Login{})
IO.puts "#{loginPacket}"

When trying to compile the above I get this:

** (CompileError) lib/packet.ex:31: invalid alias: "packet.Token". If you wanted to define an alias, an alias must expand to an atom at compile time but it did not, you may use Module.concat/2 to build it at runtime. If instead you wanted to invoke a function or access a field, wrap the function or field name in double quotes
(elixir) expanding macro: Kernel.to_string/1

Is there a way for me to fix this somehow? I have thought of parsing the map and de-capitalizing all fields first, but I would rather not.

Why can't I have capitalized keys for a struct? It seems like I can though, as long as I don't try to use them.

1
Try packet."Token" and packet."Data".Dogbert
Wow. Yeah, that worked. So, is the key a string then? I want an explanation as to why that's necessary.Simon
@Dogbert Make and answer with an explanation of why so I can accept the answer (Given that the explanation is explanatory enough) :)Simon

1 Answers

6
votes

In order to access a field of a map which is an atom starting with an uppercase letter, you need to either put the key in quotes, e.g. foo."Bar" or use the bracket syntax, e.g. foo[:Bar]. foo.Bar in Elixir is parsed as an alias. With structs, you cannot use the bracket syntax, so the easiest way is to use quotes around the field name. In your code, you'll therefore need to change:

"Packet:\n---Token:\t\t#{packet.Token}\n---Data:\t#{packet.Data}"

to:

"Packet:\n---Token:\t\t#{packet."Token"}\n---Data:\t#{packet."Data"}"

I could not find this documented clearly anywhere but Elixir's source mentions this in some places and also uses this syntax to access some functions in :erlang which have names that are not valid identifiers in Elixir, e.g. :erlang."=<".


Fun fact: you can define functions in Elixir that can only be called with this quote syntax as well:

iex(1)> defmodule Foo do
...(1)>   def unquote(:"!@#")(), do: :ok
...(1)> end
iex(2)> Foo."!@#"()
:ok