-
Couldn't load subscription status.
- Fork 3k
Use Jason instead of Poison for json encoding #2734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
lib/phoenix/controller.ex
Outdated
| defp get_json_encoder do | ||
| Application.get_env(:phoenix, :format_encoders) | ||
| |> Keyword.get(:json, Poison) | ||
| |> Keyword.get(:json, Jason) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is backwards incompatible, I am afraid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was going to ask how we should handle it. We can make it an explicit config, but then we'd still need to ship with the poison* dep, which is not ideal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot simply change to Jason as that is backwards incompatible, since Jason is not fully compatible with Poison.
In my opinion, we should expose this function behind Phoenix.json_encoder (it can be @doc false) and use it throughout the codebase but we should still default to poison. In new apps, we generate:
config :phoenix, :format_encoders, json: Jason
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another reason to this clean up is that Ecto can only migrate to Jason by default in 3.0. So I am afraid that making this easily achievable is the best way to go for now.
lib/phoenix/template.ex
Outdated
| 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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also backwards incompatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, this is still backwards incompatible. :) It should be Phoenix.json.
| %Message{topic: msg.topic, event: msg.event, payload: msg.payload} | ||
| |> to_list() | ||
| |> Poison.encode!() | ||
| |> Jason.encode!() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should start using encode_to_iodata here.
| """ | ||
| def encode!(msg) do | ||
| {:socket_push, :text, msg |> to_list() |> Poison.encode!()} | ||
| {:socket_push, :text, msg |> to_list() |> Jason.encode!()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Encode to iodata.
guides/docs/controllers.md
Outdated
| 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 parse into JSON, such as a map. (Jason is one of Phoenix's dependencies.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be "encode" instead of "parse"?
| %Message{topic: msg.topic, event: msg.event, payload: msg.payload} | ||
| |> to_list() | ||
| |> Poison.encode!() | ||
| |> Jason.encode!() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might use encode_to_iodata! here as well, like in the websocket serializer.
| @v1_msg_json ["{\"", [[], "event"], "\":", [34, [], "e", 34], ",\"", [[], "payload"], "\":", [34, [], "m", 34], ",\"", [[], "ref"], "\":", "null", ",\"", [[], "topic"], "\":", [34, [], "t", 34], 125] | ||
| @v1_reply_json ["{\"", [[], "event"], "\":", [34, [], "phx_reply", 34], ",\"", [[], "payload"], "\":", ["{\"", [[], "response"], "\":", "null", ",\"", [[], "status"], "\":", "null", 125], ",\"", [[], "ref"], "\":", [34, [], "null", 34], ",\"", [[], "topic"], "\":", [34, [], "t", 34], 125] | ||
| @v1_fastlane_json ["{\"", [[], "event"], "\":", [34, [], "e", 34], ",\"", [[], "payload"], "\":", [34, [], "m", 34], ",\"", [[], "ref"], "\":", "null", ",\"", [[], "topic"], "\":", [34, [], "t", 34], 125] | ||
| @v2_msg_json [91, "null", 44, "null", 44, [34, [], "t", 34], 44, [34, [], "e", 34], 44, [34, [], "m", 34], 93] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to flatten the iodata before assertion in the test? It should be less brittle in case of changes in the library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ping!
|
Recap:
|
|
@josevalim updated as discussed. Can you take another look? Thanks! |
lib/phoenix.ex
Outdated
|
|
||
| children = [ | ||
| # Ensure format encoders are loaded before starting | ||
| worker(Task, [fn -> ensure_format_encoders() end], restart: :transient), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not thrilled about this approach but it's the only way to get reasonable error messages out of start. The downside is the default max_restarts shows the runtime error three times. Better ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would making it a temporary worker solve this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting a worker sounds like too much.
@chrismccord if you are not happy with the error message when raising directly inside application, you have two options:
- Log and return
{:error, :missing_format_encoder} - Log and let the app boot anyway
| metadata: [:request_id] | ||
|
|
||
| # Use Jason for JSON parsing in Phoenix | ||
| config :phoenix, :format_encoders, json: Jason |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need to generate the configuration for Ecto:
config :ecto, :json_library, Jason
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this deprecated? Should we generate config :#{db_adapter}, :json_library, Jason instead?
|
@josevalim can you take a final pass? I generated the "deprecated" for 3.0 Ecto config, but we can update to the ecto adapter config once 3.0 ships. |
| config :phoenix, :format_encoders, json: Jason | ||
| <%= if ecto do %> | ||
| config :ecto, :json_library, Jason | ||
| <% end %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OCD check: how is this going to be rendered in practice? Maybe it would be better to be defined as:
config :phoenix, :format_encoders, json: Jason<%= if ecto do %>
config :ecto, :json_library, Jason
<% end %>
?
| use Mix.Config | ||
|
|
||
| <%= if ecto do %>config :<%= app_name %>, ecto_repos: [<%= app_module %>.Repo]<% end %> | ||
| <%= if ecto do %>config :ecto, :json_library, Jason<% end %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably merge the two conditionals into one.
test/test_helper.exs
Outdated
| Application.ensure_all_started(:cowboy) | ||
|
|
||
| # Clear format encoder cache from startup and inject Jason | ||
| Application.delete_env(:phoenix, :compiled_format_encoders) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also cache Phoenix.json? Also, should we make it public?
Alternatively we can read Phoenix.json at compile time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't read Phoenix.json at compile time because the application needs to be started to fetch application env, but isn't started yet ¯_(ツ)_/¯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we don't need to start the application to fetch the environment, only to fetch the defaults in mix.exs. But if the default is in the module, like in this case here, it should be dine.
Any configuration from the user application will also be available.
In any case, we can separate this discussion in two:
- Shall we make this public?
- Shall we cache it? At runtime or compile time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recap:
Introduce public Phoenix.json_library/0 with config :phoenix, :json_library configuration. No need to cache as runtime lookup is simple ets call.
|
@chrismccord I have added some comments. Regarding Ecto 3.0, we have no estimate for its release, so I wouldn't worry about it. Should we make |
Closes #2693