Eyeon (eyeon v0.2.0)

Copy Markdown View Source

An Elixir library for encoding and decoding Amazon Ion data.

Eyeon supports both Ion text (.ion) and Ion binary (.10n) formats. It auto-detects binary input on decode, and you choose the output format on encode via the :encoding option.

Quick start

# Decode Ion text
{:ok, value} = Eyeon.decode("{name: \"Alice\", scores: [98, 87, 95]}")
value["name"]  #=> "Alice"

# Encode to Ion text (default)
{:ok, ion} = Eyeon.encode(%{"greeting" => "hello"})

# Encode to Ion binary
{:ok, bin} = Eyeon.encode(42, %{encoding: :binary})

Reading and writing .ion files

# Write Ion text to a file
data = %{"name" => "Alice", "age" => 30, "active" => true}
File.write!("user.ion", Eyeon.encode!(data))

# Read it back
Eyeon.decode!(File.read!("user.ion"))
#=> %{"active" => true, "age" => 30, "name" => "Alice"}

Reading and writing .10n (binary Ion) files

# Write Ion binary to a file
data = [1, 2, 3]
File.write!("data.10n", Eyeon.encode!(data, %{encoding: :binary}))

# Read it back — binary format is auto-detected
Eyeon.decode!(File.read!("data.10n"))
#=> [1, 2, 3]

Ion types

Most Ion types map directly to Elixir types. Types without a native Elixir equivalent use tagged tuples:

Ion typeElixir representation
nullnil
booltrue / false
intinteger
floatfloat, :nan, :infinity, :neg_infinity
decimalDecimal.t()
timestampDateTime.t(), NaiveDateTime.t(), or Date.t()
stringbinary string
symbol{:symbol, name}
blob{:blob, binary}
clob{:clob, binary}
listlist
structmap with string keys
sexp{:sexp, list}
annotation{:annotated, {:symbol, name}, value}
typed null{:null, type} e.g. {:null, :int}

Timestamps

By default (:timestamp option set to :native), Ion timestamps are decoded to native Elixir date/time types:

Ion precisionOffset known?Elixir type
Full (date + time)Yes (Z, +HH:MM, -HH:MM)DateTime.t()
Full (date + time)Unknown (-00:00)NaiveDateTime.t()
Date only (YYYY-MM-DD)n/aDate.t()
Year-month (YYYY-MMT)n/aDate.t() (day set to 1)
Year only (YYYYT)n/aDate.t() (month and day set to 1)

Fractional seconds beyond microseconds (6 digits) are truncated, since Elixir's DateTime caps at microsecond precision.

Pass %{timestamp: :raw} to decode/2 to get {:timestamp, string} tuples instead, which preserves the original Ion precision and offset semantics.

# Default: native types
Eyeon.decode!("2024-01-15T12:30:00Z")
#=> ~U[2024-01-15 12:30:00Z]

# Raw mode: tagged strings
Eyeon.decode!("2024-01-15T12:30:00Z", %{timestamp: :raw})
#=> {:timestamp, "2024-01-15T12:30:00Z"}

The encoder accepts DateTime, NaiveDateTime, Date, and {:timestamp, string} values in both text and binary modes.

Summary

Functions

Decodes Ion text or binary data to an Elixir value.

Decodes Ion text or binary data to an Elixir value, raising on error.

Encodes an Elixir value to Ion format.

Encodes an Elixir value to Ion format, raising on error.

Functions

decode(iodata, options \\ %{})

@spec decode(iodata(), map()) :: {:ok, any()} | {:error, any()}

Decodes Ion text or binary data to an Elixir value.

Binary Ion (starting with the Ion Version Marker 0xE0 0x01 0x00 0xEA) is detected automatically; all other input is treated as Ion text.

Returns {:ok, value} on success or {:error, reason} on failure.

Options

  • :timestamp:native (default) or :raw. When :native, timestamps are converted to DateTime, NaiveDateTime, or Date. When :raw, timestamps are returned as {:timestamp, string} tuples preserving the original Ion precision.
  • :shared_symbol_tables — list of Eyeon.SharedSymbolTable structs for resolving shared symbol table imports

Examples

iex> Eyeon.decode("42")
{:ok, 42}

iex> Eyeon.decode("true")
{:ok, true}

iex> Eyeon.decode("[1, 2, 3]")
{:ok, [1, 2, 3]}

iex> Eyeon.decode(~s({name: "Alice", age: 30}))
{:ok, %{"age" => 30, "name" => "Alice"}}

iex> Eyeon.decode("2023-01-15T10:30:00Z")
{:ok, ~U[2023-01-15 10:30:00Z]}

iex> Eyeon.decode("2023-01-15T10:30:00Z", %{timestamp: :raw})
{:ok, {:timestamp, "2023-01-15T10:30:00Z"}}

iex> Eyeon.decode("null.int")
{:ok, {:null, :int}}

iex> match?({:error, _}, Eyeon.decode("invalid ion !!!"))
true

decode!(iodata, options \\ %{})

@spec decode!(iodata(), map()) :: any()

Decodes Ion text or binary data to an Elixir value, raising on error.

Accepts the same options as decode/2.

Examples

iex> Eyeon.decode!("42")
42

iex> Eyeon.decode!("[1, 2, 3]")
[1, 2, 3]

iex> Eyeon.decode!(~s({name: "Alice", age: 30}))
%{"age" => 30, "name" => "Alice"}

iex> Eyeon.decode!("hello")
{:symbol, "hello"}

iex> Eyeon.decode!(<<0xE0, 0x01, 0x00, 0xEA, 0x21, 0x01>>)
1

iex> Eyeon.decode!("2024-01-15T10:30:00Z")
~U[2024-01-15 10:30:00Z]

iex> Eyeon.decode!("2024-01-15")
~D[2024-01-15]

encode(value, options \\ %{})

@spec encode(any(), map()) :: {:ok, binary()} | {:error, Exception.t()}

Encodes an Elixir value to Ion format.

Returns {:ok, iodata} on success or {:error, reason} on failure.

Options

  • :encoding:text (default) or :binary

Examples

iex> Eyeon.encode(42)
{:ok, "42"}

iex> Eyeon.encode(true)
{:ok, "true"}

iex> Eyeon.encode([1, 2, 3])
{:ok, "[1,2,3]"}

iex> {:ok, bin} = Eyeon.encode(42, %{encoding: :binary})
iex> is_binary(bin)
true

encode!(value, options \\ %{})

@spec encode!(any(), map()) :: binary()

Encodes an Elixir value to Ion format, raising on error.

Examples

iex> Eyeon.encode!("hello")
~s("hello")

iex> Eyeon.encode!(nil)
"null"

iex> Eyeon.encode!(%{"key" => "value"})
~s({key:"value"})

iex> Eyeon.encode!({:symbol, "foo"})
"foo"

iex> Eyeon.encode!({:blob, "data"})
"{{ZGF0YQ==}}"