Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ config :logger, :console,
format: "\n$time $metadata[$level] $message\n"

config :phoenix,
json_library: Jason,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably here we would need to have something similar to if Code.ensure_loaded?(JSON), do: JSON, else: Jason if that conditional loading trick makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does and yes, we definitely need it (and also adjust the tests to assert the correct errors in CI)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, for sure. I wasn't certain if the said approach (determining JSON availability by Code.ensure_loaded?) is the correct one. But if it will do, then yes, I'll update the tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can wait for José to chime in. Sometimes you need Code.ensure_compiled, but I think for a built in module ensure_loaded should be fine?

json_library: JSON,
stacktrace_depth: 20,
trim_on_html_eex_engine: false

Expand Down
2 changes: 1 addition & 1 deletion guides/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ end

Now [`/hello/Frank`] in your browser should display `From messenger Frank` 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 [Jason library](`Jason`) can decode into JSON, such as a map. (Jason 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 [JSON library](https://hexdocs.pm/elixir/JSON.html) can decode into JSON, such as a map.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's how it looks like with mouse hover:

Image


```elixir
def show(conn, %{"messenger" => messenger}) do
Expand Down
2 changes: 1 addition & 1 deletion guides/json_and_apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ defmodule HelloWeb.UrlJSON do
end
```

This view is very simple. The `index` function receives all URLs, and converts them into a list of maps. Those maps are placed inside the data key at the root, exactly as we saw when interfacing with our application from `cURL`. In other words, our JSON view converts our complex data into simple Elixir data-structures. Once our view layer returns, Phoenix uses the `Jason` library to encode JSON and send the response to the client.
This view is very simple. The `index` function receives all URLs, and converts them into a list of maps. Those maps are placed inside the data key at the root, exactly as we saw when interfacing with our application from `cURL`. In other words, our JSON view converts our complex data into simple Elixir data-structures. Once our view layer returns, Phoenix uses the built-in [JSON library](https://hexdocs.pm/elixir/1.18/JSON.html) to encode JSON and send the response to the client.

If you explore the remaining controller, you will learn the `show` action is similar to the `index` one. For `create`, `update`, and `delete` actions, Phoenix uses one other important feature, called "Action fallback".

Expand Down
2 changes: 1 addition & 1 deletion guides/plug.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ The default endpoint plugs do quite a lot of work. Here they are in order:

- `Plug.Telemetry` - adds instrumentation points so Phoenix can log the request path, status code and request time by default.

- `Plug.Parsers` - parses the request body when a known parser is available. By default, this plug can handle URL-encoded, multipart and JSON content (with `Jason`). The request body is left untouched if the request content-type cannot be parsed.
- `Plug.Parsers` - parses the request body when a known parser is available. By default, this plug can handle URL-encoded, multipart and JSON content. The request body is left untouched if the request content-type cannot be parsed.

- `Plug.MethodOverride` - converts the request method to PUT, PATCH or DELETE for POST requests with a valid `_method` parameter.

Expand Down
4 changes: 2 additions & 2 deletions installer/templates/phx_single/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
# Use built-in `JSON` for JSON parsing in Phoenix
config :phoenix, :json_library, JSON

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
Expand Down
4 changes: 2 additions & 2 deletions installer/templates/phx_umbrella/config/extra_config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
# Use built-in `JSON` for JSON parsing in Phoenix
config :phoenix, :json_library, JSON

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
Expand Down
2 changes: 1 addition & 1 deletion installer/test/phx_new_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ defmodule Mix.Tasks.Phx.NewTest do
assert_file("phx_blog/config/config.exs", fn file ->
assert file =~ "ecto_repos: [PhxBlog.Repo]"
assert file =~ "generators: [timestamp_type: :utc_datetime]"
assert file =~ "config :phoenix, :json_library, Jason"
assert file =~ "config :phoenix, :json_library, JSON"
assert file =~ ~s[cd: Path.expand("../assets", __DIR__),]
refute file =~ "namespace: PhxBlog"
refute file =~ "config :phx_blog, :generators"
Expand Down
2 changes: 1 addition & 1 deletion installer/test/phx_new_umbrella_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
assert file =~ ~r/config :esbuild/
assert file =~ "cd: Path.expand(\"../apps/phx_umb_web/assets\", __DIR__)"
assert file =~ ~S[import_config "#{config_env()}.exs"]
assert file =~ "config :phoenix, :json_library, Jason"
assert file =~ "config :phoenix, :json_library, JSON"
assert file =~ "ecto_repos: [PhxUmb.Repo]"
assert file =~ ":phx_umb_web, PhxUmbWeb.Endpoint"
assert file =~ "generators: [context_app: :phx_umb]\n"
Expand Down
2 changes: 1 addition & 1 deletion integration_test/config/config.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Config

config :phoenix, :json_library, Jason
config :phoenix, :json_library, JSON

config :swoosh, api_client: false

Expand Down
4 changes: 3 additions & 1 deletion lib/phoenix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ defmodule Phoenix do
"""
use Application

@default_json_library if Code.ensure_loaded?(JSON), do: JSON, else: Jason

@doc false
def start(_type, _args) do
# Warm up caches
Expand Down Expand Up @@ -45,7 +47,7 @@ defmodule Phoenix do

"""
def json_library do
Application.get_env(:phoenix, :json_library, Jason)
Application.get_env(:phoenix, :json_library, @default_json_library)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot change the default here, as that would be a breaking change. We can only change it in new apps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. It makes things a bit simpler for this PR:

  • Mark the default library in Phoenix.json_library as a breaking change for Phoenix v2.0
  • Use JSON for new apps
  • Make sure that tests pass with both JSON and Jason libraries

What I'm not sure is whether changes in config/config.exs are safe?

I could be wrong, but it seems that they aren't propagated to library users. The users will only see variables from application.env, right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing config is safe!

end

@doc """
Expand Down
6 changes: 3 additions & 3 deletions test/phoenix/socket/v1_json_serializer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
IO.iodata_to_binary(encoded)
end

test "encode!/1 encodes `Phoenix.Socket.Message` as JSON" do

Check failure on line 23 in test/phoenix/socket/v1_json_serializer_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3.2.9 | Elixir 1.15.8)

test encode!/1 encodes `Phoenix.Socket.Message` as JSON (Phoenix.Socket.V1.JSONSerializerTest)
msg = %Message{topic: "t", event: "e", payload: "m"}
encoded = encode!(@serializer, msg)

assert Jason.decode!(encoded) == %{
assert JSON.decode!(encoded) == %{
"event" => "e",
"payload" => "m",
"ref" => nil,
Expand All @@ -36,7 +36,7 @@
msg = %Reply{topic: "t", ref: "null"}
encoded = encode!(@serializer, msg)

assert Jason.decode!(encoded) == %{
assert JSON.decode!(encoded) == %{
"event" => "phx_reply",
"payload" => %{"response" => nil, "status" => nil},
"ref" => "null",
Expand All @@ -57,11 +57,11 @@
)
end

test "fastlane!/1 encodes a broadcast into a message as JSON" do

Check failure on line 60 in test/phoenix/socket/v1_json_serializer_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3.2.9 | Elixir 1.15.8)

test fastlane!/1 encodes a broadcast into a message as JSON (Phoenix.Socket.V1.JSONSerializerTest)
msg = %Broadcast{topic: "t", event: "e", payload: "m"}
encoded = fastlane!(@serializer, msg)

assert Jason.decode!(encoded) == %{
assert JSON.decode!(encoded) == %{
"event" => "e",
"payload" => "m",
"ref" => nil,
Expand Down
2 changes: 1 addition & 1 deletion test/phoenix/socket/v2_json_serializer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
end
end

test "encode!/1 encodes `Phoenix.Socket.Message` as JSON" do

Check failure on line 91 in test/phoenix/socket/v2_json_serializer_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3.2.9 | Elixir 1.15.8)

test encode!/1 encodes `Phoenix.Socket.Message` as JSON (Phoenix.Socket.V2.JSONSerializerTest)
msg = %Message{topic: "t", event: "e", payload: %{m: 1}}
assert encode!(@serializer, msg) == @v2_msg_json
end
Expand All @@ -98,11 +98,11 @@
assert_raise ArgumentError, fn -> encode!(@serializer, msg) end
end

test "encode!/1 encodes `Phoenix.Socket.Reply` as JSON" do

Check failure on line 101 in test/phoenix/socket/v2_json_serializer_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3.2.9 | Elixir 1.15.8)

test encode!/1 encodes `Phoenix.Socket.Reply` as JSON (Phoenix.Socket.V2.JSONSerializerTest)
msg = %Reply{topic: "t", payload: %{m: 1}}
encoded = encode!(@serializer, msg)

assert Jason.decode!(encoded) == [
assert JSON.decode!(encoded) == [

Check warning on line 105 in test/phoenix/socket/v2_json_serializer_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3.2.9 | Elixir 1.15.8)

JSON.decode!/1 is undefined (module JSON is not available or is yet to be defined)
nil,
nil,
"t",
Expand All @@ -111,12 +111,12 @@
]
end

test "decode!/2 decodes `Phoenix.Socket.Message` from JSON" do

Check failure on line 114 in test/phoenix/socket/v2_json_serializer_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3.2.9 | Elixir 1.15.8)

test decode!/2 decodes `Phoenix.Socket.Message` from JSON (Phoenix.Socket.V2.JSONSerializerTest)
assert %Message{topic: "t", event: "e", payload: %{"m" => 1}} ==
decode!(@serializer, @v2_msg_json, opcode: :text)
end

test "fastlane!/1 encodes a broadcast into a message as JSON" do

Check failure on line 119 in test/phoenix/socket/v2_json_serializer_test.exs

View workflow job for this annotation

GitHub Actions / mix test (OTP 25.3.2.9 | Elixir 1.15.8)

test fastlane!/1 encodes a broadcast into a message as JSON (Phoenix.Socket.V2.JSONSerializerTest)
msg = %Broadcast{topic: "t", event: "e", payload: %{m: 1}}
assert fastlane!(@serializer, msg) == @v2_fastlane_json
end
Expand Down
6 changes: 3 additions & 3 deletions test/phoenix/test/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -328,13 +328,13 @@ defmodule Phoenix.Test.ConnTest do
build_conn(:get, "/") |> resp(200, "ok") |> json_response(200)
end

assert_raise Jason.DecodeError,
"unexpected byte at position 0: 0x6F (\"o\")", fn ->
assert_raise JSON.DecodeError,
"invalid byte 111 at position (byte offset) 0", fn ->
build_conn(:get, "/") |> put_resp_content_type("application/json")
|> resp(200, "ok") |> json_response(200)
end

assert_raise Jason.DecodeError, ~r/unexpected end of input at position 0/, fn ->
assert_raise JSON.DecodeError, ~r/unexpected end of JSON binary at position \(byte offset\) 0/, fn ->
build_conn(:get, "/")
|> put_resp_content_type("application/json")
|> resp(200, "")
Expand Down
Loading