01 — Getting Started

View Source

This guide takes you from installation to your first decoded value, selected fields, and lazy file stream. You can paste each Elixir example into iex -S mix.

Install SimdJson

Add simd_json to the dependencies in your mix.exs:

def deps do
  [
    {:simd_json, "~> 1.0.0"}
  ]
end

Fetch the package and compile your project:

mix deps.get
mix compile

On Ubuntu 24.04 x86-64, Mix downloads a checksum-verified native library. You do not need Zig, a C++ compiler, or a separate simdjson installation.

Start an IEx session and make sure everything works:

iex> SimdJson.decode(~s({"hello":"world"}))
{:ok, %{"hello" => "world"}}

If compilation reports an unsupported native target, see 06 — Deployment and Native Delivery.

Decode a complete value

Imagine that an HTTP response contains an account and its recent activity:

json = ~s({
  "account": {"id": 7, "name": "Northwind"},
  "events": ["signed_in", "exported_report"]
})

{:ok, data} = SimdJson.decode(json)

data["account"]["name"]
# => "Northwind"

decode/1 is the simplest choice when your application needs most or all of the JSON. It returns ordinary Elixir maps, lists, binaries, numbers, booleans, and nil.

Return only the fields you need

If you only need the account identifier and name, ask SimdJson for those fields directly:

{:ok, account} =
  SimdJson.select(json,
    id: ["account", "id"],
    name: ["account", "name"]
  )

account
# => %{id: 7, name: "Northwind"}

Each entry has an output key on the left and a path through the JSON on the right. Only the selected scalar values are returned to the BEAM.

Process a large file lazily

For a large file of newline-delimited events, stream rows instead of decoding the complete file. Create events.ndjson:

{"id":1,"kind":"signed_in","internal":{"trace":"a1"}}
{"id":2,"kind":"exported_report","internal":{"trace":"b2"}}
{"id":3,"kind":"signed_out","internal":{"trace":"c3"}}

Now project and consume just id and kind:

events =
  SimdJson.stream_file("events.ndjson",
    format: :ndjson,
    fields: [id: ["id"], kind: ["kind"]],
    batch_size: 500
  )

Enum.each(events, fn event ->
  IO.inspect(event, label: "event")
end)

The stream requests one bounded batch at a time. The native layer maps and parses the file, while Elixir receives only the projected rows.

Handle an error

Non-raising operations return a structured error:

case SimdJson.decode(~s({"broken":])) do
  {:ok, value} ->
    value

  {:error, %SimdJson.Error{reason: reason, byte_offset: offset}} ->
    IO.puts("JSON failed near byte #{offset}: #{reason}")
end

Errors contain stable metadata but never embed the JSON source. Lazy streams raise SimdJson.Error during enumeration because parsing happens as rows are requested.

Choose an API

  • Use decode/1 when you need the complete Elixir value.
  • Use select/2 when JSON is already a binary and you need a few scalars.
  • Use select_file/2 to select scalars without first reading the file into a BEAM binary.
  • Use stream_file/2 for row-oriented work, early stopping, and bounded parser and result-batch memory.

Next: 02 — Decoding JSON.