loading...
gumi TECH Blog

Elixir: 純粋Elixirの最速JSONパーサ/ジェネレータJason

gumitech profile image gumi TECH ・2 min read

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

パーサとジェネレータは、ともにRFC 8259ECMA 404規格に完全に準拠しています。また、パーサーは、JSONTestSuiteを用いてテストされています。

インストール

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

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

基本的な使い方

Jasonによるエンコードとデコードのコードは、たとえばつぎのとおりです。詳しくは、Jasonのドキュメントをご参照ください。

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によりカスタムの型を定めなければなりません。

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

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

これで、Postgrex.start_link/1にモジュールを渡せば使えるようになります。

Ecto

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

config :ecto, json_library: Jason

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

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

Plug (およびPhoenix)

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

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

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

config :phoenix, :format_encoders,
  json: Jason

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

Absinthe

Absinthe.Plug:json_codecオプションを定めてください。

# 直に呼び出すとき
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をご覧ください。HTMLのパフォーマンスレポートはjason/decode.htmljason/encode.htmlで公開されています。

実行

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

Poisonとの違い

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

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

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

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

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

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

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

@derive Jason.Encoder
defstruct # ...

ライセンス

JasonはApache 2.0のライセンスでリリースされています。詳しくはLICENSEファイルをご参照ください。

テストとベンチマークについては、Poisonライブラリをもとにしています。このライセンスははじめCC0-1.0とされていました。

Posted on by:

gumitech profile

gumi TECH

@gumitech

gumi TECH は、株式会社gumiのエンジニアによる技術記事公開やDrinkupイベントなどの技術者交流を行うアカウントです。 gumi TECH Blog: http://dev.to/gumi / gumi TECH Drinkup: http://gumitech.connpass.com

gumi TECH Blog

株式会社gumiのエンジニアによる技術記事を公開しています。

Discussion

pic
Editor guide