Skip to content

Commit

Permalink
Use Jason instead of Poison for json encoding. Closes phoenixframewor…
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismccord committed Feb 9, 2018
1 parent 975589f commit 02a204a
Show file tree
Hide file tree
Showing 30 changed files with 107 additions and 82 deletions.
3 changes: 1 addition & 2 deletions guides/docs/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ end
```
Assuming we had a route for `get "/our_path/:id"` mapped to this `show` action, going to `/our_path/15` in your browser should display `Showing id 15` as plain text without any HTML.

A step beyond this is rendering pure JSON with the `json/2` function. We need to pass it something that the [Poison library](https://github.com/devinus/poison) can parse into JSON, such as a map. (Poison is one of Phoenix's dependencies.)
A step beyond this is rendering pure JSON with the `json/2` function. We need to pass it something that the [Jason library](https://github.com/michalmuskala/jason) can decode into JSON, such as a map. (Jason is one of Phoenix's dependencies.)

```elixir
def show(conn, %{"id" => id}) do
Expand Down Expand Up @@ -678,4 +678,3 @@ It's also important to note that halting will only stop the plug pipeline from c
. . .
end
```

2 changes: 1 addition & 1 deletion guides/docs/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ Endpoints organize all the plugs common to every request, and apply them before

- [Phoenix.CodeReloader](https://hexdocs.pm/phoenix/Phoenix.CodeReloader.html) - a plug that enables code reloading for all entries in the web directory. It is configured directly in the Phoenix application

- [Plug.Parsers](https://hexdocs.pm/plug/Plug.Parsers.html) - parses the request body when a known parser is available. By default parsers parse urlencoded, multipart and json (with poison). The request body is left untouched when the request content-type cannot be parsed
- [Plug.Parsers](https://hexdocs.pm/plug/Plug.Parsers.html) - parses the request body when a known parser is available. By default parsers parse urlencoded, multipart and json (with `jason`). The request body is left untouched when the request content-type cannot be parsed

- [Plug.MethodOverride](https://hexdocs.pm/plug/Plug.MethodOverride.html) - converts the request method to
PUT, PATCH or DELETE for POST requests with a valid `_method` parameter
Expand Down
2 changes: 1 addition & 1 deletion guides/docs/views.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ When we go back to [http://localhost:4000/such/a/wrong/path](http://localhost:40

## Rendering JSON

The view's job is not only to render HTML templates. Views are about data presentation. Given a bag of data, the view's purpose is to present that in a meaningful way given some format, be it HTML, JSON, CSV, or others. Many web apps today return JSON to remote clients, and Phoenix views are *great* for JSON rendering. Phoenix uses [Poison](https://github.com/devinus/poison) to encode Maps to JSON, so all we need to do in our views is format the data we'd like to respond with as a Map, and Phoenix will do the rest. It is possible to respond with JSON back directly from the controller and skip the View. However, if we think about a controller as having the responsibilities of receiving a request and fetching data to be sent back, data manipulation and formatting don't fall under those responsibilities. A view gives us a module responsible for formatting and manipulating the data. Let's take our `PageController`, and see what it might look like when we respond with some static page maps as JSON, instead of HTML.
The view's job is not only to render HTML templates. Views are about data presentation. Given a bag of data, the view's purpose is to present that in a meaningful way given some format, be it HTML, JSON, CSV, or others. Many web apps today return JSON to remote clients, and Phoenix views are *great* for JSON rendering. Phoenix uses [Jason](https://github.com/michalmuskala/jason) to encode Maps to JSON, so all we need to do in our views is format the data we'd like to respond with as a Map, and Phoenix will do the rest. It is possible to respond with JSON back directly from the controller and skip the View. However, if we think about a controller as having the responsibilities of receiving a request and fetching data to be sent back, data manipulation and formatting don't fall under those responsibilities. A view gives us a module responsible for formatting and manipulating the data. Let's take our `PageController`, and see what it might look like when we respond with some static page maps as JSON, instead of HTML.

```elixir
defmodule HelloWeb.PageController do
Expand Down
3 changes: 3 additions & 0 deletions installer/templates/phx_single/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :format_encoders, json: Jason

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"
1 change: 1 addition & 0 deletions installer/templates/phx_single/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ defmodule <%= app_module %>.Mixfile do
{:phoenix_html, "~> 2.10"},
{:phoenix_live_reload, "~> 1.0", only: :dev},<% end %>
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:cowboy, "~> 1.0"}
]
end<%= if ecto do %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ config :<%= web_app_name %>, <%= endpoint_module %>,
pubsub: [name: <%= web_namespace %>.PubSub,
adapter: Phoenix.PubSub.PG2]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :format_encoders, json: Jason

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"
1 change: 1 addition & 0 deletions installer/templates/phx_umbrella/apps/app_name_web/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ defmodule <%= web_namespace %>.Mixfile do
{:phoenix_live_reload, "~> 1.0", only: :dev},<% end %>
{:gettext, "~> 0.11"},<%= if app_name != web_app_name do %>
{:<%= app_name %>, in_umbrella: true},<% end %>
{:jason, "~> 1.0"},
{:cowboy, "~> 1.0"}
]
end
Expand Down
2 changes: 1 addition & 1 deletion installer/templates/phx_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ defmodule <%= endpoint_module %> do
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Poison
json_decoder: Jason

plug Plug.MethodOverride
plug Plug.Head
Expand Down
6 changes: 5 additions & 1 deletion installer/test/phx_new_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ defmodule Mix.Tasks.Phx.NewTest do

assert_file "phx_blog/config/config.exs", fn file ->
assert file =~ "ecto_repos: [PhxBlog.Repo]"
assert file =~ "config :phoenix, :format_encoders, json: Jason"
refute file =~ "namespace: PhxBlog"
refute file =~ "config :phx_blog, :generators"
end
Expand All @@ -43,7 +44,10 @@ defmodule Mix.Tasks.Phx.NewTest do

assert_file "phx_blog/lib/phx_blog/application.ex", ~r/defmodule PhxBlog.Application do/
assert_file "phx_blog/lib/phx_blog.ex", ~r/defmodule PhxBlog do/
assert_file "phx_blog/mix.exs", ~r/mod: {PhxBlog.Application, \[\]}/
assert_file "phx_blog/mix.exs", fn file ->
assert file =~ "mod: {PhxBlog.Application, []}"
assert file =~ "{:jason, \"~> 1.0\"}"
end
assert_file "phx_blog/lib/phx_blog_web.ex", fn file ->
assert file =~ "defmodule PhxBlogWeb do"
assert file =~ "use Phoenix.View, root: \"lib/phx_blog_web/templates\""
Expand Down
6 changes: 5 additions & 1 deletion installer/test/phx_new_umbrella_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
assert file =~ "ecto_repos: [PhxUmb.Repo]"
assert file =~ ":phx_umb_web, PhxUmbWeb.Endpoint"
assert file =~ "generators: [context_app: :phx_umb]\n"
assert file =~ "config :phoenix, :format_encoders, json: Jason"
end

assert_file web_path(@app, "config/prod.exs"), fn file ->
Expand All @@ -79,7 +80,10 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
assert_file app_path(@app, "test/test_helper.exs")

assert_file web_path(@app, "lib/#{@app}_web/application.ex"), ~r/defmodule PhxUmbWeb.Application do/
assert_file web_path(@app, "mix.exs"), ~r/mod: {PhxUmbWeb.Application, \[\]}/
assert_file web_path(@app, "mix.exs"), fn file ->
assert file =~ "mod: {PhxUmbWeb.Application, []}"
assert file =~ "{:jason, \"~> 1.0\"}"
end
assert_file web_path(@app, "lib/#{@app}_web.ex"), fn file ->
assert file =~ "defmodule PhxUmbWeb do"
assert file =~ "use Phoenix.View, root: \"lib/phx_umb_web/templates\""
Expand Down
7 changes: 7 additions & 0 deletions lib/phoenix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,11 @@ defmodule Phoenix do

Supervisor.start_link(children, strategy: :one_for_one, name: Phoenix.Supervisor)
end

@doc false
def json do
:phoenix
|> Application.fetch_env!(:format_encoders)
|> Keyword.get(:json, Jason)
end
end
10 changes: 2 additions & 8 deletions lib/phoenix/controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,6 @@ defmodule Phoenix.Controller do
conn.private[:phoenix_template]
end

defp get_json_encoder do
Application.get_env(:phoenix, :format_encoders)
|> Keyword.get(:json, Poison)
end

@doc """
Sends JSON response.
Expand All @@ -281,9 +276,8 @@ defmodule Phoenix.Controller do
"""
@spec json(Plug.Conn.t, term) :: Plug.Conn.t
def json(conn, data) do
encoder = get_json_encoder()

send_resp(conn, conn.status || 200, "application/json", encoder.encode_to_iodata!(data))
response = Phoenix.json().encode_to_iodata!(data)
send_resp(conn, conn.status || 200, "application/json", response)
end

@doc """
Expand Down
6 changes: 3 additions & 3 deletions lib/phoenix/digester.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ defmodule Phoenix.Digester do

if File.exists?(manifest_path) do
manifest_path
|> File.read!
|> Poison.decode!
|> File.read!()
|> Phoenix.json().decode!()
|> migrate_manifest(output_path)
else
@empty_manifest
Expand Down Expand Up @@ -105,7 +105,7 @@ defmodule Phoenix.Digester do
end

defp save_manifest(%{"latest" => _, "version" => _, "digests" => _} = manifest, output_path) do
manifest_content = Poison.encode!(manifest)
manifest_content = Phoenix.json().encode!(manifest)
File.write!(Path.join(output_path, "cache_manifest.json"), manifest_content)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/phoenix/endpoint/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ defmodule Phoenix.Endpoint.Supervisor do
if File.exists?(outer) do
manifest =
outer
|> File.read!
|> Poison.decode!
|> File.read!()
|> Phoenix.json().decode!()

manifest["latest"]
else
Expand Down
4 changes: 2 additions & 2 deletions lib/phoenix/template.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ defmodule Phoenix.Template do
config :phoenix, :format_encoders,
html: Phoenix.Template.HTML,
json: Poison
json: Jason
"""

Expand All @@ -104,7 +104,7 @@ defmodule Phoenix.Template do

alias Phoenix.Template

@encoders [html: Phoenix.Template.HTML, json: Poison, js: Phoenix.Template.HTML]
@encoders [html: Phoenix.Template.HTML, json: Jason, js: Phoenix.Template.HTML]
@engines [eex: Phoenix.Template.EExEngine, exs: Phoenix.Template.ExsEngine]
@default_pattern "*"

Expand Down
10 changes: 2 additions & 8 deletions lib/phoenix/test/conn_test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -407,14 +407,8 @@ defmodule Phoenix.ConnTest do
def json_response(conn, status) do
body = response(conn, status)
_ = response_content_type(conn, :json)
case Poison.decode(body) do
{:ok, body} ->
body
{:error, {:invalid, token, _}} ->
raise "could not decode JSON body, invalid token #{inspect token} in body:\n\n#{body}"
{:error, :invalid, _} ->
raise "could not decode JSON body, body is empty"
end

Phoenix.json().decode!(body)
end

@doc """
Expand Down
30 changes: 22 additions & 8 deletions lib/phoenix/transports/long_poll_serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Phoenix.Transports.LongPollSerializer do
Translates a `Phoenix.Socket.Broadcast` into a `Phoenix.Socket.Message`.
"""
def fastlane!(%Broadcast{} = broadcast) do
{:socket_push, :text, to_msg(broadcast)}
{:socket_push, :text, to_map(broadcast)}
end

@doc """
Expand All @@ -18,29 +18,43 @@ defmodule Phoenix.Transports.LongPollSerializer do
Encoding is handled downstream in the LongPoll controller.
"""
def encode!(message) do
{:socket_push, :text, to_msg(message)}
{:socket_push, :text, to_map(message)}
end

defp to_msg(%Reply{} = reply) do
%Message{
defp to_map(%Reply{} = reply) do
%{
topic: reply.topic,
event: "phx_reply",
ref: reply.ref,
join_ref: reply.ref,
payload: %{status: reply.status, response: reply.payload}
}
end
defp to_msg(%Message{} = msg), do: msg
defp to_msg(%Broadcast{} = bcast) do
%Message{topic: bcast.topic, event: bcast.event, payload: bcast.payload}
defp to_map(%Message{} = msg) do
%{
topic: msg.topic,
event: msg.event,
payload: msg.payload,
ref: msg.ref,
join_ref: msg.join_ref
}
end
defp to_map(%Broadcast{} = bcast) do
%{
topic: bcast.topic,
event: bcast.event,
payload: bcast.payload,
ref: nil,
join_ref: nil
}
end

@doc """
Decodes JSON String into `Phoenix.Socket.Message` struct.
"""
def decode!(message, _opts) do
message
|> Poison.decode!()
|> Phoenix.json().decode!()
|> Phoenix.Socket.Message.from_map!()
end
end
6 changes: 3 additions & 3 deletions lib/phoenix/transports/v2/longpoll_serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule Phoenix.Transports.V2.LongPollSerializer do
encoded =
%Message{topic: msg.topic, event: msg.event, payload: msg.payload}
|> to_list()
|> Poison.encode!()
|> Phoenix.json().encode_to_iodata!()

{:socket_push, :text, encoded}
end
Expand All @@ -23,7 +23,7 @@ defmodule Phoenix.Transports.V2.LongPollSerializer do
Encoding is handled downstream in the LongPoll controller.
"""
def encode!(msg) do
{:socket_push, :text, msg |> to_list() |> Poison.encode!()}
{:socket_push, :text, msg |> to_list() |> Phoenix.json().encode_to_iodata!()}
end
defp to_list(%Reply{} = reply) do
[reply.join_ref, reply.ref, reply.topic, "phx_reply",
Expand All @@ -37,7 +37,7 @@ defmodule Phoenix.Transports.V2.LongPollSerializer do
Decodes JSON String into `Phoenix.Socket.Message` struct.
"""
def decode!(raw_message, _opts) do
[join_ref, ref, topic, event, payload | _] = Poison.decode!(raw_message)
[join_ref, ref, topic, event, payload | _] = Phoenix.json().decode!(raw_message)

%Phoenix.Socket.Message{
topic: topic,
Expand Down
8 changes: 4 additions & 4 deletions lib/phoenix/transports/v2/websocket_serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Phoenix.Transports.V2.WebSocketSerializer do
Translates a `Phoenix.Socket.Broadcast` into a `Phoenix.Socket.Message`.
"""
def fastlane!(%Broadcast{} = msg) do
data = Poison.encode_to_iodata!([nil, nil, msg.topic, msg.event, msg.payload])
data = Phoenix.json().encode_to_iodata!([nil, nil, msg.topic, msg.event, msg.payload])
{:socket_push, :text, data}
end

Expand All @@ -19,19 +19,19 @@ defmodule Phoenix.Transports.V2.WebSocketSerializer do
def encode!(%Reply{} = reply) do
data = [reply.join_ref, reply.ref, reply.topic, "phx_reply",
%{status: reply.status, response: reply.payload}]
{:socket_push, :text, Poison.encode_to_iodata!(data)}
{:socket_push, :text, Phoenix.json().encode_to_iodata!(data)}
end

def encode!(%Message{} = msg) do
data = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]
{:socket_push, :text, Poison.encode_to_iodata!(data)}
{:socket_push, :text, Phoenix.json().encode_to_iodata!(data)}
end

@doc """
Decodes JSON String into `Phoenix.Socket.Message` struct.
"""
def decode!(raw_message, _opts) do
[join_ref, ref, topic, event, payload | _] = Poison.decode!(raw_message)
[join_ref, ref, topic, event, payload | _] = Phoenix.json().decode!(raw_message)

%Phoenix.Socket.Message{
topic: topic,
Expand Down
4 changes: 2 additions & 2 deletions lib/phoenix/transports/websocket_serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ defmodule Phoenix.Transports.WebSocketSerializer do
"""
def decode!(message, _opts) do
message
|> Poison.decode!()
|> Phoenix.json().decode!()
|> Phoenix.Socket.Message.from_map!()
end

defp encode_v1_fields_only(%Message{} = msg) do
msg
|> Map.take([:topic, :event, :payload, :ref])
|> Poison.encode_to_iodata!()
|> Phoenix.json().encode_to_iodata!()
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ defmodule Phoenix.Mixfile do
{:cowboy, "~> 1.0 or ~> 2.2.2 or ~> 2.3", optional: true},
{:plug, "~> 1.5.0-rc.0", override: true},
{:phoenix_pubsub, "~> 1.0"},
{:poison, "~> 2.2 or ~> 3.0"},
{:jason, "~> 1.0", optional: true},
{:gettext, "~> 0.8", only: :test},

# Docs dependencies
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []},
"inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
"jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []},
"phoenix_guides": {:git, "https://github.com/phoenixframework/phoenix_guides.git", "cc1899054e31704f1ae92645d4b0534e6aac407e", []},
"phoenix_html": {:hex, :phoenix_html, "2.10.4", "d4f99c32d5dc4918b531fdf163e1fd7cf20acdd7703f16f5d02d4db36de803b7", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
Expand Down
4 changes: 2 additions & 2 deletions priv/templates/phx.gen.json/controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
conn = get conn, Routes.<%= schema.route_helper %>_path(conn, :show, id)
assert json_response(conn, 200)["data"] == %{
"id" => id<%= for {key, val} <- schema.params.create do %>,
"<%= key %>" => <%= Poison.encode!(val) %><% end %>}
"<%= key %>" => <%= Phoenix.json().encode!(val) %><% end %>}
end

test "renders errors when data is invalid", %{conn: conn} do
Expand All @@ -51,7 +51,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
conn = get conn, Routes.<%= schema.route_helper %>_path(conn, :show, id)
assert json_response(conn, 200)["data"] == %{
"id" => id<%= for {key, val} <- schema.params.update do %>,
"<%= key %>" => <%= Poison.encode!(val) %><% end %>}
"<%= key %>" => <%= Phoenix.json().encode!(val) %><% end %>}
end

test "renders errors when data is invalid", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
Expand Down
Loading

0 comments on commit 02a204a

Please sign in to comment.