99 lines
2.8 KiB
Elixir
99 lines
2.8 KiB
Elixir
defmodule Jason.Helpers do
|
|
@moduledoc """
|
|
Provides macro facilities for partial compile-time encoding of JSON.
|
|
"""
|
|
|
|
alias Jason.{Codegen, Fragment}
|
|
|
|
@doc ~S"""
|
|
Encodes a JSON map from a compile-time keyword.
|
|
|
|
Encodes the keys at compile time and strives to create as flat iodata
|
|
structure as possible to achieve maximum efficiency. Does encoding
|
|
right at the call site, but returns an `%Jason.Fragment{}` struct
|
|
that needs to be passed to one of the "main" encoding functions -
|
|
for example `Jason.encode/2` for final encoding into JSON - this
|
|
makes it completely transparent for most uses.
|
|
|
|
Only allows keys that do not require escaping in any of the supported
|
|
encoding modes. This means only ASCII characters from the range
|
|
0x1F..0x7F excluding '\', '/' and '"' are allowed - this also excludes
|
|
all control characters like newlines.
|
|
|
|
Preserves the order of the keys.
|
|
|
|
## Example
|
|
|
|
iex> fragment = json_map(foo: 1, bar: 2)
|
|
iex> Jason.encode!(fragment)
|
|
"{\"foo\":1,\"bar\":2}"
|
|
|
|
"""
|
|
defmacro json_map(kv) do
|
|
kv_values = Macro.expand(kv, __CALLER__)
|
|
kv_vars = Enum.map(kv_values, fn {key, _} -> {key, generated_var(key, Codegen)} end)
|
|
|
|
values = Enum.map(kv_values, &elem(&1, 1))
|
|
vars = Enum.map(kv_vars, &elem(&1, 1))
|
|
|
|
escape = quote(do: escape)
|
|
encode_map = quote(do: encode_map)
|
|
encode_args = [escape, encode_map]
|
|
kv_iodata = Codegen.build_kv_iodata(kv_vars, encode_args)
|
|
|
|
quote do
|
|
{unquote_splicing(vars)} = {unquote_splicing(values)}
|
|
|
|
%Fragment{
|
|
encode: fn {unquote(escape), unquote(encode_map)} ->
|
|
unquote(kv_iodata)
|
|
end
|
|
}
|
|
end
|
|
end
|
|
|
|
@doc ~S"""
|
|
Encodes a JSON map from a variable containing a map and a compile-time
|
|
list of keys.
|
|
|
|
It is equivalent to calling `Map.take/2` before encoding. Otherwise works
|
|
similar to `json_map/2`.
|
|
|
|
## Example
|
|
|
|
iex> map = %{a: 1, b: 2, c: 3}
|
|
iex> fragment = json_map_take(map, [:c, :b])
|
|
iex> Jason.encode!(fragment)
|
|
"{\"c\":3,\"b\":2}"
|
|
|
|
"""
|
|
defmacro json_map_take(map, take) do
|
|
take = Macro.expand(take, __CALLER__)
|
|
kv = Enum.map(take, &{&1, generated_var(&1, Codegen)})
|
|
escape = quote(do: escape)
|
|
encode_map = quote(do: encode_map)
|
|
encode_args = [escape, encode_map]
|
|
kv_iodata = Codegen.build_kv_iodata(kv, encode_args)
|
|
|
|
quote do
|
|
case unquote(map) do
|
|
%{unquote_splicing(kv)} ->
|
|
%Fragment{
|
|
encode: fn {unquote(escape), unquote(encode_map)} ->
|
|
unquote(kv_iodata)
|
|
end
|
|
}
|
|
|
|
other ->
|
|
raise ArgumentError,
|
|
"expected a map with keys: #{unquote(inspect(take))}, got: #{inspect(other)}"
|
|
end
|
|
end
|
|
end
|
|
|
|
# The same as Macro.var/2 except it sets generated: true
|
|
defp generated_var(name, context) do
|
|
{name, [generated: true], context}
|
|
end
|
|
end
|