Skip to content

Commit

Permalink
Implement JSON.Encoder protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
sabiwara committed Dec 13, 2024
1 parent 5c63604 commit b0f699e
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Dev

- Support the `JSON.Encode` protocol in Elixir 1.18+.

## v0.7.3 (2024-12-12)

### Enhancements
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 23 additions & 1 deletion lib/ord_map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions lib/vector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
30 changes: 20 additions & 10 deletions test/ord_map_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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!()

Check warning on line 122 in test/ord_map_test.exs

View workflow job for this annotation

GitHub Actions / OTP 23.3 / Elixir 1.12

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 122 in test/ord_map_test.exs

View workflow job for this annotation

GitHub Actions / OTP 23.3 / Elixir 1.12

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 122 in test/ord_map_test.exs

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.14

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 122 in test/ord_map_test.exs

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.14

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 122 in test/ord_map_test.exs

View workflow job for this annotation

GitHub Actions / OTP 26.0 / Elixir 1.15

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 122 in test/ord_map_test.exs

View workflow job for this annotation

GitHub Actions / OTP 26.0 / Elixir 1.15

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)
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
11 changes: 11 additions & 0 deletions test/vector_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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!()

Check warning on line 548 in test/vector_test.exs

View workflow job for this annotation

GitHub Actions / OTP 23.3 / Elixir 1.12

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 548 in test/vector_test.exs

View workflow job for this annotation

GitHub Actions / OTP 23.3 / Elixir 1.12

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 548 in test/vector_test.exs

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.14

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 548 in test/vector_test.exs

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.14

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 548 in test/vector_test.exs

View workflow job for this annotation

GitHub Actions / OTP 26.0 / Elixir 1.15

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)

Check warning on line 548 in test/vector_test.exs

View workflow job for this annotation

GitHub Actions / OTP 26.0 / Elixir 1.15

JSON.encode!/1 is undefined (module JSON is not available or is yet to be defined)
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

0 comments on commit b0f699e

Please sign in to comment.