diff --git a/CHANGELOG.md b/CHANGELOG.md index 25dea68..e04a623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Dev +- Support the `JSON.Encode` protocol in Elixir 1.18+. + ## v0.7.3 (2024-12-12) ### Enhancements diff --git a/README.md b/README.md index 681c488..0989d05 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ All data structures offer: - well-documented APIs that are consistent with the standard library - implementation of `Inspect`, `Enumerable` and `Collectable` protocols - implementation of the `Access` behaviour +- implementation of the `JSON.Encoder` protocol (on Elixir 1.18+) - (optional if `Jason` is installed) implemention of the `Jason.Encoder` protocol diff --git a/lib/ord_map.ex b/lib/ord_map.ex index da18c0f..d545fb0 100644 --- a/lib/ord_map.ex +++ b/lib/ord_map.ex @@ -15,6 +15,7 @@ defmodule Aja.OrdMap do `Aja.OrdMap`: - provides efficient (logarithmic) access: it is not a simple list of tuples - implements the `Access` behaviour, `Enum` / `Inspect` / `Collectable` protocols + - implements the `JSON.Encoder` protocol (on Elixir 1.18+) - optionally implements the `Jason.Encoder` protocol if `Jason` is installed ## Examples @@ -98,7 +99,9 @@ defmodule Aja.OrdMap do true - ## With `Jason` + ## JSON encoding + + Both `JSON.Encoder` and `Jason.Encoder` are supported. iex> Aja.OrdMap.new([{"un", 1}, {"deux", 2}, {"trois", 3}]) |> Jason.encode!() "{\"un\":1,\"deux\":2,\"trois\":3}" @@ -1655,6 +1658,25 @@ defmodule Aja.OrdMap do end end + if Code.ensure_loaded?(JSON.Encoder) do + defimpl JSON.Encoder do + def encode(map, encoder) do + key_values = + Aja.Enum.map_intersperse(map, ?,, fn {key, value} -> + [key(key, encoder), ?:, encoder.(value, encoder)] + end) + + [?{, key_values, ?}] + end + + # ported from :json + defp key(key, encode) when is_binary(key), do: encode.(key, encode) + defp key(key, encode) when is_atom(key), do: encode.(Atom.to_string(key), encode) + defp key(key, _encode) when is_integer(key), do: [?", Integer.to_string(key), ?"] + defp key(key, _encode) when is_float(key), do: [?", Float.to_string(key), ?"] + end + end + if Code.ensure_loaded?(Jason.Encoder) do defimpl Jason.Encoder do def encode(map, opts) do diff --git a/lib/vector.ex b/lib/vector.ex index fc523fb..04b539e 100644 --- a/lib/vector.ex +++ b/lib/vector.ex @@ -19,6 +19,7 @@ defmodule Aja.Vector do - is heavily optimized and should offer higher performance in most use cases, especially "loops" like `map/2` / `to_list/1` / `foldl/3` - mirrors most of the `Enum` module API (together with `Aja.Enum`) with highly optimized versions for vectors (`Aja.Enum.join/1`, `Aja.Enum.sum/1`, `Aja.Enum.random/1`...) - supports negative indexing (e.g. `-1` corresponds to the last element) + - implements the `JSON.Encoder` protocol (on Elixir 1.18+) - optionally implements the `Jason.Encoder` protocol if `Jason` is installed Note: most of the design is inspired by @@ -2039,6 +2040,15 @@ defmodule Aja.Vector do end end + if Code.ensure_loaded?(JSON.Encoder) do + defimpl JSON.Encoder do + def encode(vector, encoder) do + values = Aja.Enum.map_intersperse(vector, ?,, fn value -> encoder.(value, encoder) end) + [?[, values, ?]] + end + end + end + if Code.ensure_loaded?(Jason.Encoder) do defimpl Jason.Encoder do def encode(vector, opts) do diff --git a/test/ord_map_test.exs b/test/ord_map_test.exs index 9229e46..c5fdeeb 100644 --- a/test/ord_map_test.exs +++ b/test/ord_map_test.exs @@ -88,16 +88,15 @@ defmodule Aja.OrdMapTest do Aja.OrdMap.new([{:foo, :atom}, {5, :integer}]) |> inspect() end - if Version.compare(System.version(), "1.14.0") != :lt do - test "from_struct/1" do - ord_map = %User{name: "John", age: 44} |> Aja.OrdMap.from_struct() - expected = Aja.OrdMap.new(name: "John", age: 44) - assert ^expected = ord_map - - ord_map = Aja.OrdMap.from_struct(User) - expected = Aja.OrdMap.new(name: nil, age: nil) - assert ^expected = ord_map - end + @tag skip: Version.compare(System.version(), "1.14.0") == :lt + test "from_struct/1" do + ord_map = %User{name: "John", age: 44} |> Aja.OrdMap.from_struct() + expected = Aja.OrdMap.new(name: "John", age: 44) + assert ^expected = ord_map + + ord_map = Aja.OrdMap.from_struct(User) + expected = Aja.OrdMap.new(name: nil, age: nil) + assert ^expected = ord_map end test "get_and_update/3" do @@ -117,5 +116,16 @@ defmodule Aja.OrdMapTest do Aja.OrdMap.get_and_update!(ord_map, :b, fn value -> value end) end end + + @tag skip: Version.compare(System.version(), "1.18.0-rc.0") == :lt + test "JSON.encode!/1" do + result = Aja.OrdMap.new([{"un", 1}, {"deux", 2}, {"trois", 3}]) |> JSON.encode!() + assert result == "{\"un\":1,\"deux\":2,\"trois\":3}" + end + + test "Jason.encode!/1" do + result = Aja.OrdMap.new([{"un", 1}, {"deux", 2}, {"trois", 3}]) |> Jason.encode!() + assert result == "{\"un\":1,\"deux\":2,\"trois\":3}" + end end end diff --git a/test/vector_test.exs b/test/vector_test.exs index b01d3b1..d3dca73 100644 --- a/test/vector_test.exs +++ b/test/vector_test.exs @@ -542,5 +542,16 @@ defmodule Aja.VectorTest do assert [18, 19] == Aja.Vector.new(1..20) |> Enum.slice(-3, 2) assert Enum.to_list(2..99) == Aja.Vector.new(1..100) |> Enum.slice(1..98) end + + @tag skip: Version.compare(System.version(), "1.18.0-rc.0") == :lt + test "JSON.encode!/1" do + result = Aja.Vector.new(["un", 2, :trois]) |> JSON.encode!() + assert result == "[\"un\",2,\"trois\"]" + end + + test "Jason.encode!/1" do + result = Aja.Vector.new(["un", 2, :trois]) |> Jason.encode!() + assert result == "[\"un\",2,\"trois\"]" + end end end