defmodule ApiBrasil.Core.JSON do @moduledoc """ Codec JSON da SDK — sem dependência obrigatória. O codec é escolhido em tempo de execução, na primeira chamada, e fica memorizado em `:persistent_term`: 1. [`Jason`](https://hex.pm/packages/jason), se estiver no projeto; 2. `JSON`, nativo do Elixir 1.18+; 3. `:json`, nativo do Erlang/OTP 27+. Só é preciso adicionar o `:jason` ao projeto em versões anteriores a Elixir 1.18 / OTP 27. """ @typedoc "Termo compatível com JSON: mapa, lista, string, número, booleano ou `nil`." @type json :: map() | list() | String.t() | number() | boolean() | nil @doc """ Codifica um termo em JSON. Levanta se o termo não for serializável. """ @spec encode!(term()) :: binary() def encode!(term) do case codec() do {:module, module} -> apply(module, :encode!, [term]) :otp -> IO.iodata_to_binary(:json.encode(term)) end end @doc """ Decodifica uma string JSON. Objetos viram mapas com chaves binárias — o mesmo formato em todos os codecs suportados. """ @spec decode(binary()) :: {:ok, json()} | {:error, term()} def decode(binary) when is_binary(binary) do case codec() do {:module, module} -> apply(module, :decode, [binary]) :otp -> try do {:ok, :json.decode(binary)} rescue error -> {:error, error} catch _kind, reason -> {:error, reason} end end end @doc """ Informa qual codec está em uso — `Jason`, `JSON` ou `:json`. Útil para diagnosticar o ambiente. """ @spec codec_name() :: module() | :json def codec_name do case codec() do {:module, module} -> module :otp -> :json end end defp codec do case :persistent_term.get(__MODULE__, nil) do nil -> detected = detect() :persistent_term.put(__MODULE__, detected) detected detected -> detected end end defp detect do cond do Code.ensure_loaded?(Jason) -> {:module, Jason} Code.ensure_loaded?(JSON) -> {:module, JSON} Code.ensure_loaded?(:json) -> :otp true -> raise ArgumentError, no_codec_message() end end defp no_codec_message do """ Nenhum codec JSON disponível. Use Elixir 1.18+ (módulo JSON), Erlang/OTP 27+ (módulo :json) ou adicione o Jason ao seu projeto: {:jason, "~> 1.4"} """ end end