Skip to content
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

Support JSON #4566

Merged
merged 4 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
70 changes: 37 additions & 33 deletions lib/ecto/json.ex
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
if Code.ensure_loaded?(Jason.Encoder) do
defimpl Jason.Encoder, for: Ecto.Association.NotLoaded do
def encode(%{__owner__: owner, __field__: field}, _) do
raise """
cannot encode association #{inspect(field)} from #{inspect(owner)} to \
JSON because the association was not loaded.
for encoder <- [Jason.Encoder, JSON.Encoder] do
module = Macro.inspect_atom(:literal, encoder)

You can either preload the association:
if Code.ensure_loaded?(encoder) do
defimpl encoder, for: Ecto.Association.NotLoaded do
def encode(%{__owner__: owner, __field__: field}, _) do
raise """
cannot encode association #{inspect(field)} from #{inspect(owner)} to \
JSON because the association was not loaded.

Repo.preload(#{inspect(owner)}, #{inspect(field)})
You can either preload the association:

Or choose to not encode the association when converting the struct \
to JSON by explicitly listing the JSON fields in your schema:
Repo.preload(#{inspect(owner)}, #{inspect(field)})

defmodule #{inspect(owner)} do
# ...
Or choose to not encode the association when converting the struct \
to JSON by explicitly listing the JSON fields in your schema:

@derive {Jason.Encoder, only: [:name, :title, ...]}
schema ... do
defmodule #{inspect(owner)} do
# ...

You can also use the :except option instead of :only if you would \
prefer to skip some fields.
"""
@derive {#{unquote(module)}, only: [:name, :title, ...]}
schema ... do

You can also use the :except option instead of :only if you would \
prefer to skip some fields.
"""
end
end
end

defimpl Jason.Encoder, for: Ecto.Schema.Metadata do
def encode(%{schema: schema}, _) do
raise """
cannot encode metadata from the :__meta__ field for #{inspect(schema)} \
to JSON. This metadata is used internally by Ecto and should never be \
exposed externally.
defimpl encoder, for: Ecto.Schema.Metadata do
def encode(%{schema: schema}, _) do
raise """
cannot encode metadata from the :__meta__ field for #{inspect(schema)} \
to JSON. This metadata is used internally by Ecto and should never be \
exposed externally.

You can either map the schemas to remove the :__meta__ field before \
encoding or explicitly list the JSON fields in your schema:
You can either map the schemas to remove the :__meta__ field before \
encoding or explicitly list the JSON fields in your schema:

defmodule #{inspect(schema)} do
# ...
defmodule #{inspect(schema)} do
# ...

@derive {Jason.Encoder, only: [:name, :title, ...]}
schema ... do
@derive {#{unquote(module)}, only: [:name, :title, ...]}
schema ... do

You can also use the :except option instead of :only if you would \
prefer to skip some fields.
"""
You can also use the :except option instead of :only if you would \
prefer to skip some fields.
"""
end
end
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%{
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
Expand Down
39 changes: 25 additions & 14 deletions test/ecto/json_test.exs
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
defmodule Ecto.JsonTest do
use ExUnit.Case, async: true

@implementations [{Jason, Jason.Encoder}, {JSON, JSON.Encoder}]

loaded_implementations =
for {_lib, encoder} = implementation <- @implementations,
Code.ensure_loaded?(encoder),
do: implementation

defmodule User do
use Ecto.Schema

@derive Jason.Encoder
@derive Keyword.values(loaded_implementations)
schema "users" do
has_many :comments, Ecto.Comment
end
end

test "encodes decimal" do
decimal = Decimal.new("1.0")
assert Jason.encode!(decimal) == ~s("1.0")
end
for {json_library, _encoder} <- loaded_implementations do
describe to_string(json_library) do
test "encodes decimal" do
decimal = Decimal.new("1.0")
assert unquote(json_library).encode!(decimal) == ~s("1.0")
end

test "fails on association not loaded" do
assert_raise RuntimeError,
~r/cannot encode association :comments from Ecto.JsonTest.User to JSON/,
fn -> Jason.encode!(%User{}.comments) end
end
test "fails on association not loaded" do
assert_raise RuntimeError,
~r/cannot encode association :comments from Ecto.JsonTest.User to JSON/,
fn -> unquote(json_library).encode!(%User{}.comments) end
end

test "fails when encoding __meta__" do
assert_raise RuntimeError,
~r/cannot encode metadata from the :__meta__ field for Ecto.JsonTest.User to JSON/,
fn -> Jason.encode!(%User{comments: []}) end
test "fails when encoding __meta__" do
assert_raise RuntimeError,
~r/cannot encode metadata from the :__meta__ field for Ecto.JsonTest.User to JSON/,
fn -> unquote(json_library).encode!(%User{comments: []}) end
end
end
end
end
Loading