Help us understand the problem. What is going on with this article?

PureScriptで簡単にJSONをパースする

More than 1 year has passed since last update.

はじめに

この記事は、Simple-JSONの作者であるJustin Wooさん(@kimagure)の記事を翻訳したものです。
twitterでこのようなことを書いてくださっていたので、たまたま反応した @oreshinya が翻訳することになりました。

PureScriptで簡単にJSONをパースする

JSONのような何らかのテキストデータを、プログラムで利用できるデータ型にパースすることは、私たちが使用している言語のほとんどについて行う必要があることです。いくつかの言語では、パーサーを書く方法を習得する必要がありますが、PureScriptでは、Simple-JSONを使うことにより、機械的にパースすることができます。私は、PureScript 0.11.6でRowToList機能がリリースされた時にこのライブラリを書きました。このライブラリは型シノニムを書くだけで使用できます。

プロジェクトのセットアップ

このライブラリを使うプロジェクトのセットアップをしましょう。

$ mkdir simple-json-example && cd simple-json-example
$ pulp --psc-package init
$ psc-package install simple-json
$ pulp -w run
> Compiling [...]
> * Build Successful.
> Hello sailor!

src/Main.pursを開き、サンプルを書いてみましょう。

Recordのエイリアスの例

a :: String, b :: Int, c :: Booleanを持つRecordのエイリアスを定義しましょう。

type MyRecord =
  { a :: String
  , b :: Int
  , c :: Boolean
  }

それから、パースしたいJSONの例をいくつか書きましょう。ここに正しいJSONと不正なJSONがあります。

goodJSON = """{
  "a": "apple",
  "b": 1,
  "c": true
}"""

badJSON = """{
  "a": "apple"
  "b": "wrong type",
  "c": true
}"""

これで、JSONをパースするのに必要なものが全て揃いました。Simple.JSON.readJSONの型シグネチャを見ると、Simple.JSON.readJSON :: forall a. ReadForeign a => String -> Either (NonEmptyList ForeignError) aであることがわかります。これは、ReadForeignのインスタンスである全ての型(この場合、すべてのRecordの型)について、この関数に文字列を渡すと、パース時のエラーを少なくとも1つ持つリストか、あるいはパースした値が得られることを示しています。このことから、型注釈付きのlet bindingsを使ってmain関数を書くことができます。

main = do
  let
    (goodParse :: Either _ MyRecord) = readJSON goodJSON
    (badParse :: Either _ MyRecord) = readJSON badJSON

これは、コンパイラにここでMyRecord型を使いたいということを伝え、MyRecordによってエイリアス化された型{ a :: String, b :: Int , c :: Boolean }を使って、その型の制約が満たされるように穴を埋めるために使われます。

これらをログに出力する関数を書いてみましょう。

main = do
  let
    (goodParse :: Either _ MyRecord) = readJSON goodJSON
    (badParse :: Either _ MyRecord) = readJSON badJSON
    logEitherMyRecord (Left _) = log $ "failed to parse"
    logEitherMyRecord (Right _) = log "successfully parsed"
  logEitherMyRecord goodParse
  logEitherMyRecord badParse
  -- result:
  -- successfully parsed
  -- failed to parse

正しいJSONの場合、Rightを得るのに対して、不正なJSONの場合、Leftを得ることがわかります。

以上です。

なぜこのように動くのか

PureScriptでは、型レベルにおいて、Recordは糖衣構文です。

type MyRecord = { a :: String }

は、以下と同じです。

type MyRecord = Record ( a :: String )

row自体はイテレートできないですが、これをRowToListを使って、以下のような型レベルのリストに変換できます。

RowToList (a :: String) (Cons "a" String Nil)

そして、タームレベル(すいません、ここうまい訳し方が思いつかなかったです)のリストのように、リストアイテムをイテレートできます。(代わりに型クラスのインスタンスを使いますが。)
もし、これについてもっと知りたければ、Simple-JSONに関するこちらの投稿をご覧ください。

結論

型情報から完全に導出できるJSONデコードのようなものは、PureScriptのユーザーライブラリで自動的に実行できる、ということを示せたのではないかと思います。

リンク

原文は以下から

Simple JSON Parsing with PureScript

Parsing some kind of text data format like JSON into data types that we can use in our programs is something we have to do with just about any language we use. While some languages will require that you have to learn how to write a parser, in PureScript, we can automatically get a parser for our types by using the Simple-JSON library. I wrote this library when the RowToList feature was released in PureScript 0.11.6, and it requires little more than type synonyms for use.

Project Setup

Let's set up a project that uses this library:

$ mkdir simple-json-example && cd simple-json-example
$ pulp --psc-package init
$ psc-package install simple-json
$ pulp -w run
> Compiling [...]
> * Build Successful.
> Hello sailor!

Let's open up src/Main.purs and try an example.

Record alias example

Let's define a record type alias that has fields a :: String, b :: Int, c :: Boolean.

type MyRecord =
  { a :: String
  , b :: Int
  , c :: Boolean
  }

Now let's write some example JSON we'd like to parse. We'll have one good example and one bad example.

goodJSON = """{
  "a": "apple",
  "b": 1,
  "c": true
}"""

badJSON = """{
  "a": "apple"
  "b": "wrong type",
  "c": true
}"""

Now we have just about everything we need to parse JSON. If we look at the type signature of Simple.JSON.readJSON, we can see that it's Simple.JSON.readJSON :: forall a. ReadForeign a => String -> Either (NonEmptyList ForeignError) a. This tells us that for all types that have a ReadForeign instance (in this case, all Record types do), we can pass in a string to this function and get back an either of a non-empty list of foreign-reading errors or our value. Using this information, we can write our main function using annotated let bindings.

main = do
  let
    (goodParse :: Either _ MyRecord) = readJSON goodJSON
    (badParse :: Either _ MyRecord) = readJSON badJSON

This tells the compiler that we want to use our MyRecord type here, and that will be used to fill in the hole that the constraint should be fulfilled with the type aliased by MyRecord, which is { a :: String, b :: Int , c :: Boolean }. Let's write a function that can take these and log these out:

main = do
  let
    (goodParse :: Either _ MyRecord) = readJSON goodJSON
    (badParse :: Either _ MyRecord) = readJSON badJSON
    logEitherMyRecord (Left _) = log $ "failed to parse"
    logEitherMyRecord (Right _) = log "successfully parsed"
  logEitherMyRecord goodParse
  logEitherMyRecord badParse
  -- result:
  -- successfully parsed
  -- failed to parse

So we can see that in the case of goodJSON, we got the Right from the either for our result, whereas for our badJSON, we got the Left. And that's it!

Why this works

In PureScript, a record at the type level is syntactic sugar:

type MyRecord = { a :: String }

is the same as

type MyRecord = Record ( a :: String )

While the row type itself can't be iterated, we can use RowToList to convert this into a type level list, such that

RowToList (a :: String) (Cons "a" String Nil)

And like a term-level list, we can iterate the items of the list -- but using type class instances instead. If you'd like to know more about this, please see my post about Simple-JSON here.

Conclusion

Hopefully this has shown you that things like JSON decoding that can be completely derived from type information can be done automatically with user libraries in PureScript.

Links

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away