I'm using Poison to encode a map to JSON that will send it to the Slack API. This is what Poison gives me:
"{\"text\":\"changed readme fad996e98e04fd4a861840d92bdcbbcb1e1ec296\"}"
When I put that into JSON lint it says it is valid JSON, but Slack responds "invalid payload".
If I change the JSON to look like this
{"text":"changed readme fad996e98e04fd4a861840d92bdcbbcb1e1ec296"}
Then it works. Does anyone know where I'm going wrong with this? Do I need to do extra processing on the encoded JSON or is there some header I need to set?
Here is my controller
def create(conn, opts) do
message = Message.create_struct(opts)
response = Slack.Messages.send(message)
case response do
{:ok, data} ->
render conn, json: Poison.encode!(data)
{:error, reason} ->
render conn, json: reason
end
end
Here is part of the library for sending the messages
defmodule Slack.Messages do
def format_simple_message(map) do
text = map.description <> " " <> map.commits
message = %{text: text}
end
def post_to_slack(map) do
Slack.post(:empty, map)
end
def send(map) do
map
|> format_simple_message
|> post_to_slack
end
end
And my HTTPoison processing
defmodule Slack do
use HTTPoison.Base
@endpoint "http://url.com"
def process_url() do
@endpoint
end
def process_response_body(body) do
body
|> Poison.decode! # Turns JSON into map
end
def process_request_body(body) do
body
|> Poison.encode! # Turns map into JSON
end
end
The part that creates the JSON is in the last block.
It seems your request payload is JSON encoded twice:
At first it returns the output string {"text":"..."}
and then that string is encoded again. JSON can not only encode objects, but also strings so the encoding the above again will give an output of "{\"text\":\"...\"}"
.
That is, a string that contains the JSON encoded representation of an object.
However, the Slack API expects an object of the form {"text": "..."}
, not a string "..."
. The latter is still valid JSON, but it is not a valid request as far as the API is concerned. That is why you get the “invalid payload” error back.
Where I’m not entirely sure is where the object is encoded a second time. Your code above looks okay, but maybe there are other processing steps that are not in those code snippets. I would suggest you carefully examine your code for a path where two Poison.encode!
calls are applied to some data.
There is, however, a workaround for this – although I highly recommend you find and fix the root cause, the double JSON encoding. In process_request_body/1
you could match the argument – and if it is already a string, skip the Poison.encode!
. To do so, use the following code:
def process_request_body(body) when is_binary(body), do: body
def process_request_body(body)
body
|> Poison.encode! # Turns map into JSON
end
This will pass strings through verbatim and JSON encode anything else. However, this may be dangerous because a string is still a valid input for JSON encoding, so you have to be careful if you want to send pure JSON encoded strings over the API. Again, I recommend fixing the root cause, but depending on your situation, this workaround might also be viable.