Elixir: 純粋Elixirの最速JSONパーサ/ジェネレータJason
Tags: elixir, webdev, progarmming, tutorial
Published: 2018-10-23 02:30:47

---
title: Elixir: 純粋Elixirの最速JSONパーサ/ジェネレータJason
published: true
description: Jasonは、Elixir開発者のひとりMichał Muskała氏により純粋なElixirで書かれた最速のJSONパーサおよびジェネレータです。他のライブラリ、とくにPoisonより少なくとも2倍高速です。ライブラリのREADME.mdにもとづいてその内容をご紹介します。
tags: #elixir, #webdev, #progarmming, #tutorial
---

[Jason](https://t.co/Md4qwtxzGf)は、Elixir開発者のひとり[Michał Muskała](https://michal.muskala.eu/)氏により純粋なElixirで書かれた最速のJSONパーサおよびジェネレータです。他のライブラリ、とくに[Poison](https://github.com/devinus/poison#poison)より少なくとも2倍高速です。CでNIFとして実装されているjiffyにも劣りません(処理時間は2倍程度に収まります)。本稿はMuskała氏の許諾を得て、Jasonライブラリの[`README.md`](https://github.com/michalmuskala/jason#jason)にもとづいてその内容をご紹介します。

パーサとジェネレータは、ともに[RFC 8259](https://tools.ietf.org/html/rfc8259)と[ECMA 404](http://www.ecma-international.org/publications/standards/Ecma-404.htm)規格に完全に準拠しています。また、パーサーは、[JSONTestSuite](https://github.com/nst/JSONTestSuite)を用いてテストされています。


# インストール
このパッケージをインストールするには、`mix.exs`の`deps`に依存関係として`jason`を加えてください。

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


# 基本的な使い方
Jasonによるエンコードとデコードのコードは、たとえばつぎのとおりです。詳しくは、[Jasonのドキュメント](https://hexdocs.pm/jason)をご参照ください。

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

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


# 他のライブラリとの使い方

## Postgrex
`lib`内の`.ex`ファイルで、[`Postgrex.Types.define/3`](https://hexdocs.pm/postgrex/Postgrex.Types.html#define/3)によりカスタムの型を定めなければなりません。

```elixir
Postgrex.Types.define(MyApp.PostgresTypes, [], json: Jason)

## Ectoで使うときはデフォルト拡張を渡さなければなりません
Postgrex.Types.define(MyApp.PostgresTypes, [] ++ Ecto.Adapters.Postgres.extensions(), json: Jason)
```

これで、[`Postgrex.start_link/1`](https://hexdocs.pm/postgrex/Postgrex.html#start_link/1)にモジュールを渡せば使えるようになります。


## Ecto
Ectoアプリケーションで`Poison`の現行の動作を完全に再現するには、`config/config.exs`内で`Jason`をデフォルトエンコーダに定めなければなりません。

```elixir
config :ecto, json_library: Jason
```

さらに、PostgreSQLを使う場合は、`config/config.exs`または`config/<env>.exs`にカスタム型モジュールを定めてください。

```elixir
config :my_app, MyApp.Repo, types: MyApp.PostgresTypes
```

## Plug (およびPhoenix)
まず、JSONの解析に`Jason`を用いるよう[`Plug.Parsers`](https://hexdocs.pm/plug/Plug.Parsers.html)に定めなければなりません。そして、`Plug.Parsers`プラグをどこに差し込むか決めます。Phoenixであれば、エンドポイントモジュール`lib/app_web/endpoint.ex`に加えるコードは、たとえばつぎのとおりです。

```elixir
plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  json_decoder: Jason
```

さらに、Phoenixでは`config/config.exs`にエンコーダをつぎのように定めてください。

```elixir
config :phoenix, :format_encoders,
  json: Jason
```

Phoenixチャンネル用のカスタムJSONエンコーダはもう少し手間がかかります。カスタムシリアライザのコードとその使い方については[`json_transport_serializer.ex`](https://gist.github.com/michalmuskala/d5fabcd26be2befdfb72b72e0b0f2797)をご参照ください。

## Absinthe
[`Absinthe.Plug`](https://hexdocs.pm/absinthe_plug/Absinthe.Plug.html)に`:json_codec`オプションを定めてください。

```elixir
# 直に呼び出すとき
plug Absinthe.Plug,
  schema: MyApp.Schema,
  json_codec: Jason

# Phoenixルータで使うとき
forward "/api",
  to: Absinthe.Plug,
  init_opts: [schema: MyApp.Schema, json_codec: Jason]
```


# ベンチマーク
メモリの計測も含めたベンチマークは[`decode.txt`](https://gist.github.com/michalmuskala/4d64a5a7696ca84ac7c169a0206640d5)をご覧ください。HTMLのパフォーマンスレポートは[`jason/decode.html`](http://michal.muskala.eu/jason/decode.html)と[`jason/encode.html`](http://michal.muskala.eu/jason/encode.html)で公開されています。

## 実行
広く使われているElixirとErlangのJSONライブラリは、`mix bench.encode`および`mix bench.decode`でベンチマークが行われます。ベンチマークを実行した結果は、それぞれ`bench/output/encode.html`と`bench/output/decode.html`でお確かめください。


# Poisonとの違い
JasonはPoisonといくつか異なる機能があります。

- JSON仕様への準拠はより厳密です。
    - JSON文字列内のエスケープされていない改行(たとえば"\"\n\"")はデコードエラーになります。
- データ構造へのデコード(`:as`オプション)には対応しません。
- `MapSet`と`Range`および`Stream`の組み込みエンコーダーはありません。
- 任意の構造体へのエンコードはサポートされません。
    - `Jason.Encoder`プロトコルを明示的に実装してください。
- 高品質出力(デフォルト`pretty: true`)のカスタマイズオプションが異なります。

サポートされていないコレクション型のエンコーダが必要な場合には、プロジェクトに直接追加することが推奨されます。

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

プロトコルを実装していない構造体をエンコードしなければならないときは、どのフィールドをJSONにするのが実装で明示できます。

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

すべてのフィールドをエンコードすることもできます。けれど、新たなフィールドを加えるとき、誤ってプライベートな情報が漏れることのないように注意してください。

```elixir
@derive Jason.Encoder
defstruct # ...
```


# ライセンス
JasonはApache 2.0のライセンスでリリースされています。詳しくは[LICENSE](https://github.com/michalmuskala/jason/blob/master/LICENSE)ファイルをご参照ください。

テストとベンチマークについては、[Poisonライブラリ](https://github.com/devinus/poison)をもとにしています。このライセンスははじめ[CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/)とされていました。