drop support for versions before OTP-17 and bump to 3.0.0

This commit is contained in:
Tristan Sloughter 2020-06-07 12:40:11 -06:00
parent 1bbe8986c7
commit fab436e1d5
No known key found for this signature in database
GPG key ID: AAB97DDECCEB8150
14 changed files with 64 additions and 286 deletions

33
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: EUnit
on:
pull_request:
branches:
- 'master'
push:
branches:
- 'master'
jobs:
build:
name: Test on OTP ${{ matrix.otp_version }} and ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
# important to check a pre-23 version that still uses nodetool
# plus 23 once it is released
otp_version: [22.3.2]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- uses: gleam-lang/setup-erlang@v1.0.0
with:
otp-version: ${{ matrix.otp_version }}
- name: compile
run: rebar3 compile
- name: test
run: rebar3 eunit

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.rebar3
.eunit
deps
ebin

View file

@ -1,10 +0,0 @@
language: erlang
script: rebar compile && rebar skip_deps=true eunit
otp_release:
- 22.0.7
- 21.3.3
- 20.3
- 19.3
- 18.3
- 17.5
- R16B03-1

View file

@ -1,3 +1,8 @@
v3.0
* drop support for OTP versions before 17.0
* remove definition options for disabling maps globally, `{return_maps, false}` is still an accepted option to `decode/2`
v2.8.2
* enable `debug_info` for rebar3

View file

@ -1,23 +1,17 @@
# jsx (v2.9.0) #
# jsx (v3.0.0) #
an erlang application for consuming, producing and manipulating [json][json].
inspired by [yajl][yajl]
**jsx** is built via [rebar3][rebar3], [rebar][rebar] or [mix][mix] and continuous integration testing provided courtesy [travis-ci][travis]
**jsx** is built via [rebar3][rebar3]
current status: [![Build Status](https://secure.travis-ci.org/talentdeficit/jsx.png?branch=develop)](http://travis-ci.org/talentdeficit/jsx)
current status: ![](https://github.com/talentdeficit/jsx/workflows/EUnit/badge.svg)
**jsx** is released under the terms of the [MIT][MIT] license
copyright 2010-2016 alisdair sullivan
## really important note ##
there are a few changes for users upgrading from 1.x. see [CHANGES.md](CHANGES.md)
for the overview or [migrating from 1.x](#migrating) for the details
## index ##
* [quickstart](#quickstart)
@ -39,7 +33,6 @@ for the overview or [migrating from 1.x](#migrating) for the details
- [`prettify/1`](#prettify1)
- [`is_json/1,2`](#is_json12)
- [`is_term/1,2`](#is_term12)
- [`maps_support/0`](#maps_support0)
* [callback exports](#callback_exports)
- [`Module:init/1`](#moduleinit1)
- [`Module:handle_event/2`](#modulehandle_event2)
@ -55,7 +48,7 @@ Add to `rebar.config`
{erl_opts, [debug_info]}.
{deps, [
...
{jsx, {git, "https://github.com/talentdeficit/jsx.git", {branch, "v2.8.0"}}}
{jsx, "~> 3.0"}
]}.
...
```
@ -65,19 +58,15 @@ Add to `rebar.config`
```bash
$ rebar3 compile
$ rebar3 eunit
$ rebar compile
$ rebar eunit
$ mix compile
$ mix eunit
```
#### to convert a utf8 binary containing a json string into an erlang term ####
```erlang
1> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>).
[{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]
2> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [return_maps]).
1> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, []).
#{<<"awesome">> => true,<<"library">> => <<"jsx">>}
2> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [{return_maps, false}]).
[{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]
3> jsx:decode(<<"[\"a\",\"list\",\"of\",\"words\"]">>).
[<<"a">>, <<"list">>, <<"of">>, <<"words">>]
```
@ -85,10 +74,10 @@ $ mix eunit
#### to convert an erlang term into a utf8 binary containing a json string ####
```erlang
1> jsx:encode([{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]).
<<"{\"library\": \"jsx\", \"awesome\": true}">>
2> jsx:encode(#{<<"library">> => <<"jsx">>, <<"awesome">> => true}).
1> jsx:encode(#{<<"library">> => <<"jsx">>, <<"awesome">> => true}).
<<"{\"awesome\":true,\"library\":\"jsx\"}">>
2> jsx:encode([{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]).
<<"{\"library\": \"jsx\", \"awesome\": true}">>
3> jsx:encode([<<"a">>, <<"list">>, <<"of">>, <<"words">>]).
<<"[\"a\",\"list\",\"of\",\"words\"]">>
```
@ -132,13 +121,6 @@ false
}">>
```
#### to compile **jsx** so that it always decodes json objects to maps ####
```bash
$ JSX_FORCE_MAPS rebar3 compile
$ JSX_FORCE_MAPS mix compile
```
## description ##
@ -172,29 +154,6 @@ quotes but must end with single quotes and must escape any single quotes they co
json and **jsx** only recognize escape sequences as outlined in the json spec. it just
ignores bad escape sequences leaving them in strings unaltered
### migrating from 1.x ###
if you're migrating from jsx v1.x to v2.x in most cases you won't need to
make any changes to your code
support for otp 17.0's new map type is now enabled by default when compiling
via rebar for any release that supports them. jsx should still compile cleanly for
earlier releases without any user intervention
if you used any of `replaced_bad_utf8`, `single_quoted_strings`, `comments`,
`ignored_bad_escapes` or `relax` you can simply omit them from your calls to jsx,
they are all enabled by default now. if you want stricter parsing see the new
[`strict` options](#option) available
if you were using jsx to parse partial json using it's streaming features it is now
disabled by default. you'll need to pass the `stream` option to calls to jsx functions
to reenable it
support for `pre_encode` and `post_decode` has been removed. they were fragile and hard
to understand and they prevented evolution of the encoding and decoding code
### json &lt;-> erlang mapping ###
**json** | **erlang**
@ -261,18 +220,7 @@ see below | `datetime()`
* objects
json objects are represented by erlang proplists. json maps may also be
encoded to json and optionally decoded to maps (via the `return_maps`
option)
the empty object has the special representation `[{}]` to differentiate it
from the empty list. ambiguities like `[true, false]` prevent the use of
the shorthand form of property lists using atoms as properties so all
properties must be tuples. all keys must be encoded as in `string` or as
atoms or integers (which will be escaped and converted to binaries for
presentation to handlers). values should be valid json values. repeated
keys are tolerated in json text decoded to erlang terms but are not allowed
in erlang terms encoded to json
json objects are represented by erlang maps.
* datetime
@ -533,9 +481,8 @@ new atoms to the atom table and will result in a `badarg` error if the atom
does not exist. `attempt_atom` will convert keys to atoms when they exist,
and leave them as binary otherwise
the option `return_maps` will attempt to return objects as maps instead of
proplists. this option has no effect when used with releases that do not
support maps
the option `{return_maps, false}` will return objects as proplists instead
of maps.
raises a `badarg` error exception if input is not valid json
@ -650,17 +597,6 @@ returns true if input is a valid erlang representation of json, false if not
what exactly constitutes valid json may be altered via [options](#option)
#### `maps_support/0` ####
```erlang
maps_support() -> true | false
```
if **jsx** was compiled with map support enabled returns `true`, else
`false`
## callback exports ##
the following functions should be exported from a **jsx** callback module
@ -753,8 +689,6 @@ jsx wouldn't be what it is without the contributions of [Paul J. Davis](https://
[yajl]: http://lloyd.github.com/yajl
[MIT]: http://www.opensource.org/licenses/mit-license.html
[rebar3]: https://rebar3.org
[rebar]: https://github.com/rebar/rebar
[mix]: http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html
[meck]: https://github.com/eproxus/meck
[rfc4627]: http://tools.ietf.org/html/rfc4627
[travis]: https://travis-ci.org/

43
mix.exs
View file

@ -1,43 +0,0 @@
defmodule JSX.Mixfile do
use Mix.Project
def project do
[
app: :jsx,
version: "2.11.0",
description: "an erlang application for consuming, producing and manipulating json. inspired by yajl",
deps: deps(Mix.env),
package: package(),
language: :erlang,
erlc_options: opts(Mix.env)
]
end
defp opts(:dev), do: [d: :TEST] ++ opts(:prod)
defp opts(_) do
force_maps = case System.get_env("JSX_FORCE_MAPS") do
nil -> []
_ -> [d: :maps_always]
end
[:debug_info, d: :maps_support] ++ force_maps
end
defp deps(_), do: [{:mixunit, "~> 0.9.2", only: :dev}]
defp package do
[
files: [
"CHANGES.md",
"LICENSE",
"mix.exs",
"rebar.config",
"rebar.config.script",
"README.md",
"src"
],
contributors: ["alisdair sullivan"],
links: %{"github" => "https://github.com/talentdeficit/jsx"},
licenses: ["MIT"]
]
end
end

View file

@ -1 +0,0 @@
%{"mixunit": {:hex, :mixunit, "0.9.2"}}

View file

@ -1,15 +0,0 @@
Def0 = case erlang:is_builtin(erlang, binary_to_integer, 1) andalso
erlang:is_builtin(erlang, binary_to_float, 1) of
true -> [];
false -> [{d, no_binary_to_whatever}]
end,
Def1 = case erlang:is_builtin(erlang, is_map, 1) of
true -> [{d, maps_support}|Def0];
false -> Def0
end,
Defs = case os:getenv("JSX_FORCE_MAPS") of
false -> Def1;
_ -> [{d, maps_always}|Def1]
end,
lists:keystore(erl_opts, 1, CONFIG,
{erl_opts, proplists:get_value(erl_opts, CONFIG, []) ++ Defs}).

View file

@ -1,7 +1,7 @@
{application, jsx,
[
{description, "a streaming, evented json parsing toolkit"},
{vsn, "2.11.0"},
{vsn, "3.0.0"},
{modules, [
jsx,
jsx_encoder,
@ -18,11 +18,7 @@
stdlib
]},
{env, []},
{files, [
"src",
"rebar.config", "rebar.config.script", "rebar.lock"
"README.md", "CHANGES.md", "LICENSE"
]},
{licenses, ["MIT"]},
{links, [{"Github", "https://github.com/talentdeficit/jsx"}]}
]}.

View file

@ -29,7 +29,6 @@
-export([consult/1, consult/2]).
-export([encoder/3, decoder/3, parser/3]).
-export([resume/3]).
-export([maps_support/0]).
-export_type([json_term/0, json_text/0, token/0]).
-export_type([encoder/0, decoder/0, parser/0, internal_state/0]).
@ -42,18 +41,6 @@
-export([init/1, handle_event/2]).
-endif.
-ifndef(maps_support).
-type json_term() :: [{binary() | atom(), json_term()}] | [{},...]
| [json_term()] | []
| {with_tail, json_term(), binary()}
| true | false | null
| integer() | float()
| binary() | atom()
| calendar:datetime().
-endif.
-ifdef(maps_support).
-type json_term() :: [{binary() | atom(), json_term()}] | [{},...]
| [json_term()] | []
| {with_tail, json_term(), binary()}
@ -62,7 +49,6 @@
| integer() | float()
| binary() | atom()
| calendar:datetime().
-endif.
-type json_text() :: binary().
@ -183,17 +169,6 @@ resume(Term, {decoder, State, Handler, Acc, Stack}, Config) ->
resume(Term, {parser, State, Handler, Stack}, Config) ->
jsx_parser:resume(Term, State, Handler, Stack, jsx_config:parse_config(Config)).
-spec maps_support() -> boolean().
-ifndef(maps_support).
maps_support() -> false.
-endif.
-ifdef(maps_support).
maps_support() -> true.
-endif.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").

View file

@ -35,18 +35,6 @@
-type config() :: list().
-export_type([config/0]).
-ifndef(maps_support).
-type json_value() :: list(json_value())
| list({binary() | atom(), json_value()})
| true
| false
| null
| integer()
| float()
| binary().
-endif.
-ifdef(maps_support).
-type json_value() :: list(json_value())
| map()
| true
@ -55,15 +43,8 @@
| integer()
| float()
| binary().
-endif.
-ifdef(maps_always).
opts(Opts) -> [return_maps, multi_term] ++ Opts.
-endif.
-ifndef(maps_always).
opts(Opts) -> [multi_term] ++ Opts.
-endif.
-spec consult(File::file:name_all(), Config::config()) -> [json_value()].

View file

@ -946,15 +946,8 @@ exp(_, N) -> {finish_float, N}.
finish_number(Rest, Handler, Acc, Stack, Config) ->
maybe_done(Rest, handle_event(format_number(Acc), Handler, Config), Stack, Config).
-ifndef(no_binary_to_whatever).
format_number({integer, Acc}) -> {integer, binary_to_integer(Acc)};
format_number({float, Acc}) -> {float, binary_to_float(Acc)}.
-else.
format_number({integer, Acc}) -> {integer, list_to_integer(unicode:characters_to_list(Acc))};
format_number({float, Acc}) -> {float, list_to_float(unicode:characters_to_list(Acc))}.
-endif.
true(<<$r, $u, $e, Rest/binary>>, Handler, Stack, Config) ->
maybe_done(Rest, handle_event({literal, true}, Handler, Config), Stack, Config);
@ -1882,26 +1875,26 @@ custom_incomplete_handler_test_() ->
return_tail_test_() ->
[
{"return_tail with tail", ?_assertEqual(
{with_tail,[{}],<<"3">>},
{with_tail,#{},<<"3">>},
jsx:decode(<<"{} 3">>, [return_tail])
)},
{"return_tail without tail", ?_assertEqual(
{with_tail,[{}],<<"">>},
{with_tail,#{},<<"">>},
jsx:decode(<<"{}">>, [return_tail])
)},
{"return_tail with trimmed whitespace", ?_assertEqual(
{with_tail,[{}],<<"">>},
{with_tail,#{},<<"">>},
jsx:decode(<<"{} ">>, [return_tail])
)},
{"return_tail and streaming", ?_assertEqual(
{with_tail,[{}],<<"3">>},
{with_tail,#{},<<"3">>},
begin
{incomplete, F} = jsx:decode(<<"{">>, [return_tail, stream]),
F(<<"} 3">>)
end
)},
{"return_tail and streaming", ?_assertEqual(
{with_tail,[{}],<<"">>},
{with_tail,#{},<<"">>},
begin
%% In case of infinite stream of objects a user does not know
%% when to call F(end_stream).

View file

@ -39,17 +39,11 @@ encode(Term) -> encode(Term, ?MODULE).
-spec encode(Term::any(), EntryPoint::module()) -> any().
-ifndef(maps_support).
encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
-endif.
-ifdef(maps_support).
encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 ->
[start_object, end_object];
encode(Term, EntryPoint) when is_map(Term) ->
[start_object] ++ unpack(Term, EntryPoint);
encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
-endif.
encode_([], _EntryPoint) -> [start_array, end_array];
encode_([{}], _EntryPoint) -> [start_object, end_object];
@ -75,16 +69,11 @@ unhitch([V|Rest], EntryPoint) ->
EntryPoint:encode(V, EntryPoint) ++ unhitch(Rest, EntryPoint);
unhitch([], _) -> [end_array].
-ifdef(maps_support).
unpack(Map, EntryPoint) -> unpack(Map, maps:keys(Map), EntryPoint).
unpack(Map, [K|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
[K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint) ++ unpack(Map, Rest, EntryPoint);
unpack(_, [], _) -> [end_object].
-endif.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").

View file

@ -44,19 +44,6 @@
-type config() :: list().
-export_type([config/0]).
-ifndef(maps_support).
-type json_value() :: list(json_value())
| list({binary() | atom(), json_value()}) | [{},...]
| {with_tail, json_value(), binary()}
| true
| false
| null
| integer()
| float()
| binary().
-endif.
-ifdef(maps_support).
-type json_value() :: list(json_value())
| list({binary() | atom(), json_value()}) | [{},...]
| {with_tail, json_value(), binary()}
@ -67,19 +54,11 @@
| integer()
| float()
| binary().
-endif.
-spec to_term(Source::binary(), Config::config()) -> json_value().
-ifdef(maps_always).
to_term(Source, Config) when is_list(Config) ->
(jsx:decoder(?MODULE, [return_maps] ++ Config, jsx_config:extract_config(Config)))(Source).
-endif.
-ifndef(maps_always).
to_term(Source, Config) when is_list(Config) ->
(jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source).
-endif.
parse_config(Config) -> parse_config(Config, #config{}).
@ -166,41 +145,6 @@ format_key(Key, Config) ->
start_term(Config) when is_list(Config) -> {[], parse_config(Config)}.
-ifndef(maps_support).
%% allocate a new object on top of the stack
start_object({Stack, Config}) -> {[{object, []}] ++ Stack, Config}.
%% allocate a new array on top of the stack
start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
%% finish an object or array and insert it into the parent object if it exists or
%% return it if it is the root object
finish({[{object, []}], Config}) -> {[{}], Config};
finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
finish(_) -> erlang:error(badarg).
%% insert a value when there's no parent object or array
insert(Value, {[], Config}) -> {Value, Config};
%% insert a key or value into an object or array, autodetects the 'right' thing
insert(Key, {[{object, Pairs}|Rest], Config}) ->
{[{object, Key, Pairs}] ++ Rest, Config};
insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
{[{object, [{Key, Value}] ++ Pairs}] ++ Rest, Config};
insert(Value, {[{array, Values}|Rest], Config}) ->
{[{array, [Value] ++ Values}] ++ Rest, Config};
insert(_, _) -> erlang:error(badarg).
-endif.
-ifdef(maps_support).
%% allocate a new object on top of the stack
start_object({Stack, Config=#config{return_maps=true}}) ->
{[{object, #{}}] ++ Stack, Config};
@ -239,8 +183,6 @@ insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
insert(Value, {[{array, Values}|Rest], Config}) ->
{[{array, [Value] ++ Values}] ++ Rest, Config};
insert(_, _) -> erlang:error(badarg).
-endif.
get_key({[{object, Key, _}|_], _}) -> Key;
get_key(_) -> erlang:error(badarg).
@ -368,7 +310,6 @@ rep_manipulation_test_() ->
].
-ifdef(maps_support).
rep_manipulation_with_maps_test_() ->
[
{"allocate a new object on an empty stack", ?_assertEqual(
@ -420,10 +361,10 @@ return_maps_test_() ->
[
{"an empty map", ?_assertEqual(
#{},
jsx:decode(<<"{}">>, [return_maps])
jsx:decode(<<"{}">>, [])
)},
{"an empty map", ?_assertEqual(
[{}],
#{},
jsx:decode(<<"{}">>, [])
)},
{"an empty map", ?_assertEqual(
@ -432,18 +373,17 @@ return_maps_test_() ->
)},
{"a small map", ?_assertEqual(
#{<<"awesome">> => true, <<"library">> => <<"jsx">>},
jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [return_maps])
jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [])
)},
{"a recursive map", ?_assertEqual(
#{<<"key">> => #{<<"key">> => true}},
jsx:decode(<<"{\"key\": {\"key\": true}}">>, [return_maps])
jsx:decode(<<"{\"key\": {\"key\": true}}">>, [])
)},
{"a map inside a list", ?_assertEqual(
[#{}],
jsx:decode(<<"[{}]">>, [return_maps])
jsx:decode(<<"[{}]">>, [])
)}
].
-endif.
handle_event_test_() ->