# Jason for Elixir: JSON Encoding and Decoding

> Jason is a pure Elixir JSON parser and generator used by Elixir applications that need standards-compliant decoding, encoding, and integration with Postgrex, Ecto, Plug, Phoenix, and Absinthe. Its main tradeoff is strict JSON behavior paired with less convenience for arbitrary structs and some collection types.

**michalmuskala/jason** — A blazing fast JSON parser and generator in pure Elixir.

- Repository: https://github.com/michalmuskala/jason
- Stars: 1,680 · Forks: 181
- Language: Elixir
- License: NOASSERTION
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/michalmuskala-jason

## Why Jason Fits the Elixir JSON Boundary

Jason addresses the need to move JSON between an Elixir application and external systems without introducing a C component. It provides both a parser and a generator, so the same library covers incoming API payloads, configuration documents, database-related JSON, and outgoing responses. The README states that the parser and generator conform to RFC 8259 and ECMA 404, and that the parser is tested with JSONTestSuite. That standards focus is more useful to an engineer than a broad compatibility claim because it makes invalid JSON handling part of the library's stated contract.

The target audience is Elixir developers working with JSON libraries or applications in the surrounding ecosystem. Postgrex 0.14.0 and later, Ecto 3.0.0 and later, and Phoenix 1.4.0 and later use Jason by default, according to the README. This matters because many teams can inherit Jason as part of a wider Elixir stack rather than selecting it as an isolated encoding dependency. Absinthe users must configure it explicitly with the `:json_codec` option.

Jason is not a general-purpose serializer for arbitrary Elixir values. It follows a narrower model: decoded JSON becomes ordinary Elixir maps, lists, strings, numbers, booleans, and null, while structs need an encoder path. The repository metadata also labels its licence as `Other`, with a custom licence that GitHub cannot classify, so licence review remains a separate adoption check.

## How Jason Parses and Generates JSON

Jason exposes the decoding and generation operations through `Jason.decode!` and `Jason.encode!`. The README's decode example takes a JSON object and returns an Elixir map whose string keys are represented as strings. Its encode example takes an Elixir map and returns a JSON object string. The mapping is direct and easy to inspect, which makes Jason suitable for code that already works with Elixir's built-in collection types.

The repository layout places implementation code under `lib/`, tests under `test/`, standards-oriented fixtures under `json_test_suite/`, formatting cases under `formatter_test_suite/`, and benchmark code under `bench/`. The README does not document the parser's internal state machine, tokenization strategy, or memory behavior in detail, so those implementation details cannot be inferred from the repository layout. Its architecture can be described conservatively as a pure Elixir library with separate parser and generator entry points.

`Jason.Fragment` provides a specific exception to the normal encoding path. It marks JSON that has already been encoded and avoids a decoding and encoding roundtrip. This is useful for cached JSON or JSON produced by another system, including a PostgreSQL aggregate such as `jsonb_agg`. The generator can therefore preserve an existing JSON fragment inside a larger encoded object.

The strictness choice has visible consequences. Jason rejects an unescaped newline inside a JSON string, while some permissive parsers may accept it. That behavior is appropriate for standards-focused processing, but it can cause compatibility problems when an upstream producer emits technically invalid JSON.

## Adding Jason and Running the First Conversion

Jason is distributed as the `jason` package for a Mix-based Elixir project. Add the dependency shown below to the project's `mix.exs`. The README uses the requirement `~> 1.4`, so this is the documented starting point rather than a new version constraint invented for this guide.

```elixir
def deps do
  [{:jason, "~> 1.4"}]
end
```

The README does not provide a separate dependency installation command such as `mix deps.get`, so no additional command is claimed here. Once the project's normal dependency workflow has resolved the package, the first useful check is encoding a map in an IEx session:

```elixir
iex(1)> Jason.encode!(%{"age" => 44, "name" => "Steve Irwin", "nationality" => "Australian"})
"{\"age\":44,\"name\":\"Steve Irwin\",\"nationality\":\"Australian\"}"
```

The reader should see one JSON object string containing the three fields. The inverse operation returns an Elixir map:

```elixir
iex(2)> Jason.decode!(~s({"age":44,"name":"Steve Irwin","nationality":"Australian"}))
%{"age" => 44, "name" => "Steve Irwin", "nationality" => "Australian"}
```

This is a practical first use because it verifies both directions without requiring a web server, database, or framework configuration. For an application, `decode!` is the concise form that raises on failure according to its naming, while the surrounding application still needs to decide how to handle malformed payloads at its API boundary.

## Encoder Derivation and the Limits of Automatic Struct Encoding

Jason does not provide built-in encoders for `MapSet`, `Range`, `Stream`, or arbitrary structs. The omission is deliberate and creates an explicit design boundary. For a project-owned struct, the README recommends deriving `Jason.Encoder` while selecting only the fields that should be emitted:

```elixir
@derive {Jason.Encoder, only: [....]}
defstruct # ...
```

The restricted field list is the safer documented form. The README also allows all fields with `@derive Jason.Encoder`, but cautions that this can expose private information when new fields are added. This is an operational maintenance concern for every application that changes its structs over time.

For unsupported collection types, the README suggests adding protocol implementations in the application. Its example converts each value to a list before encoding:

```elixir
defimpl Jason.Encoder, for: [MapSet, Range, Stream] do
  def encode(struct, opts) do
    Jason.Encode.list(Enum.to_list(struct), opts)
  end
end
```

For a struct owned by another project, `Protocol.derive/3` can supply an encoder with an explicit `only:` list, or derive the complete protocol when that is appropriate. The main limitation is that protocol work is necessary before these values can be passed to `Jason.encode!`. Teams that expect automatic serialization of arbitrary domain objects should account for this before choosing the library.

## Phoenix, Ecto, Postgrex, and Absinthe Integration

Jason's integration story is strongest when it is already part of the standard Elixir stack. The README states that Postgrex versions starting at 0.14.0 use Jason by default, Ecto versions starting at 3.0.0 use it by default, and Phoenix starting at 1.4.0 uses it by default. Applications within those version ranges may therefore not need to add a second JSON codec merely because those frameworks exchange JSON.

Absinthe is different. Its plug configuration accepts the codec through `json_codec`, either when the plug is called directly or when it is forwarded from a Phoenix router. The documented direct configuration is:

```elixir
plug Absinthe.Plug,
  schema: MyApp.Schema,
  json_codec: Jason
```

For a router, the same option belongs in `init_opts`:

```elixir
forward "/api",
  to: Absinthe.Plug,
  init_opts: [schema: MyApp.Schema, json_codec: Jason]
```

These integrations reduce configuration work, but they do not remove the need to review decoder behavior. A GraphQL endpoint can still receive invalid JSON that Jason intentionally rejects. Likewise, an Ecto or Postgrex integration does not by itself guarantee that every custom domain struct has an encoder.

The README does not document a global setting for changing all JSON handling, or fallback behavior when another codec is configured. The supported integration points should therefore be treated as framework-specific choices rather than a universal plug-and-replace policy.

## Jason Versus Poison, jiffy, and Strict JSON Requirements

The clearest alternative documented by the README is Poison. Jason follows JSON more strictly and rejects inputs such as unescaped newline characters in strings. Poison has differences in supported features, including its `as:` option for decoding into data structures. A team that depends on those conveniences may find Poison more suitable, while a team that wants stricter standards behavior may prefer Jason.

The README also compares Jason with `jiffy`, which is implemented as a C NIF. The stated performance relationship is that Jason is comparable to `jiffy` and usually only twice as slow. That is a project claim from the README, not a result established by this article. Pure Elixir also means Jason does not require the C NIF boundary used by `jiffy`, but the repository does not document deployment trade-offs beyond that distinction.

A native NIF can be attractive when runtime characteristics dominate, while Jason offers a pure-Elixir implementation with no separate NIF dependency described in the README. Jason is the wrong choice when direct decoding into structs, built-in support for `MapSet`, `Range`, or `Stream`, or automatic encoding of arbitrary structs is essential. It is also a poor fit for a producer that relies on non-standard JSON syntax, because strict rejection will expose that incompatibility instead of normalizing it.

## Licensing, Releases, and Maintenance Signals

The README says Jason is released under the Apache License 2.0 and points to the repository's `LICENSE` file. The repository metadata labels the licence as `Other`, with a custom licence that GitHub cannot classify. Those statements should be reconciled by reading `LICENSE` before distribution or commercial use. This is a project-specific licensing check, not a claim about what legal terms the file contains beyond the README's Apache License 2.0 statement.

The release history lists `v1.4.0` on 2022-09-13 and `v1.5.0-alpha.1` on 2022-10-16. The repository's last push was on 2026-05-05, so there has been repository activity after those releases, but the release notes do not explain whether that push produced a new stable release or changed maintenance policy. The stable release history should therefore be considered separately from repository activity.

The upgrade cost depends on how the library is used. Existing `Jason.encode!` and `Jason.decode!` call sites have a simple surface, but custom encoders, strict parsing behavior, and dependency constraints still need review when moving between versions. The benchmark directory contains `mix bench.encode` and `mix bench.decode` commands for the documented comparison workflow, but the README does not specify a compatibility test procedure for upgrades.

The project is not archived, and its last push was on 2026-05-05. That fact should be recorded as a maintenance signal, not expanded into a guarantee about future releases, bug response, or support.

## Conclusion

Adopt Jason for an Elixir application that needs a pure Elixir JSON codec and already fits Ecto, Phoenix, or Postgrex defaults. Do not choose it when decoding directly into structures, automatic handling of every Elixir type, or native NIF execution is a requirement. Before adoption, verify the `LICENSE` file against the Apache License 2.0 statement in the README, inspect the custom licence implications for your organization, and confirm that your structs implement `Jason.Encoder` or can be configured with an explicit field list.

## FAQ

### How do I install Jason in an Elixir project?

Add `{:jason, "~> 1.4"}` to the `deps` list in `mix.exs`, following the README example. The README does not provide a separate dependency download command.

### Does Jason decode JSON into Elixir structs automatically?

No. The README states that Jason has no support for decoding into data structures through the `as:` option. Decoding examples return ordinary Elixir maps.

### Can Jason encode arbitrary Elixir structs?

Only with an encoder path. Project-owned structs can derive `Jason.Encoder`, preferably with an explicit `only:` field list, while structs owned by another project can use `Protocol.derive/3`.

### Which frameworks use Jason by default?

The README states that Postgrex 0.14.0 and later, Ecto 3.0.0 and later, and Phoenix 1.4.0 and later use Jason by default. Absinthe requires the `:json_codec` option.

### How does Jason avoid re-encoding existing JSON?

Wrap already encoded JSON with `Jason.Fragment.new(already_encoded_json)`. Jason then treats that part as already encoded, avoiding a decoding and encoding roundtrip.

## Sources

- [Issues](https://github.com/michalmuskala/jason/issues)
- [michalmuskala/jason on GitHub](https://github.com/michalmuskala/jason)
- [README](https://github.com/michalmuskala/jason/blob/master/README.md)
- [Releases](https://github.com/michalmuskala/jason/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/michalmuskala-jason
