Json and
Elm decoders
GET / HTTP/1.1
<html>
<head>...</head>
<body>
<h1>Wonderfull site!</h1>
<p>Hello</p>
</body>
</html>
GET / HTTP/1.1
<html>
<head>...</head>
<body>
<h1>Wonderfull site!</h1>
<p>Hello</p>
</body>
</html>
GET /users/ HTTP/1.1
<html>
<head>...</head>
<body>
<h1>Wonderfull site!</h1>
<ul>
<li>Arthur</li>
<li>Ford</li>
</ul>
</body>
</html>
Need to reload the entire page!
Json response
GET /userlist/ HTTP/1.1
Accept: application/json
Json response
HTTP/1.1 200 OK
Content-Length: 130
[ { "name": "Marvin",
"kind": "Robot" },
{ "name": "Zaphod",
"kind": "ET"},
{ "name": "Arthur",
"kind": "human"}
]
GET /userlist/ HTTP/1.1
Accept: application/json
Json response
HTTP/1.1 200 OK
Content-Length: 130
[ { "name": "Marvin",
"kind": "Robot" },
{ "name": "Zaphod",
"kind": "ET"},
{ "name": "Arthur",
"kind": "human"}
]
Display those data with JS (via Elm).
"AJAX"
GET /userlist/ HTTP/1.1
Accept: application/json
Dessine moi un mouton !
A
B
C
D
E
F
G
H
I
JSON
JavaScript Object Notation
JSON
JavaScript Object Notation
Only data, not tied to JavaScript
Primitives
Numbers
15
15.2
-54
5e10
Primitives
Numbers
Strings
(including UTF-8)
""
"Hello world"
"😊"
15
15.2
-54
5e10
Primitives
Numbers
15
15.2
-54
5e10
Boolean
true
false
Strings
(including UTF-8)
""
"Hello world"
"😊"
Primitives
Numbers
15
15.2
-54
5e10
Strings
(including UTF-8)
""
"Hello world"
"😊"
Boolean
true
false
Null
(represents empty value)
null
Composites
Arrays
[5, 42, "hello", null]
Composites
Arrays
[5, 42, "hello", null]
Objects
{ "name": "Arthur",
"kind": "human"
}
Composites
[5, 42, "hello", null]
[ { "name": "Marvin",
"kind": "Robot" },
{ "name": "Zaphod",
"kind": "ET"},
{ "name": "Arthur",
"kind": "human"}
]
{ "name": "Arthur",
"kind": "human"
}
{ "server": "Heart-of-gold",
"arenas": [
{ "game": "pong",
"players": [
{ "name": "Arthur",
"score": 5 },
{ "name": "Zaphod",
"score": 2 }
]
}
]
}
Arrays
Objects
Convert JSON to Elm
Convert JSON to Elm
can fail!
"Maybe" represents failure
Produce a Maybe
getDay : Int -> Maybe String
getDay dayNumber = case dayNumber of
0 -> Just "Monday"
1 -> Just "Tuesday"
...
6 -> Just "Sunday"
_ -> Nothing
Deal with maybe!
res : Int
res = case (String.toInt "42") of
Just x ->
2 * x
Nothing ->
0
"Maybe" represents failure
"Result" represents failure
...
"Result" represents failure
with an error message
Result
type Result error value
= Err error
| Ok value
Result
type Result error value
= Err error
| Ok value
Can be any type
Result
type Result error value
= Err error
| Ok value
type Maybe a
= Nothing
| Just a
Result
type Result error value
= Err error
| Ok value
type Maybe a
= Nothing
| Just a
Result
type Result error value
= Err error
| Ok value
toBool : String -> Result String Bool
toBool s = case s of
"true" -> Ok True
"false" -> Ok False
"42" -> Err "Nice try!"
_ -> Err ("not a bool: " ++ s)
Result
type Result error value
= Err error
| Ok value
toBool : String -> Result String Bool
toBool s = case s of
"true" -> Ok True
"false" -> Ok False
"42" -> Err "Nice try!"
_ -> Err ("not a bool: " ++ s)
Error messages,
impossible with Maybe
Result
type Result error value
= Err error
| Ok value
toBool : String -> Result String Bool
toBool s = case s of
"true" -> Ok True
"false" -> Ok False
"42" -> Err "Nice try!"
_ -> Err ("not a bool: " ++ s)
Result
type Result error value
= Err error
| Ok value
toBool : String -> Result String Bool
toBool s = case s of
"true" -> Ok True
"false" -> Ok False
"42" -> Err "Nice try!"
_ -> Err ("not a bool: " ++ s)
Hands on!
Write a function
kineticE : Float -> Float -> Result String Float
kineticE mass speed =
...
using
E_k = \frac{1}{2} mass \times speed^2
with distinct errors if the mass or the speed are negative
Convert JSON to Elm
Decoders
describe what to do
but do nothing
import Json.Decode as Decode exposing (int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
Decode.decodeString :
Decoder val
-> String
-> Result Error val
Decode.decodeString :
Decoder val
-> String
-> Result Error val
import Json.Decode as Decode exposing (int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
Decode.decodeString :
Decoder val
-> String
-> Result Error val
The type we want to decode to
import Json.Decode as Decode exposing (int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
Decode.decodeString :
Decoder val
-> String
-> Result Error val
The decoder, describing how to transform a JSON value into
an Elm value of type "val"
import Json.Decode as Decode exposing (int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
Decode.decodeString :
Decoder val
-> String
-> Result Error val
The JSON we want to decode:
- 42
- {"name": "Arthur", "age": 42}
- [5, {"name":"Ford"}]
- ...
import Json.Decode as Decode exposing (int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
Decode.decodeString :
Decoder val
-> String
-> Result Error val
Either:
- the resulting value,
- or an Error if the decoder failed to decode the JSON
import Json.Decode as Decode exposing (int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
import Json.Decode as Decode exposing (Decoder, int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
int : Decoder Int
Decode.decodeString :
Decoder val
-> String
-> Result Error val
import Json.Decode as Decode exposing (Decoder, int)
Decode.decodeString int "4" == Ok 4
Decode.decodeString int "hello" == Err ...
Decode.decodeString int "1 + 2" == Err ...
int : Decoder Int
Decode.decodeString :
Decoder Int
-> String
-> Result Error Int
Decode primitives
import Json.Decode as Decode exposing
(Decoder, decodeString)
Decode.int : Decoder Int
decodeString Decode.int "4" == Ok 4
Decode.string : Decoder String
decodeString Decode.string "\"hey\"" == Ok "hey"
decodeString Decode.string "3.14" == Err ...
decodeString Decode.string "{ \"hello\": 42 }" == Err ...
Decode.bool : Decoder Bool
decodeString bool "true" == Ok True
decodeString bool "42" == Err ...
Decode Arrays
import Json.Decode as Decode exposing
(Decoder, decodeString, list, int, bool)
Decode.list : Decoder a -> Decoder (List a)
decodeString (list int) "[1,2,3]" == Ok [1,2,3]
decodeString (list bool) "[true,false]" == Ok [True,False]
decodeString (list int) "[true,false]" == Err ...
decodeString (list bool) "[1,2,3]" == Err ...
decodeString (list int) "42" == Err ...
Hands on 1
Write a decoder for the following JSON:
42
Starting point :
https://ellie-app.com/6H9QpXpnJXka1
Hands on 2
Write a decoder for the following JSON:
[42, 10, 52]
Starting point :
https://ellie-app.com/6H9Ry2yg45Wa1
Hands on 3
Write a decoder for the following JSON:
[[1, 2, 3], [42, 6, 9], [5,1]]
Starting point :
https://ellie-app.com/6H9PfrTdj3ka1
Decode Field in Object
import Json.Decode as Decode exposing
(Decoder, decodeString, field, int, string)
json = "{ \"username\": \"Tom\", \"age\": 42 }"
Decode.field : String -> Decoder a -> Decoder a
decodeString (field "username" string) json == Ok "Tom"
decodeString (field "age" int) json == Ok 42
decodeString (field "age" string) json == Err ...
decodeString (field "username" int) json == Err ...
Hands on 4
Get the score from this JSON:
{ "name": "Ford", "score": 42 }
Starting point :
https://ellie-app.com/6KKSDtSCM6Ma1
Decode Objects
import Json.Decode as Decode exposing
(Decoder, decodeString, field, int, string, map2)
json = "{ \"username\": \"Tom\", \"age\": 42 }"
type alias User = { name : String, age : Int }
userDecoder : Decoder User
userDecoder =
map2 User
(field "username" string)
(field "age" int)
decodeString userDecoder json
== Ok { name = "Tom", age = 42 }
Decode Objects
import Json.Decode as Decode exposing
(Decoder, decodeString, field, int, string, map2)
json = "{ \"username\": \"Tom\", \"age\": 42 }"
type alias User = { name : String, age : Int }
userDecoder : Decoder User
userDecoder =
map2 User
(field "username" string)
(field "age" int)
decodeString userDecoder json
== Ok { name = "Tom", age = 42 }
⚠️
Order of the fields matters!
import Json.Decode as Decode
Decode.decodeString userDecoder
"""{ "username": "Tom", "age": 42, "city": "NY" }"""
type alias User =
{ name : String
, age : Int
}
userDecoder: Decoder User
-- produces:
Ok { name : "Tom", age : 42 }
- Field names are decoupled in JSON and Elm
(the matching is done in the decoder) - The decoder can "ignore" some data
import Json.Decode as Decode
Decode.decodeString userDecoder
"""{ "username": "Tom", "age": 42, "city": "NY" }"""
Decode.decodeString userDecoder
"""{ "username": "Tom", "nbYears": 42, "city": "NY" }"""
-- produces:
Ok { name : "Tom", age : 42 }
-- produces:
Err ...
Hands on 5
Decode the JSON on the left to the type on the right:
{
"name": "Ford",
"score": 42,
"favorite_color": "red",
"from": "Betelgeuse"
}
Starting point :
type alias Player =
{ name : String
, score: Int
, origin: String
}
Hands on 6
Decode the JSON on the left to the type on the right:
{
"players": [{
"name": "Ford",
"score": 42,
"favorite_color": "red",
"from": "Betelgeuse"
}],
"games": ["pong", "tetris"]
}
Starting point :
type alias Board =
{ players: List Player
, games : List String
}
-- Player is the same
-- as previously
initialModel : Model
initialModel =
{ count = 0 }
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
view : Model -> Html Msg
view model =
div []
[ button [ onClick Increment ] [ text "+1" ]
, text <| String.fromInt model.count
]
init : Model
view : Model -> Html Msg
update : Msg -> Model -> Model
Json response
GET /userlist/ HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Length: 130
[ { "name": "Marvin",
"kind": "Robot" },
{ "name": "Zaphod",
"kind": "ET"},
{ "name": "Arthur",
"kind": "human"}
]
init : Model
view : Model -> Html Msg
update : Msg -> Model -> Model
No side
effect!
init : flags -> (Model, Cmd Msg)
view : Model -> Html Msg
update : Msg -> Model -> (Model, Cmd Msg)
1. Hey Runtime,
please perform an HTTP request!
3. Ok, here is the response
2.
1. A new model indicating "Loading"
4. A new model with loaded data
HTTP request can fail
HTTP request can fail
because:
-
the server is down,
-
the network is down,
-
Obiwan Kenobi,
- ...
Http Error
type Result error value
= Err error
| Ok value
-- in the module Http
type Error
= BadUrl String
| Timeout
| NetworkError
| BadStatus Int
| BadBody String
showResp : Result Http.Error (List User)
-> String
showResp resp =
case resp of
Ok users ->
showUsers users
Err _ ->
"An error occured, I'm sory!"
showResp : Result Http.Error (List User)
-> String
showResp resp =
case resp of
Ok users ->
showUsers users
Err _ ->
"An error occured, I'm sory!"
showResp : Result Http.Error (List User)
-> String
showResp resp =
case resp of
Ok users ->
showUsers users
Err _ ->
"An error occured, I'm sory!"
The lazy way...
showResp : Result Http.Error (List User)
-> String
showResp resp =
case resp of
Ok users ->
showUsers users
Err Http.NetworkError ->
"You seem disconnected."
Err Http.Timeout ->
"The server is too slow."
Err _ ->
"An error occured, I'm sory!"
Json response
GET /userlist/ HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Length: 130
[ { "name": "Marvin",
"kind": "Robot" },
{ "name": "Zaphod",
"kind": "ET"},
{ "name": "Arthur",
"kind": "human"}
]
type Msg
= LoadUserClicked
| GotUsers (Result Http.Error (List User))
<<Hey Runtime! Fetch data from "/userlist/" and
when you are done, issue a 'GotUsers'
message with the result and convert the data
with 'usersDecoder'!>>
type Msg
= LoadUserClicked
| GotUsers (Result Http.Error (List User))
Cmd Msg
<<Hey Runtime! Fetch data from "/userlist/" and
when you are done, issue a 'GotUsers'
message with the result and convert the data
with 'usersDecoder'!>>
type Msg
= LoadUserClicked
| GotUsers (Result Http.Error (List User))
Cmd Msg
Msg
Json decoder
<<Hey Runtime! Fetch data from "/userlist/" and
when you are done, issue a 'GotUsers'
message with the result and convert the data
with 'usersDecoder'!>>
type Msg
= LoadUserClicked
| GotUsers (Result Http.Error (List User))
init : flags -> (Model, Cmd Msg)
view : Model -> Html Msg
update : Msg -> Model -> (Model, Cmd Msg)
Cmd Msg
Msg
Http.get { url = "/userlist/"
, expect = Http.expectJson
GotUsers
usersDecoder
}
Json decoder
type Msg
= LoadUserClicked
| GotUsers (Result Http.Error (List User))
type Msg
= GotUsers (Result Http.Error (List User))
| LoadUserClicked
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
LoadUserClicked ->
( model -- model not changed
, Http.get
{ url = "/userlist/"
, expect = Http.expectJson GotUsers usersDecoder
}
)
GotUsers result ->
( { model | users = result }
, Cmd.none -- no command to perform!
)
Json and Elm decoders - TWTS 04
By sebbes
Json and Elm decoders - TWTS 04
- 928