最近、複雑な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の世界に持っていきたい
とりあえず型を定義する
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を書いてみた
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に渡せばいいようだ。
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と、少し仲良くなれた気がしました。