Skip to content

Commit

Permalink
DRY up json protocol usage
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerWitt committed Jan 10, 2025
1 parent 0c729db commit fed82c7
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 87 deletions.
117 changes: 36 additions & 81 deletions lib/ecto/json.ex
Original file line number Diff line number Diff line change
@@ -1,97 +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.
"""
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.
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
# ...
@derive {#{unquote(module)}, only: [:name, :title, ...]}
schema ... do
@derive {Jason.Encoder, 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

if Code.ensure_loaded?(JSON.Encoder) do
defimpl JSON.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.
You can either preload the association:
Repo.preload(#{inspect(owner)}, #{inspect(field)})
Or choose to not encode the association when converting the struct \
to JSON by explicitly listing the JSON fields in your schema:
defmodule #{inspect(owner)} do
# ...
@derive {JSON.Encoder, 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

defimpl JSON.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 {JSON.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
14 changes: 8 additions & 6 deletions test/ecto/json_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ defmodule Ecto.JsonTest do

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

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

defmodule User do
use Ecto.Schema
Expand All @@ -15,7 +18,6 @@ defmodule Ecto.JsonTest do
end

for {json_library, _encoder} <- loaded_implementations do

describe to_string(json_library) do
test "encodes decimal" do
decimal = Decimal.new("1.0")
Expand All @@ -24,14 +26,14 @@ defmodule Ecto.JsonTest do

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
~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 -> unquote(json_library).encode!(%User{comments: []}) end
~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
Expand Down

0 comments on commit fed82c7

Please sign in to comment.