Packages
glazer
0.5.10
0.5.17
0.5.16
0.5.15
0.5.14
retired
0.5.13
retired
0.5.12
retired
0.5.11
retired
0.5.10
retired
0.5.9
retired
0.5.8
retired
0.5.7
retired
0.5.6
retired
0.5.5
retired
0.5.4
retired
0.5.3
retired
0.5.2
retired
0.5.1
retired
0.5.0
retired
0.3.2
retired
0.3.0
retired
0.2.6
retired
0.2.5
retired
0.2.4
retired
0.2.3
retired
0.2.2
retired
0.2.1
retired
0.2.0
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
Erlang NIF JSON encoder/decoder using the glaze C++ library
Retired package: Deprecated - Deprecated
Current section
Files
Jump to
Current section
Files
src/glazer.erl
-module(glazer).
-moduledoc """
Fast JSON/YAML/CSV encoding and decoding using the glaze C++ library.
The public API is split across [`glazer_json`](`glazer_json`),
[`glazer_yaml`](`glazer_yaml`), and [`glazer_csv`](`glazer_csv`).
By default `null`s are represented as the atom `null`. To change it
application-wide, set the `null` env key in your config:
**Erlang**:
```erlang
{glazer, [{null, nil}]}.
```
**Elixir**:
```elixir
config :glazer, null: nil
```
""".
-export([encode_integer/1, decode_integer/1, try_decode_integer/1,
compile_path/1, find/2, info/0]).
-type path_step() :: {field, binary()} | iterate | {index, integer()}.
-type path() :: [path_step()].
-export_type([path/0]).
-export([json_try_decode/1, json_try_decode/2,
yaml_try_decode/1, yaml_try_decode/2, yaml_encode/1, yaml_encode/2,
csv_try_decode/1, csv_try_decode/2, csv_encode/1, csv_encode/2,
json_encode/1, json_encode/2, json_minify/1, json_prettify/1,
json_query/2, json_query/3, json_scan/1, json_scan/2,
format_error/2]).
-on_load(init/0).
-define(LIBNAME, glazer).
-define(NOT_LOADED_ERROR,
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, ?LINE}]})).
-doc false.
init() ->
NullVal = application:get_env(?LIBNAME, null, null),
is_atom(NullVal) orelse erlang:error("glazer: option 'null' must be an atom"),
SoName =
case code:priv_dir(?LIBNAME) of
{error, bad_name} ->
case code:which(?MODULE) of
Filename when is_list(Filename) ->
Dir = filename:dirname(filename:dirname(Filename)),
filename:join([Dir, "priv", ?LIBNAME]);
_ ->
filename:join("../priv", ?LIBNAME)
end;
Dir ->
filename:join(Dir, ?LIBNAME)
end,
erlang:load_nif(SoName, [{null, NullVal}]).
-doc false.
json_try_decode(_Input) ->
?NOT_LOADED_ERROR.
-doc false.
json_try_decode(_Input, _Opts) ->
?NOT_LOADED_ERROR.
-doc false.
yaml_try_decode(_Input) ->
?NOT_LOADED_ERROR.
-doc false.
yaml_try_decode(_Input, _Opts) ->
?NOT_LOADED_ERROR.
-doc false.
yaml_encode(_Data) ->
?NOT_LOADED_ERROR.
-doc false.
yaml_encode(_Data, _Opts) ->
?NOT_LOADED_ERROR.
-doc false.
csv_try_decode(_Input) ->
?NOT_LOADED_ERROR.
-doc false.
csv_try_decode(_Input, _Opts) ->
?NOT_LOADED_ERROR.
-doc false.
csv_encode(_Data) ->
?NOT_LOADED_ERROR.
-doc false.
csv_encode(_Data, _Opts) ->
?NOT_LOADED_ERROR.
-doc false.
json_encode(_Data) ->
?NOT_LOADED_ERROR.
-doc false.
json_encode(_Data, _Opts) ->
?NOT_LOADED_ERROR.
-doc false.
json_minify(_Input) ->
?NOT_LOADED_ERROR.
-doc false.
json_prettify(_Input) ->
?NOT_LOADED_ERROR.
-doc false.
json_query(_Input, _Filter) ->
?NOT_LOADED_ERROR.
-doc false.
json_query(_Input, _Filter, _DecodeOpts) ->
?NOT_LOADED_ERROR.
-doc false.
json_scan(_Bin) ->
?NOT_LOADED_ERROR.
-doc false.
json_scan(_Bin, _ScanState) ->
?NOT_LOADED_ERROR.
-doc """
Encode an integer to its JSON string representation.
Raises `badarg` if `Int` is not an integer.
""".
-spec encode_integer(integer()) -> binary().
encode_integer(_Int) ->
?NOT_LOADED_ERROR.
-doc """
Decode a JSON number string to an integer.
Raises `invalid_number_format` on invalid input.
""".
-spec decode_integer(binary() | iolist()) -> integer().
decode_integer(NumberString) ->
case try_decode_integer(NumberString) of
{ok, Int} -> Int;
{error, Reason} -> error(Reason)
end.
-doc """
Decode a JSON number string to an integer, returning `{ok, Int}` or
`{error, invalid_number_format}` instead of raising.
""".
-spec try_decode_integer(binary() | iolist()) -> {ok, integer()} | {error, invalid_number_format}.
try_decode_integer(_NumberString) ->
?NOT_LOADED_ERROR.
-doc """
Format an error message with `io_lib:format/2` and flatten to a binary.
""".
-spec format_error(io:format(), [term()]) -> binary().
format_error(Format, Args) ->
iolist_to_binary(io_lib:format(Format, Args)).
-doc """
Compile a [jq](https://jqlang.org/)-style path expression into a `path()`
for use with `find/2`.
Supports a small subset of jq syntax:
- `.` - identity (returns the input term itself)
- `.foo`, `.foo.bar` - field access (map key)
- `.["foo bar"]` - bracketed field access, for keys with special characters
- `.[]` - iterate: every element of a list, or every value of a map
- `.[N]`, `.[-N]` - index into a list (negative indices count from the end)
Segments can be chained freely, e.g. `.a.b[].c[0]`.
Raises `{invalid_path, Filter}` if `Filter` doesn't match this grammar.
## Example
```erlang
1> glazer:compile_path(<<".a[].b">>).
[{field,<<"a">>},iterate,{field,<<"b">>}]
```
""".
-spec compile_path(binary() | iolist()) -> path().
compile_path(Filter) ->
case nif_compile_path(iolist_to_binary(Filter)) of
{ok, Path} -> Path;
{error, Reason} -> error(Reason)
end.
-spec nif_compile_path(binary()) -> {ok, path()} | {error, {invalid_path, binary()}}.
nif_compile_path(_Filter) ->
?NOT_LOADED_ERROR.
-doc """
Return build information about the loaded NIF library:
- `app_version`: the `vsn` from `glazer.app.src` that this build was made
from (e.g. `<<"0.5.9">>`)
- `version`: `git describe` of the checkout the NIF was built from
(e.g. `<<"0.5.9-3-abc123">>`, with a trailing `*` if the
working tree was dirty at build time)
- `pgo`: `true` if built with profile-guided optimisation
(`make optimize` / `make PGO=use`)
- `optimization`: `none`, `'O1'` (debug/ASan builds), or `'O3'` (release)
## Example
```erlang
1> glazer:info().
#{app_version => <<"0.5.9">>, version => <<"0.5.9-3-abc123">>,
pgo => true, optimization => 'O3'}
```
""".
-spec info() -> #{app_version => binary(), version => binary(),
pgo => boolean(), optimization => none | 'O1' | 'O3'}.
info() ->
nif_info().
-spec nif_info() -> map().
nif_info() ->
?NOT_LOADED_ERROR.
-doc """
Find value(s) in `Term` by walking `Path`.
`Term` is typically a decoded JSON/YAML document: nested maps and lists.
`Path` is either a `path()` produced by `compile_path/1`, or a raw
jq-style filter string (compiled on the fly via `compile_path/1` — raises
`{invalid_path, Filter}` if it doesn't parse).
As a string, `Path` supports a small subset of [jq](https://jqlang.org/)
syntax (see `compile_path/1` for the full grammar), e.g. `.a.b[].c[0]`.
Returns the list of values found at the end of `Path`. An empty list means
no match. `.[]` steps fan out over every element of a list (or every value
of a map), so a path containing `.[]` can produce multiple results.
## Examples
```erlang
1> Doc = #{<<"a">> => [#{<<"b">> => 1}, #{<<"b">> => 2}, #{<<"c">> => 3}]}.
2> glazer:find(Doc, <<".a[].b">>).
[1, 2]
3> glazer:find(Doc, <<".a[2].c">>).
[3]
4> glazer:find(Doc, <<".a[-1].c">>).
[3]
5> glazer:find(Doc, <<".">>).
[Doc]
6> glazer:find(#{<<"foo bar">> => 1}, <<".[\"foo bar\"]">>).
[1]
```
""".
-spec find(term(), path() | binary()) -> [term()].
find(Term, Filter) when is_binary(Filter) ->
find_path(Term, compile_path(Filter));
find(Term, Path) when is_list(Path) ->
find_path(Term, Path).
find_path(Term, []) ->
[Term];
find_path(Term, [iterate | Rest]) when is_list(Term) ->
[Found || Elem <- Term, Found <- find_path(Elem, Rest)];
find_path(Term, [iterate | Rest]) when is_map(Term) ->
[Found || Value <- maps:values(Term), Found <- find_path(Value, Rest)];
find_path(Term, [{index, Index} | Rest]) when is_list(Term) ->
Pos = if Index < 0 -> length(Term) + Index + 1; true -> Index + 1 end,
case Pos >= 1 andalso Pos =< length(Term) of
true -> find_path(lists:nth(Pos, Term), Rest);
false -> []
end;
find_path(Term, [{field, Key} | Rest]) when is_map(Term) ->
case maps:find(Key, Term) of
{ok, Value} -> find_path(Value, Rest);
error ->
%% Maps decoded with {keys, atom | existing_atom} have atom keys;
%% fall back to looking up Key as an existing atom.
try binary_to_existing_atom(Key, utf8) of
Atom ->
case maps:find(Atom, Term) of
{ok, Value} -> find_path(Value, Rest);
error -> []
end
catch
error:badarg -> []
end
end;
find_path(_Term, _Path) ->
[].