helpers.ex (2898B)
1 defmodule Jason.Helpers do 2 @moduledoc """ 3 Provides macro facilities for partial compile-time encoding of JSON. 4 """ 5 6 alias Jason.{Codegen, Fragment} 7 8 @doc ~S""" 9 Encodes a JSON map from a compile-time keyword. 10 11 Encodes the keys at compile time and strives to create as flat iodata 12 structure as possible to achieve maximum efficiency. Does encoding 13 right at the call site, but returns an `%Jason.Fragment{}` struct 14 that needs to be passed to one of the "main" encoding functions - 15 for example `Jason.encode/2` for final encoding into JSON - this 16 makes it completely transparent for most uses. 17 18 Only allows keys that do not require escaping in any of the supported 19 encoding modes. This means only ASCII characters from the range 20 0x1F..0x7F excluding '\', '/' and '"' are allowed - this also excludes 21 all control characters like newlines. 22 23 Preserves the order of the keys. 24 25 ## Example 26 27 iex> fragment = json_map(foo: 1, bar: 2) 28 iex> Jason.encode!(fragment) 29 "{\"foo\":1,\"bar\":2}" 30 31 """ 32 defmacro json_map(kv) do 33 kv_values = Macro.expand(kv, __CALLER__) 34 kv_vars = Enum.map(kv_values, fn {key, _} -> {key, generated_var(key, Codegen)} end) 35 36 values = Enum.map(kv_values, &elem(&1, 1)) 37 vars = Enum.map(kv_vars, &elem(&1, 1)) 38 39 escape = quote(do: escape) 40 encode_map = quote(do: encode_map) 41 encode_args = [escape, encode_map] 42 kv_iodata = Codegen.build_kv_iodata(kv_vars, encode_args) 43 44 quote do 45 {unquote_splicing(vars)} = {unquote_splicing(values)} 46 47 %Fragment{ 48 encode: fn {unquote(escape), unquote(encode_map)} -> 49 unquote(kv_iodata) 50 end 51 } 52 end 53 end 54 55 @doc ~S""" 56 Encodes a JSON map from a variable containing a map and a compile-time 57 list of keys. 58 59 It is equivalent to calling `Map.take/2` before encoding. Otherwise works 60 similar to `json_map/2`. 61 62 ## Example 63 64 iex> map = %{a: 1, b: 2, c: 3} 65 iex> fragment = json_map_take(map, [:c, :b]) 66 iex> Jason.encode!(fragment) 67 "{\"c\":3,\"b\":2}" 68 69 """ 70 defmacro json_map_take(map, take) do 71 take = Macro.expand(take, __CALLER__) 72 kv = Enum.map(take, &{&1, generated_var(&1, Codegen)}) 73 escape = quote(do: escape) 74 encode_map = quote(do: encode_map) 75 encode_args = [escape, encode_map] 76 kv_iodata = Codegen.build_kv_iodata(kv, encode_args) 77 78 quote do 79 case unquote(map) do 80 %{unquote_splicing(kv)} -> 81 %Fragment{ 82 encode: fn {unquote(escape), unquote(encode_map)} -> 83 unquote(kv_iodata) 84 end 85 } 86 87 other -> 88 raise ArgumentError, 89 "expected a map with keys: #{unquote(inspect(take))}, got: #{inspect(other)}" 90 end 91 end 92 end 93 94 # The same as Macro.var/2 except it sets generated: true 95 defp generated_var(name, context) do 96 {name, [generated: true], context} 97 end 98 end