Packages

Elixir implementation of the Agent Client Protocol (ACP) for communication between code editors and AI coding agents. Includes schema types, JSON-RPC primitives, behaviours, and connection management.

Current section

Files

Jump to
agent_client_protocol lib acp json_helpers.ex
Raw

lib/acp/json_helpers.ex

defmodule ACP.JSONHelpers do
@moduledoc "JSON serialization helpers for ACP types."
@doc "Convert an atom or string from snake_case to camelCase."
def to_camel_case(key) when is_atom(key), do: to_camel_case(Atom.to_string(key))
def to_camel_case(key) when is_binary(key) do
case String.split(key, "_") do
[first | rest] -> first <> Enum.map_join(rest, &String.capitalize/1)
_ -> key
end
end
@doc "Convert a string from camelCase to snake_case."
def to_snake_case(key) when is_binary(key) do
key
|> String.replace(~r/([A-Z])/, "_\\1")
|> String.downcase()
|> String.trim_leading("_")
end
@doc "Convert a map with snake_case atom keys to camelCase string keys, dropping nil values."
def to_json_map(map, opts \\ []) when is_map(map) do
rename = Keyword.get(opts, :rename, %{})
map
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
|> Enum.reject(fn {_k, v} -> v == [] and not Keyword.get(opts, :keep_empty_lists, false) end)
|> Enum.map(fn {k, v} ->
json_key = Map.get(rename, k, to_camel_case(k))
{json_key, v}
end)
|> Map.new()
end
@doc "Convert a map with camelCase string keys to a keyword list or map with snake_case atom keys."
def from_json_map(map, opts \\ []) when is_map(map) do
rename = Keyword.get(opts, :rename, %{})
# Build reverse rename map: json_key -> atom_key
reverse = Map.new(rename, fn {atom_key, json_key} -> {json_key, atom_key} end)
map
|> Enum.map(fn {k, v} ->
atom_key = Map.get(reverse, k, String.to_atom(to_snake_case(k)))
{atom_key, v}
end)
|> Map.new()
end
@doc "Encode a value to JSON, dropping nil fields from structs."
def encode_struct(struct, fields, opts \\ []) do
rename = Keyword.get(opts, :rename, %{})
keep_empty = Keyword.get(opts, :keep_empty_lists, false)
struct
|> Map.take(fields)
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
|> Enum.reject(fn {_k, v} -> v == [] and not keep_empty end)
|> Enum.map(fn {k, v} ->
json_key = Map.get(rename, k, to_camel_case(k))
{json_key, v}
end)
|> Map.new()
end
end