diff --git a/lib/jiffy.ex b/lib/jiffy.ex new file mode 100644 index 00000000..2202a061 --- /dev/null +++ b/lib/jiffy.ex @@ -0,0 +1,118 @@ +defmodule Jiffy do + @moduledoc """ + A JSON parser as a NIF. + + # Data Format + + | Elixir | -> JSON | -> Elixir | + | ---------------------------- | ---------------- | ------- | + | `nil` | `null` | `nil` | + | `true` | `true` | `true` | + | `false` | `false` | `false` | + | `'hi'` | `[104, 105]` | `[104, 105]` | + | `"hi"` | `"hi"` | `"hi"` | + | `:hi` | `"hi"` | `"hi"` | + | `1` | `1` | `1` | + | `1.25` | `1.25` | `1.25` | + | `[]` | `[]` | `[]` | + | `[true, 1.0]` | `[true, 1.0]` | `[true, 1.0]` | + | `%{"foo" => "bar"}` | `{"foo": "bar"}` | `%{"foo" => "bar"}` | + | `%{foo: "bar"}` | `{"foo": "bar"}` | `%{"foo" => "bar"}` | + """ + @encode_options [:use_nil] + @decode_options [:use_nil, :return_maps] + + @doc """ + Encode a value to JSON. + + # Unsupported structures + + * Encoding Keywords currently is not supported. + * Encoding DateTime, Date or other Date-related Elixir structures will return + `{:error, {:invalid_ejson, any}}`. If you want to encode them - you need to cast + them to string before encoding. + + # Options + + * `:uescape` - Escapes UTF-8 sequences to produce a 7-bit clean output. + * `:pretty` - Produce JSON using two-space indentation. + * `:force_utf8` - Force strings to encode as UTF-8 by fixing broken + surrogate pairs and/or using the replacement character to remove + broken UTF-8 sequences in data. + * `:escape_forward_slashes` - Escapes the `/` character which can be + useful when encoding URLs in some cases. + * `{:bytes_per_red, n}` - Refer to the `decode/2` options. + * `{:bytes_per_iter, n}` - Refer to the `decode/2` options. + + # Examples + + iex> Jiffy.encode([1, 2, 3]) + {:ok, "[1,2,3]"} + """ + @spec encode(any, opts :: :jiffy.encode_option()) :: {:ok, any()} | {:error, any()} + def encode(data, opts \\ []) do + {:ok, encode!(data, opts)} + catch + {:error, reason} -> {:error, reason} + end + + @doc """ + Encode a value to JSON, raises an exception on error. + + For list of options see `encode/2`. + + # Examples + + iex> Jiffy.encode!([1, 2, 3]) + "[1,2,3]" + """ + @spec encode!(any, opts :: :jiffy.encode_option()) :: {:ok, any()} | no_return() + def encode!(data, opts \\ []) do + data + |> :jiffy.encode(@encode_options ++ opts) + |> :erlang.iolist_to_binary() + end + + @doc """ + Decode JSON to a value. + + # Options + + * `:return_trailer` - If any non-whitespace is found after the first + JSON term is decoded the return value of decode/2 becomes + `{:has_trailer, first_term, rest_iodata}`. This is useful to + decode multiple terms in a single binary. + * `{:bytes_per_red, n}` where `n` >= 0 - This controls the number of + bytes that Jiffy will process as an equivalent to a reduction. Each + 20 reductions we consume 1% of our allocated time slice for the current + process. When the Erlang VM indicates we need to return from the NIF. + * `{:bytes_per_iter, n}` where `n` >= 0 - Backwards compatible option + that is converted into the `bytes_per_red` value. + + # Examples + + iex> Jiffy.decode("[1,2,3]") + {:ok, [1, 2, 3]} + """ + @spec decode(String.t(), opts :: :jiffy.decode_option()) :: {:ok, any()} | {:error, atom()} + def decode(data, opts \\ []) do + {:ok, decode!(data, opts)} + catch + {:error, reason} -> {:error, reason} + end + + @doc """ + Decode JSON to a value, raises an exception on error. + + For list of options see `decode/2`. + + # Examples + + iex> Jiffy.decode!("[1,2,3]") + [1, 2, 3] + """ + @spec decode!(String.t(), opts :: :jiffy.decode_option()) :: any() | no_return() + def decode!(data, opts \\ []) do + :jiffy.decode(data, @decode_options ++ opts) + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 00000000..d8fcdf97 --- /dev/null +++ b/mix.exs @@ -0,0 +1,39 @@ +defmodule Jiffy.Mixfile do + use Mix.Project + + @version "0.14.11" + + def project do + [app: :jiffy, + description: "A JSON parser as a NIF.", + package: package(), + version: @version, + compilers: [:elixir_make] ++ Mix.compilers(), + elixir: "~> 1.4", + build_embedded: Mix.env == :prod, + start_permanent: Mix.env == :prod, + deps: deps(), + docs: [source_ref: "v#\{@version\}", main: "readme", extras: ["README.md"]], + aliases: aliases()] + end + + def application do + [extra_applications: [:logger]] + end + + defp package do + [contributors: ["davisp"], + maintainers: ["davisp"], + licenses: ["MIT", "BSD"], + links: %{github: "https://github.com/davisp/jiffy"}, + files: ~w(c_src src lib LICENSE mix.exs README.md)] + end + + defp deps do + [{:elixir_make, "~> 0.4", runtime: false}] + end + + defp aliases do + [clean: ["clean", "clean.make"]] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 00000000..254869f4 --- /dev/null +++ b/mix.lock @@ -0,0 +1,3 @@ +%{ + "elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [:mix], [], "hexpm", "4549183795460c581fd82010d10862e46bcf796e2039d16c255bad3e408f435d"}, +}