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,
Comment thread
SteffenDE marked this conversation as resolved.
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.
Comment thread
SteffenDE marked this conversation as resolved.
Outdated
Comment thread
elfenlaid marked this conversation as resolved.
Outdated

```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.
Comment thread
elfenlaid marked this conversation as resolved.
Outdated

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

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 here we could do a similar runtime detection as in https://github.com/phoenixframework/phoenix/pull/6481/changes#r2356177940, and only configure JSON as the default for Elixir versions where its available.

Same for the umbrella app config template.

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.

It's a nice idea, yet I don't think it would fly.

The first option is to check Elixir version during phx.new, but the Elixir version that generates the project is not guaranteed to match the Elixir version that runs the project. So it sounds like a breaking change.

The second option is to embed this check into new projects. Technically it should work, but I'm not sure that such checks are awkward and probably are not something you would like to see in new projects.


# 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)
Comment thread
SteffenDE marked this conversation as resolved.
Outdated

@saleyn saleyn Jun 15, 2026

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.

Consider :persistent_term, which is roughly 30-100% faster:

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

  def json_library do
    case :persistent_term.get(:json_library, nil) do
      nil ->
        module = :application.get_env(:phoenix, :json_library, @default_json_library)
        :persistent_term.put(:json_library, module)
        module
      module ->
        module
    end
  end
end

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.

TIL persistent_term and it looks cool, but I'm not sure if we are in the position of adding anything significant to this PR. I'm happy to add it, but it's @SteffenDE call at this point

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