Encoding map to JSON using Poison for use with Slack
Asked Answered
F

1

11

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.

Fertile answered 16/7, 2016 at 19:15 Comment(6)
Can you post the source of the relevant controller function? You're probably calling json(conn, Poison.encode!(data)) instead of json(conn, data).Breedlove
Added some code to give a better idea of what I'm doing.Fertile
"This is what Poison gives me: ..." where in the code did you get this value from?Breedlove
That comes from any time I encode with Poison. That specifically from process_request_body. In the Poison documentation, that's what the output looks like in his example under the usage section: github.com/devinus/poisonFertile
What kind of endpoint are you trying to reach, an incoming webhook URL or are you using the chat.postMessage API endpoint? The former accepts application/json (but you should make sure a Content-type: application/json HTTP header is being set) while the latter does not support POSTing JSON-based messages this way.Danikadanila
Try JSON.parse on returned object from Poison encode map to JSON and then send it to slack API ?Fda
B
4

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.

Balm answered 27/9, 2016 at 7:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.