Skip to content

Commit

Permalink
Support JSON (#4566)
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerWitt authored Jan 10, 2025
1 parent 5275d6e commit bf01d85
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 48 deletions.
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

0 comments on commit bf01d85

Please sign in to comment.