Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read in data from JSON file

Tags:

json

elm

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.

like image 366
Stanko Avatar asked Dec 18 '15 14:12

Stanko


People also ask

How do I view data in a JSON file?

Cross-platform to open JSON files: Generally, users can open the JSON file in any text editor as it is a plain text-based file. The Google Chrome and Mozilla Firefox web browsers are cross-platform to open JSON files that are compatible with every operating system (OS).

Can we extract the data from JSON file?

To extract the name and projects properties from the JSON string, use the json_extract function as in the following example. The json_extract function takes the column containing the JSON string, and searches it using a JSONPath -like expression with the dot . notation. JSONPath performs a simple tree traversal.

How do I parse a JSON file?

If you need to parse a JSON string that returns a dictionary, then you can use the json. loads() method. If you need to parse a JSON file that returns a dictionary, then you can use the json. load() method.


1 Answers

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)
like image 173
Chad Gilbert Avatar answered Oct 06 '22 19:10

Chad Gilbert