Read in data from JSON file

2020-06-19 07:37发布

问题:

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.

回答1:

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)


标签: json elm