Evan Czaplicki 寫了篇為什麼選擇json來當做是資料交換的處理,而不是其他的格式,之前有簡單地帶過關於elm在json的處理是怎麼做的,不過當時沒有說很多,只是簡單提到,可以這麼做。但其實elm在json的處理是可以出一本書的:json survival kit!簡單說就是其他的交換系統都是被特定廠商挾持(譬如GraphQL ?),只有JSON是大家都可以認可的,但下個十年會怎麼變也沒有人知道,姑且就先用JSON擋著用
書有沒有買沒有關係(我也沒有),不過要好好地去想想關於json要怎麼在elm裡頭變成是你要的資料型態才是重點。
先來個 octocat的api
{
"login": "octocat",
"id": 583231,
"name": "The Octocat",
}
你可以自建一個 type alias
type alias User =
{ id: Int
, login: String
, name: String
}
這個是兩個常常你會用的的method。簡單的說,field 就是針對一個,at是針對nested json裡,用list的方式去取得json內的值。
field : String -> Decoder a -> Decoder a
at : List String -> Decoder a -> Decoder a
因為我們不知道json內是string, int或是其他的type(你也可以自建自己要的decode 後的 type。)
如果不懂的話,可以直接看doc:
decodeString (field "x" int) "{ \"x\": 3 }" == Ok 3
decodeString (field "x" int) "{ \"x\": 3, \"y\": 4 }" == Ok 3
json = """{ "person": { "name": "tom", "age": 42 } }"""
decodeString (at ["person", "name"] string) json == Ok "tom"
map : (a -> value) -> Decoder a -> Decoder value
githubUser : Decoder User
githubUser =
map3 User
(field "id" int)
(field "login" string)
(field "name" string)
-- 其實這就是object -> record的方法
map
有從2 到8,如果你要decode的位置沒有很多的話,可以直接使用,但是如果你需要大於8 以上的話,elm上也是直接推薦你使用社群套件:elm-decode-pipeline
Note: If you run out of map functions, take a look at elm-decode-pipeline which makes it easier to handle large objects, but produces lower quality type errors.
githubUser' : Decoder (String, String)
githubUser' =
map2 (,)
(field "login" string)
(field "name" string)
這是參考 Brian Hicks的文章:
{
"Site1": {
"PC1": {
"ip": "x.x.x.x",
"version": "3"
},
"PC2": {
"ip": "x.x.x.x",
"version": "3"
}
},
"Site2": {
"PC1": {
"ip": "x.x.x.x",
"version": "3"
},
"PC2": {
"ip": "x.x.x.x",
"version": "3"
}
}
}
elm的decode裡,還有一個很好用的東西:
dict : Decoder a -> Decoder (Dict String a)
type alias Value =
Value
value : Decoder Value
value
有點像是個停泊點,你尚未要去取得在json的特定type,你可以用一個叫 Value
的
type先暫時叫這個名字,等到你很確定了,再加上你要的type。
Do not do anything with a JSON value, just bring it into Elm as a Value. This can be useful if you have particularly crazy data that you would like to deal with later. Or if you are going to send it out a port and do not care about its structure.
-- first attempt
sites : Decoder (Dict String Value)
sites =
dict value
decodeString sites ourSitesBlob
== Ok (Dict.fromList
[ ("Site1", { PC1 = { ip = "x.x.x.x", version = "3" }, PC2 = { ip = "x.x.x.x", version = "3" } })
, ("Site2", { PC1 = { ip = "x.x.x.x", version = "3" }, PC2 = { ip = "x.x.x.x", version = "3" } })
])
sites : Decoder (Dict String (Dict String Value))
sites =
dict (dict value)
-- second attempt
decodeString sites ourSitesBlob
== Ok (Dict.fromList
[ ("Site1"
, Dict.fromList
[ ( "PC1", { ip = "x.x.x.x", version = "3" } )
, ( "PC2", { ip = "x.x.x.x", version = "3" } )
]
)
, ( "Site2"
, Dict.fromList
[ ( "PC1", { ip = "x.x.x.x", version = "3" } )
, ( "PC2", { ip = "x.x.x.x", version = "3" } )
]
)
])
-- final result
type alias Machine =
{ ip : String
, version : String
}
machine : Decoder Machine
machine =
map2 Machine
(field "ip" string)
(field "version" string)
我們可以一步步的去嘗試去取得我們想要的data.
但是我們不太可能常常在本機端剛好有json的data,常常要使用http的方式,我們拿這個elm-json-decoding來當例子:
getData : Http.Request (List User)
getData =
Http.get "/src/data.json" usersDecoder
fetchUsers : Cmd Msg
fetchUsers =
Http.send NewHttpData getData
send : (Result Error a -> msg) -> Request a -> Cmd msg
先是用 Htpp.get
得到的type 因為你的decoder會是 List User
。我們等等再來看,先來看 send
。send
會是一個 Cmd
會吃一個 Request
當parameter,再傳出去給 elm runtime
,
所以要send出一個你定義的 Msg
type,而且這個type要是 Result
type Msg
= NewHttpData (Result Http.Error (List User))
update msg model =
case msg of
NewHttpData result ->
case result of
Ok users ->
( { model | users = Just users }, Cmd.none )
Err e ->
( { model | error = Just e }, Cmd.none )
我們自行定義的Msg裡的 NewHttpData
就是一個 Result
如果成功就會是 List User
,我們再自己定義自己要的decoder
type alias User =
{ name : String
, age : Int
, description : Maybe String
, languages : List String
, playsFootball : Bool
}
type alias Model =
{ users : Maybe (List User)
, error : Maybe Http.Error
}
usersDecoder : Decode.Decoder (List User)
usersDecoder =
Decode.at [ "users" ] (Decode.list userDecoder)
userDecoder : Decode.Decoder User
userDecoder =
Decode.map5
User
(Decode.at [ "name" ] Decode.string)
(Decode.at [ "age" ] Decode.int)
(Decode.maybe (Decode.at [ "description" ] Decode.string))
(Decode.at [ "languages" ] (Decode.list Decode.string))
(Decode.at [ "sports" ] sportsDecoder)
這樣就完成了從伺服器端到elm後,如何讀取json了。 希望這樣有再更清楚一點。