lib/util/fast_jason_encoder.ex

# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved.

use Croma

defmodule Antikythera.FastJasonEncoder do
  @moduledoc """
  This module converts structures to JSON at low load by avoiding protocol calls. Internally Jason is used.
  """

  alias Croma.Result, as: R
  alias Antikythera.Time

  defmodule Wrapper do
    defstruct item: nil
  end

  defimpl Jason.Encoder, for: Wrapper do
    def encode(%Wrapper{item: item}, opts) do
      Antikythera.FastJasonEncoder.encode(item, opts)
    end
  end

  defun encode(value :: any) :: R.t(String.t(), Jason.EncodeError.t() | Exception.t()) do
    Jason.encode(%Wrapper{item: value})
  end

  R.define_bang_version_of(encode: 1)

  defun encode(value :: any, opts :: Jason.Encode.opts()) :: iodata do
    [], _ ->
      "[]"

    list, opts when is_list(list) ->
      [?[ | encode_list(list, opts, false)]

    empty_map, _ when empty_map == %{} ->
      "{}"

    %MapSet{map: map}, opts ->
      encode(Map.keys(map), opts)

    %DateTime{} = value, _ ->
      [?", DateTime.to_iso8601(value), ?"]

    map, opts when is_map(map) ->
      [?{ | encode_map(Map.to_list(map), opts, false)]

    value, opts ->
      if Time.valid?(value) do
        [?", Time.to_iso_timestamp(value), ?"]
      else
        Jason.Encode.value(value, opts)
      end
  end

  defunp encode_map(value :: [tuple], opts :: Jason.Encode.opts(), comma :: boolean) :: iodata do
    [], _, _ ->
      '}'

    [{:__struct__, _v} | tail], opts, comma ->
      encode_map(tail, opts, comma)

    list, opts, true ->
      [?,, encode_map(list, opts, false)]

    [{k, v} | tail], opts, false ->
      [encode(k, opts), ?:, encode(v, opts) | encode_map(tail, opts, true)]
  end

  defunp encode_list(value :: list, opts :: Jason.Encode.opts(), comma :: boolean) :: iodata do
    [], _, _ -> ']'
    list, opts, true -> [?,, encode_list(list, opts, false)]
    [head | tail], opts, false -> [encode(head, opts) | encode_list(tail, opts, true)]
  end
end