Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol protocols serializable.ex
Raw

lib/raxol/protocols/serializable.ex

defprotocol Raxol.Protocols.Serializable do
@moduledoc """
Protocol for serializing and deserializing data structures.
This protocol provides a unified interface for converting data structures
to and from various formats like JSON, TOML, and binary formats.
## Examples
defimpl Raxol.Protocols.Serializable, for: MyStruct do
def serialize(data, :json) do
data
|> Map.from_struct()
|> Jason.encode!()
end
def deserialize(data, :json, MyStruct) do
data
|> Jason.decode!()
|> then(&struct(MyStruct, &1))
end
def serializable?(data, format) do
format in [:json, :toml]
end
end
"""
@type format :: :json | :toml | :binary | :erlang_term | atom()
@doc """
Serializes data to the specified format.
## Formats
* `:json` - JSON string using Jason
* `:toml` - TOML string
* `:binary` - Binary format using :erlang.term_to_binary
* `:erlang_term` - Erlang external term format
## Returns
A string or binary containing the serialized data, or `{:error, reason}`.
"""
@spec serialize(t, format()) :: binary() | {:error, term()}
def serialize(data, format \\ :json)
@doc """
Checks if the data can be serialized to the given format.
## Returns
`true` if the data can be serialized to the format, `false` otherwise.
"""
@spec serializable?(t, format()) :: boolean()
def serializable?(data, format)
end
# Implementation for maps (most common case)
defimpl Raxol.Protocols.Serializable, for: Map do
def serialize(map, :json) do
serializable_map = filter_serializable_for_json(map)
case Jason.encode(serializable_map) do
{:ok, json} -> json
{:error, reason} -> {:error, reason}
end
end
def serialize(_map, :toml) do
# The toml library only supports decoding, not encoding
# For encoding, we would need a different library like tomlex
{:error, :toml_encoding_not_supported}
end
def serialize(map, :binary) do
:erlang.term_to_binary(map)
end
def serialize(map, :erlang_term) do
:erlang.term_to_binary(map, [:compressed])
end
def serialize(_map, format) do
{:error, {:unsupported_format, format}}
end
def serializable?(_map, format) do
format in [:json, :toml, :binary, :erlang_term]
end
@spec filter_serializable_for_json(map()) :: map()
defp filter_serializable_for_json(map) when is_map(map) do
Enum.into(map, %{}, fn
{key, value} when is_function(value) ->
{key, "#Function<#{inspect(value)}>"}
{key, value} when is_map(value) ->
{key, filter_serializable_for_json(value)}
{key, value} ->
{key, value}
end)
end
@spec filter_serializable_for_json(any()) :: any()
defp filter_serializable_for_json(value), do: value
end
# Implementation for lists
defimpl Raxol.Protocols.Serializable, for: List do
def serialize(list, :json) do
case Jason.encode(list) do
{:ok, json} -> json
{:error, reason} -> {:error, reason}
end
end
def serialize(list, :binary) do
:erlang.term_to_binary(list)
end
def serialize(list, :erlang_term) do
:erlang.term_to_binary(list, [:compressed])
end
def serialize(_list, format) do
{:error, {:unsupported_format, format}}
end
def serializable?(_list, format) do
format in [:json, :binary, :erlang_term]
end
end
# Implementation for strings
defimpl Raxol.Protocols.Serializable, for: BitString do
def serialize(string, :json) do
case Jason.encode(string) do
{:ok, json} -> json
{:error, reason} -> {:error, reason}
end
end
def serialize(string, :binary) do
string
end
def serialize(string, :erlang_term) do
:erlang.term_to_binary(string, [:compressed])
end
def serialize(_string, format) do
{:error, {:unsupported_format, format}}
end
def serializable?(_string, format) do
format in [:json, :binary, :erlang_term]
end
end
# Implementation for atoms
defimpl Raxol.Protocols.Serializable, for: Atom do
def serialize(nil, :json), do: "null"
def serialize(true, :json), do: "true"
def serialize(false, :json), do: "false"
def serialize(atom, :json) do
case Jason.encode(to_string(atom)) do
{:ok, json} -> json
{:error, reason} -> {:error, reason}
end
end
def serialize(atom, :binary) do
:erlang.term_to_binary(atom)
end
def serialize(atom, :erlang_term) do
:erlang.term_to_binary(atom, [:compressed])
end
def serialize(_atom, format) do
{:error, {:unsupported_format, format}}
end
def serializable?(_atom, format) do
format in [:json, :binary, :erlang_term]
end
end
# Implementation for numbers
defimpl Raxol.Protocols.Serializable, for: Integer do
def serialize(integer, :json) do
to_string(integer)
end
def serialize(integer, :binary) do
:erlang.term_to_binary(integer)
end
def serialize(integer, :erlang_term) do
:erlang.term_to_binary(integer, [:compressed])
end
def serialize(_integer, format) do
{:error, {:unsupported_format, format}}
end
def serializable?(_integer, format) do
format in [:json, :binary, :erlang_term]
end
end
defimpl Raxol.Protocols.Serializable, for: Float do
def serialize(float, :json) do
to_string(float)
end
def serialize(float, :binary) do
:erlang.term_to_binary(float)
end
def serialize(float, :erlang_term) do
:erlang.term_to_binary(float, [:compressed])
end
def serialize(_float, format) do
{:error, {:unsupported_format, format}}
end
def serializable?(_float, format) do
format in [:json, :binary, :erlang_term]
end
end