Current section

Files

Jump to
exjsx lib jsx.ex
Raw

lib/jsx.ex

import :lists, only: [flatten: 1]
defmodule JSX do
def encode!(term, opts \\ []) do
parser_opts = :jsx_config.extract_config(opts ++ [:escaped_strings])
parser(:jsx_to_json, opts, parser_opts).(flatten(JSX.Encoder.json(term) ++ [:end_json]))
end
def encode(term, opts \\ []) do
{ :ok, encode!(term, opts) }
rescue
ArgumentError -> { :error, :badarg }
end
def decode!(json, opts \\ []) do
decoder_opts = :jsx_config.extract_config(opts)
case decoder(JSX.Decoder, opts, decoder_opts).(json) do
{ :incomplete, _ } -> raise ArgumentError
result -> result
end
end
def decode(term, opts \\ []) do
{ :ok, decode!(term, opts) }
rescue
ArgumentError -> { :error, :badarg }
end
def format!(json, opts \\ []) do
case :jsx.format(json, opts) do
{ :incomplete, _ } -> raise ArgumentError
result -> result
end
end
def format(json, opts \\ []) do
{ :ok, format!(json, opts) }
rescue
ArgumentError -> { :error, :badarg }
end
def minify!(json), do: format!(json, [space: 0, indent: 0])
def minify(json) do
{ :ok, minify!(json) }
rescue
ArgumentError -> { :error, :badarg }
end
def prettify!(json), do: format!(json, [space: 1, indent: 2])
def prettify(json) do
{ :ok, prettify!(json) }
rescue
ArgumentError -> { :error, :badarg }
end
def is_json?(json, opts \\ []) do
case :jsx.is_json(json, opts) do
{ :incomplete, _ } -> false
result -> result
end
rescue
_ -> false
end
def is_term?(term, opts \\ []) do
parser_opts = :jsx_config.extract_config(opts)
parser(:jsx_verify, opts, parser_opts).(flatten(JSX.Encoder.json(term) ++ [:end_json]))
rescue
_ -> false
end
def encoder(handler, initialstate, opts) do
:jsx.encoder(handler, initialstate, opts)
end
def decoder(handler, initialstate, opts) do
:jsx.decoder(handler, initialstate, opts)
end
def parser(handler, initialstate, opts) do
:jsx.parser(handler, initialstate, opts)
end
end
defmodule JSX.Decoder do
def init(opts) do
:jsx_to_term.init(opts)
end
def handle_event(:end_json, state), do: get_value(state)
def handle_event(:start_object, state), do: start_object(state)
def handle_event(:end_object, state), do: finish(state)
def handle_event(:start_array, state), do: start_array(state)
def handle_event(:end_array, state), do: finish(state)
def handle_event({ :key, key }, { _, config } = state), do: insert(format_key(key, config), state)
def handle_event({ :literal, :null }, state), do: insert(:nil, state)
def handle_event({ _, event }, state), do: insert(event, state)
defp format_key(key, { _, :binary }), do: key
defp format_key(key, { _, :atom }), do: :erlang.binary_to_atom(key, :utf8)
defp format_key(key, { _, :existing_atom }), do: :erlang.binary_to_existing_atom(key, :utf8)
defp format_key(key, { _, :attempt_atom }) do
:erlang.binary_to_existing_atom(key, :utf8)
rescue
ArgumentError -> key
end
defp start_object({ stack, config }), do: { [{ :object, %{} }] ++ stack, config }
defp start_array({ stack, config }), do: { [{ :array, [] }] ++ stack, config }
defp finish({ [{ :object, emptyMap }], config }) when is_map(emptyMap) and map_size(emptyMap) < 1 do
{ %{}, config }
end
defp finish({ [{ :object, emptyMap }|rest], config }) when is_map(emptyMap) and map_size(emptyMap) < 1 do
insert(%{}, { rest, config })
end
defp finish({ [{ :object, pairs }], config }), do: { pairs, config }
defp finish({ [{ :object, pairs}|rest], config }), do: insert(pairs, { rest, config })
defp finish({ [{ :array, values }], config }), do: { Enum.reverse(values), config }
defp finish({ [{ :array, values}|rest], config}), do: insert(Enum.reverse(values), { rest, config })
defp finish(_), do: raise ArgumentError
defp insert(value, { [], config }), do: { value, config }
defp insert(key, { [{ :object, pairs }|rest], config }) do
{ [{ :object, key, pairs }] ++ rest, config }
end
defp insert(value, { [{ :object, key, pairs }|rest], config }) do
{ [{ :object, Map.put(pairs, key, value) }] ++ rest, config }
end
defp insert(value, { [{ :array, values}|rest], config }) do
{ [{ :array, [value] ++ values}] ++ rest, config }
end
defp insert(_, _), do: raise ArgumentError
defp get_value({ value, _config }), do: value
defp get_value(_), do: raise ArgumentError
end
defprotocol JSX.Encoder do
@fallback_to_any true
def json(term)
end
defimpl JSX.Encoder, for: Map do
def json(map) do
[:start_object] ++ flatten(for key <- Map.keys(map) do
[encode_key(key)] ++ JSX.Encoder.json(Map.get(map, key))
end) ++ [:end_object]
end
defp encode_key(key)
when is_binary(key) or is_integer(key) do
key
end
defp encode_key(key)
when is_atom(key) do
:erlang.atom_to_binary(key, :utf8)
end
end
defimpl JSX.Encoder, for: List do
def json([]), do: [:start_array, :end_array]
def json([{}]), do: [:start_object, :end_object]
def json([{ _, _ }|_] = list) do
[:start_object] ++ flatten(for {key, value} <- list do
[encode_key(key)] ++ JSX.Encoder.json(value)
end) ++ [:end_object]
end
def json(list) do
[:start_array] ++ flatten(for term <- list, do: JSX.Encoder.json(term)) ++ [:end_array]
end
defp encode_key(key)
when is_binary(key) or is_integer(key) do
key
end
defp encode_key(key)
when is_atom(key) do
:erlang.atom_to_binary(key, :utf8)
end
end
defimpl JSX.Encoder, for: HashDict do
def json(dict), do: JSX.Encoder.json(HashDict.to_list(dict))
end
defimpl JSX.Encoder, for: Atom do
def json(nil), do: [:null]
def json(true), do: [true]
def json(false), do: [false]
def json(atom), do: [:erlang.atom_to_binary(atom, :utf8)]
end
defimpl JSX.Encoder, for: [Integer, Float, BitString] do
def json(value), do: [value]
end
defimpl JSX.Encoder, for: [Tuple, PID, Port, Reference, Function, Any] do
def json(map) when is_map(map), do: JSX.Encoder.Map.json(Map.delete(map, :__struct__))
def json(_), do: raise ArgumentError
end