Say I have a JSON file located at http://www.randomurl.com/jobs.json, it looks like this:
{ "jobs": [
{ "task" : "turn burgers" ,
"who" : "Anni" ,
"place" : "Quick"}
,
{ "task" : "dishes" ,
"who" : "Bob" ,
"place" : "McDo"}
]}
I've made a decoder:
type alias Job = {
task : String
, who : String
, place: String
}
type alias Jobs = List Job
decoder : Decoder Job
decoder =
Decode.object3 Job
(Decode.at ["attributes", "task"] Decode.string)
(Decode.at ["attributes", "who"] Decode.string)
(Decode.at ["attributes", "place"] Decode.string)
decoderColl : Decoder Jobs
decoderColl =
Decode.object1 identity
("jobs" := Decode.list decoder)
How can I read in the file from that website using my decoder? I presume I need the Http package but I don't know how to apply it.
First off - your decoder
function is slightly off. There is no intermediate "attributes" object, so you can change it to this:
decoder : Decoder Job
decoder =
Decode.object3 Job
("task" := Decode.string)
("who" := Decode.string)
("place" := Decode.string)
You are correct that you'll need the elm-http package. Using this, you can create an Http.get
task which maps the result to an action.
As a basic example, let's make a button that pulls down the list of jobs from a url. We'll need a GetJobs
action to trigger the HTTP request, and a ShowJobs
action which will be trigged when the request returns successfully.
Assuming our Action type looks like this:
type Action
= NoOp
| GetJobs
| ShowJobs (Maybe Jobs)
Then we can create a getJobs
function that builds a task that can be run. For this simple example, we can use Task.toMaybe
to suppress any HTTP or JSON decoding errors.
getJobs : Effects Action
getJobs =
Http.get decoderColl jobsUrl
|> Task.toMaybe
|> Task.map ShowJobs
|> Effects.task
In order to glue it all together, we'll use StartApp since it lets us use tasks and effects. Here's a working example that you can build locally, assuming that jobs.json exists in the same directory.
import Http
import StartApp
import Effects exposing (Effects,Never)
import Task
import Html exposing (..)
import Html.Events exposing (..)
import Json.Decode as Decode exposing (Decoder, (:=))
jobsUrl = "./jobs.json"
-- StartApp plumbing
app =
StartApp.start { init = init, view = view, update = update, inputs = [] }
main =
app.html
port tasks : Signal (Task.Task Never ())
port tasks =
app.tasks
type Action
= NoOp
| GetJobs
| ShowJobs (Maybe Jobs)
type alias Model =
{ jobs : Maybe Jobs }
init =
({ jobs = Nothing }, Effects.none)
update action model =
case action of
NoOp ->
(model, Effects.none)
GetJobs ->
({ model | jobs = Nothing }, getJobs)
ShowJobs maybeJobs ->
({ model | jobs = maybeJobs }, Effects.none)
view address model =
div []
[ button [ onClick address GetJobs ] [ text "Click to get jobs!" ]
, viewJobs model.jobs
]
viewJobs maybeJobs =
let
viewJob job =
li [] [ text ("Task: " ++ job.task ++ "; Who: " ++ job.who ++ "; Place: " ++ job.place) ]
in
case maybeJobs of
Nothing ->
div [] [ text "No jobs to display. Try clicking the button" ]
Just jobs ->
ul [] (List.map viewJob jobs)
-- This is the key to map the result of the HTTP GET to an Action
-- Note: Task.toMaybe swallows any HTTP or JSON decoding errors
getJobs : Effects Action
getJobs =
Http.get decoderColl jobsUrl
|> Task.toMaybe
|> Task.map ShowJobs
|> Effects.task
-- An alternative to Task.toMaybe which dumps error information to the console log
toMaybeWithLogging : Task.Task x a -> Task.Task y (Maybe a)
toMaybeWithLogging task =
Task.map Just task `Task.onError` (\msg -> Debug.log (toString msg) (Task.succeed Nothing))
-- The Job type aliases from the question
type alias Job = {
task : String
, who : String
, place: String
}
type alias Jobs = List Job
-- The updated Job decoder
decoder : Decoder Job
decoder =
Decode.object3 Job
("task" := Decode.string)
("who" := Decode.string)
("place" := Decode.string)
decoderColl : Decoder Jobs
decoderColl =
Decode.object1 identity
("jobs" := Decode.list decoder)