21
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ElmAdvent Calendar 2018

Day 12

ElmとJSONの話

Last updated at Posted at 2018-12-11

最近、複雑なJSONをElmで扱いたい的なことがあり、JSONとElmについて知ったのでまとめる。
(思いついたまま書いたので、二番煎じ以降だったらごめんなさい)

前置き

ElmでJSONを扱うには

とりあえず公式の、
https://package.elm-lang.org/packages/elm/json/latest/
を使いましょう。

公式でDecodeできる型

  • String
  • Bool
  • Int
  • Float
  • List,Array
  • Dict
  • null

これとその他の関数を組み合わせて、あらゆる(正しい)JSONをパースして、必要な情報を取り出すことができる。
ただし、取り出すときに、取り出したい情報のkeyと型がすでにわかっていなければならない

##本題

ここで思いついてしまった。
「あらかじめ取り出したい値のkeyや型がわからない場合にも、とりあえずElmの世界に持っていきたい」

やりたいこと

あらゆる(正しい形式の)JSONを、Elmの世界に持っていきたい

とりあえず型を定義する

Main.elm
type ValueType
    = String String
    | Int Int
    | Float Float
    | Bool Bool
    | Null
    | Object (List ( String, ValueType ))
    | List (List ValueType)

JSONで表現できるならなんでも許すような感じで書いた。
JSONはvalueとなれるものがそのままトップレベルに来ることができる。
さらにObject(DictとかMapとか色々な表現があるが、この記事ではObjectに統一する)やList(Arrayなど色々な(ry )は、valueの型が再帰している。

なんか難しそう。

それに合わせてDecoderを書いてみた

Main.elm
import Json.Decode as D

valueTypeDecoder : D.Decoder ValueType
valueTypeDecoder =
    D.oneOf
        [ D.string
            |> D.andThen (\str -> D.succeed (String str))
        , D.int
            |> D.andThen (\num -> D.succeed (Int num))
        , D.float
            |> D.andThen (\num -> D.succeed (Float num))
        , D.bool
            |> D.andThen (\bool -> D.succeed (Bool bool))
        , D.lazy
            (\_ ->
                D.list valueTypeDecoder
            )
            |> D.andThen (\list -> D.succeed (List list))
        , D.lazy
            (\_ ->
                D.keyValuePairs valueTypeDecoder
            )
            |> D.andThen (\object -> D.succeed (Object object))
        , D.nullable D.value
            |> D.andThen (\_ -> D.succeed Null)
        ]

https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#oneOf
oneOfで、列挙したどれかでデコードできるのなら、デコードしてくれるらしい。
とりあえず型全てに対して行いたいので、列挙していった。
andThenが実行されるのは成功した場合、つまりそのときには必ず型が決まっているため、succeedで定義した型に合わせるように書いた。
再帰が必要なObjectとListに関してはlazyで遅延させて評価させる。
この辺りは
https://qiita.com/ymtszw/items/a10229de887b38c7a65b
とかを参考にした。
nullableは基本的にどの型のnullを許容するかみたいに書くが、valueに対してnullを許容するといった形で書けば綺麗に読みやすくなった気がする。

DecoderがあるならEncoderもあるよね

なんかEncoderではないらしい?ただElmの型にあるものをEncode.Valueに変換していって、それをEncode.encodeに渡せばいいようだ。

Main.elm
import Json.Encode as E

valueTypeEncode : ValueType -> E.Value
valueTypeEncode vt =
    case vt of
        String str ->
            E.string str

        Int num ->
            E.int num

        Float num ->
            E.float num

        Bool bool ->
            E.bool bool

        Object object ->
            E.dict identity valueTypeEncode (Dict.fromList object)

        Null ->
            E.null

        List list ->
            E.list valueTypeEncode list

case ofでパターンマッチさせて、それぞれの型で変換するルールを書いただけ。

ここまででできたもの

あらゆるJSONのDecodeとEncodeができるようになった。

作ったもの

さて、これを作ったからには、何かに活かせないかなととりあえず書いて公開したものがこちら。
https://goryudyuma.github.io/json-elm/index.html

ただJSONを入れると整形して出してくれるだけの、ggればいくらでも出てきそうなアプリケーション。

ソースコードはこちら。
https://github.com/Goryudyuma/json-elm

もちろん言うまでもないが、中では一度Elmの世界に持っていっている。

まとめ

このように、あらかじめ入ってくるJSONの型がわからなくても、Elmは対応できることがわかった。

感想

途中紆余曲折があったが楽しかった。
最初はnullをMaybeで表現しようとして無駄な表現を書いていたり、標準のJSONパッケージではできないかもと思いParserで頑張るか〜とか思ってたけど、終わってみればめちゃくちゃ綺麗でわかりやすい形になっていた。
ElmとJSONと、少し仲良くなれた気がしました。

21
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?