Skip to content

Ordered JSON (OJSON) - deterministic or stable serialization

License

Notifications You must be signed in to change notification settings

potatosalad/erlang-ojson

Repository files navigation

Ordered JSON (OJSON)

Build Status Hex.pm

Ordered JSON (JSON) is a deterministic or stable JSON encoder and decoder for Erlang and Elixir.

OJSON is similar to projects like fast-stable-stringify and json-stable-stringify which allows for deterministic serialization of object key order.

Before you say, "JSON is unordered!", the initial use-case for this library is with cryptographic operations, which rely on stable input for authentication and verification in serialized form. The ordering only applies to the encoding/serialization operations.

Initially based on devinus/poison, but rewritten for use as a dependency for Erlang or Elixir projects.

Installation

If using Hex, the package can be installed by adding ojson to your list of dependencies in mix.exs:

def deps do
  [{:ojson, "~> 1.0.0"}]
end

If using rebar3, the package can be installed by adding ojson to you list of dependencies in rebar.config:

{deps, [
  {ojson, ".*", {git, "git://github.com/potatosalad/erlang-ojson.git", {branch, "master"}}}
]}.

The HexDocs can be found at https://hexdocs.pm/ojson.

Usage

Basic Usage

Elixir

iex> OJSON.encode(%{"z" => 1, "a" => 2})
{:ok, "{\"a\":2,\"z\":1}"}

iex> OJSON.encode!(%{"z" => 1, "a" => 2})
"{\"a\":2,\"z\":1}"

iex> OJSON.encode_to_iodata(%{"z" => 1, "a" => 2})
{:ok, [123, [[34, ["a"], 34], 58, "2"], [[44, [34, ["z"], 34], 58, "1"]], 125]}

iex> OJSON.encode_to_iodata!(%{"z" => 1, "a" => 2})
[123, [[34, ["a"], 34], 58, "2"], [[44, [34, ["z"], 34], 58, "1"]], 125]

iex> OJSON.decode("{\"a\":2,\"z\":1}")
{:ok, %{"a" => 2, "z" => 1}}

iex> OJSON.decode!("{\"a\":2,\"z\":1}")
%{"a" => 2, "z" => 1}

Erlang

ojson:encode(#{ <<"z">> => 1, <<"a">> => 2 }).
% {ok, <<"{\"a\":2,\"z\":1}">>}

ojson:'encode!'(#{ <<"z">> => 1, <<"a">> => 2 }).
% <<"{\"a\":2,\"z\":1}">>

ojson:encode_to_iodata(#{ <<"z">> => 1, <<"a">> => 2 }).
% {ok, [123,[[34,[<<"a">>],34],58,<<"2">>],[[44,[34,[<<"z">>],34],58,<<"1">>]],125]}

ojson:'encode_to_iodata!'(#{ <<"z">> => 1, <<"a">> => 2 }).
% [123,[[34,[<<"a">>],34],58,<<"2">>],[[44,[34,[<<"z">>],34],58,<<"1">>]],125]

ojson:decode(<<"{\"a\":2,\"z\":1}">>).
% {ok, #{<<"a">> => 2, <<"z">> => 1}}

ojson:'decode!'(<<"{\"a\":2,\"z\":1}">>).
% #{<<"a">> => 2, <<"z">> => 1}
Deterministic Example

Elixir

# Let's first generate a map with 32 key/value pairs:
iex> map = for key <- :lists.seq(0, 32), into: %{}, do: {String.pad_leading(:erlang.integer_to_binary(key, 16), 2, "0"), 0}
%{"10" => 0, "0E" => 0, "14" => 0, "12" => 0, "16" => 0, "07" => 0, "1C" => 0,
  "06" => 0, "19" => 0, "13" => 0, "04" => 0, "1E" => 0, "1F" => 0, "11" => 0,
  "15" => 0, "17" => 0, "1A" => 0, "0A" => 0, "18" => 0, "0C" => 0, "00" => 0,
  "0B" => 0, "09" => 0, "02" => 0, "0D" => 0, "1D" => 0, "20" => 0, "03" => 0,
  "0F" => 0, "1B" => 0, "01" => 0, "05" => 0, "08" => 0}

# Notice that the key/value pairs are unordered.
# Now let's encode our unordered map into an ordered JSON string:
iex> OJSON.encode!(map)
"{\"00\":0,\"01\":0,\"02\":0,\"03\":0,\"04\":0,\"05\":0,\"06\":0,\"07\":0,\"08\":0,\"09\":0,\"0A\":0,\"0B\":0,\"0C\":0,\"0D\":0,\"0E\":0,\"0F\":0,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":0,\"16\":0,\"17\":0,\"18\":0,\"19\":0,\"1A\":0,\"1B\":0,\"1C\":0,\"1D\":0,\"1E\":0,\"1F\":0,\"20\":0}"

Erlang

%% Let's first generate a map with 32 key/value pairs:
Map = maps:from_list([{case erlang:integer_to_binary(I, 16) of <<B>> -> <<$0,B>>; B -> B end, 0} || I <- lists:seq(0, 32)]).
% #{<<"10">> => 0,<<"0E">> => 0,<<"14">> => 0,<<"12">> => 0,
%   <<"16">> => 0,<<"07">> => 0,<<"1C">> => 0,<<"06">> => 0,
%   <<"19">> => 0,<<"13">> => 0,<<"04">> => 0,<<"1E">> => 0,
%   <<"1F">> => 0,<<"11">> => 0,<<"15">> => 0,<<"17">> => 0,
%   <<"1A">> => 0,<<"0A">> => 0,<<"18">> => 0,<<"0C">> => 0,
%   <<"00">> => 0,<<"0B">> => 0,<<"09">> => 0,<<"02">> => 0,
%   <<"0D">> => 0,<<"1D">> => 0,<<"20">> => 0,<<"03">> => 0,
%   <<"0F">> => 0,<<"1B">> => 0,<<"01">> => 0,<<"05">> => 0,
%   <<"08">> => 0}

%% Notice that the key/value pairs are unordered.
%% Now let's encode our unordered map into an ordered JSON string:
ojson:'encode!'(Map).
% <<"{\"00\":0,\"01\":0,\"02\":0,\"03\":0,\"04\":0,\"05\":0,\"06\":0,\"07\":0,\"08\":0,\"09\":0,\"0A\":0,\"0B\":0,\"0C\":0,\"0D\":0,\"0E\":0,\"0F\":0,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":0,\"16\":0,\"17\":0,\"18\":0,\"19\":0,\"1A\":0,\"1B\":0,\"1C\":0,\"1D\":0,\"1E\":0,\"1F\":0,\"20\":0}">>