Search code examples
jsonelm

Elm: Parse nested json


I'm trying to parse some JSON but nothing seems to be loading. When I load the page I can see the JSON response in the Chrome debugger but the view loads the "Network Error" text.

JSON Payload

{"results":[{"gender":"female","name":{"title":"Miss","first":"آیناز","last":"سلطانی نژاد"},"location":{"street":{"number":2947,"name":"شهید رحمانی"},"city":"شیراز","state":"بوشهر","country":"Iran","postcode":80573,"coordinates":{"latitude":"33.5772","longitude":"113.9587"},"timezone":{"offset":"-3:30","description":"Newfoundland"}},"email":"aynz.sltnynjd@example.com","login":{"uuid":"25054693-ce95-451b-8b35-dbcb518c4911","username":"greenswan774","password":"call","salt":"7JvweTbR","md5":"87b9692b8d3c50f2addc99763ffd6b44","sha1":"0480a79a2087646add3f54261ce9871abae40839","sha256":"a71d7407cecac726852c2abf4a1fb15e9c67c9ce99ce9d8f0783463101606ff4"},"dob":{"date":"1988-03-28T06:33:18.348Z","age":33},"registered":{"date":"2014-07-03T22:30:18.654Z","age":7},"phone":"005-72600454","cell":"0936-203-1121","id":{"name":"","value":null},"picture":{"large":"https://randomuser.me/api/portraits/women/33.jpg","medium":"https://randomuser.me/api/portraits/med/women/33.jpg","thumbnail":"https://randomuser.me/api/portraits/thumb/women/33.jpg"},"nat":"IR"}],"info":{"seed":"e887b219e4314703","results":1,"page":1,"version":"1.3"}}

Model.elm

module Model exposing (..)

import Json.Decode as Decode exposing (Decoder, int, string, float)
import Json.Decode.Pipeline exposing (required, optional, hardcoded)
import Html.Attributes exposing (list)


-- {"results": [
--   {
--     "gender": "male",
--     "name": {
--       "title": "Mr",
--       "first": "Habacuc",
--       "last": "da Mota"
--     },
--     "location": {
--       "street": {
--         "number": 9305,
--         "name": "Rua Dois"
--       },
--       "city": "São Leopoldo",
--       "state": "Mato Grosso do Sul",
--       "country": "Brazil",
--       "postcode": 94950,
--       "coordinates": {
--         "latitude": "-78.8074",
--         "longitude": "-143.2939"
--       },
--       "timezone": {
--         "offset": "0:00",
--         "description": "Western Europe Time, London, Lisbon, Casablanca"
--       }
--     },
--     "email": "habacuc.damota@example.com",
--     "login": {
--       "uuid": "747e4d7f-31fd-4607-ac85-32b2c91e8822",
--       "username": "lazyfrog284",
--       "password": "shooter",
--       "salt": "mf82yaB6",
--       "md5": "faf67ad9fc3aa0962211b2722ecb18f1",
--       "sha1": "1ea07c8be37a3fe1d647ad801597f4335a976ba9",
--       "sha256": "a685e68ff871073a1d04488ff3ae46235e60b61663ff92b92b850460b75bf79b"
--     },
--     "dob": {
--       "date": "1953-02-25T08:30:12.318Z",
--       "age": 68
--     },
--     "registered": {
--       "date": "2014-07-13T17:56:19.517Z",
--       "age": 7
--     },
--     "phone": "(76) 0855-1123",
--     "cell": "(13) 3195-1920",
--     "id": {
--       "name": "",
--       "value": null
--     },
--     "picture": {
--       "large": "https://randomuser.me/api/portraits/men/15.jpg",
--       "medium": "https://randomuser.me/api/portraits/med/men/15.jpg",
--       "thumbnail": "https://randomuser.me/api/portraits/thumb/men/15.jpg"
--     },
--     "nat": "BR"
--   }
-- ],
-- "info": {
--   "seed": "4ad06236fb06ef08",
--   "results": 1,
--   "page": 1,
--   "version": "1.3"
-- }
-- } 

type alias ProfileName = 
    {
        title: String
        , first: String
        , last: String
    }

type alias Login = 
    {
        uuid: String
        , username: String
        , password: String
        , salt: String
        , md5: String
        , sha1: String
        , sha256: String
    }

type alias Location = 
    {
        street: Street
        ,city: String
        , state: String
        , country: String
        , postcode: String
        , coordinates: Coordinates

    }

type alias Street = 
    {
        name: String
        , number: String
    }

type alias Dob = 
    {
        date: String
        , age: String
    }

type alias Coordinates =   
    {
        latitude: String
        , longitude: String
    }

type alias Picture  = 
    {
        large: String
        , medium: String
        , thumbnail: String
    }

type alias Registered = 
    {
        date: String
        , age: String
    }


type alias Profile = 
            {
                gender: String
                , name: ProfileName
                , location: Location
                , email: String
                , login: Login
                , dob: Dob
                , phone: String
                , cell: String
                , registered: Registered
                , picture: Picture
                , nat: String
                
            }


resultsDecoder: Decoder JsonResults
resultsDecoder =
    Decode.succeed JsonResults
        |> required "results" profilesDecoder


coordinatesDecoder: Decoder Coordinates
coordinatesDecoder = 
    Decode.succeed Coordinates
        |> required "latitude" string
        |> required "longitude" string


dobDecoder: Decoder Dob
dobDecoder = 
    Decode.succeed Dob
        |> required "date" string
        |> required "age" string


registeredDecoder: Decoder Registered
registeredDecoder = 
    Decode.succeed Registered
        |> required "date" string
        |> required "age" string

pictureDecoder: Decoder Picture
pictureDecoder = 
    Decode.succeed Picture
        |> required "large" string
        |> required "medium" string
        |> required "thumbnail"  string

loginDecoder: Decoder Login
loginDecoder = 
    Decode.succeed Login
        |> required "uuid" string
        |> required "username" string
        |> required "password" string
        |> required "salt" string
        |> required "md5" string
        |> required "sha1" string
        |> required "sha256" string


streetDecoder: Decoder Street
streetDecoder = 
    Decode.succeed Street
        |> required "name" string
        |> required "number" string


locationDecoder: Decoder Location
locationDecoder = 
    Decode.succeed Location
        |> required "street" (streetDecoder)
        |> required "city" string
        |> required "state" string
        |> required "country" string
        |> required "postcode" string
        |> required "coordinates" (coordinatesDecoder)


profileNameDecoder: Decoder ProfileName
profileNameDecoder = 
    Decode.succeed ProfileName
        |> required "title" string
        |> required "first" string
        |> required "last" string

profileDecoder: Decoder Profile
profileDecoder = 
    Decode.succeed Profile
        |> required "gender" string
        |> required "name" (profileNameDecoder)
        |> required "location" (locationDecoder)
        |> required "email" string
        |> required "login" (loginDecoder)
        |> required "dob" (dobDecoder)
        |> required "phone" string
        |> required "cell" string
        |> required "registered" (registeredDecoder)
        |> required "picture" (pictureDecoder)
        |> required "nat" string

type alias Profiles = 
    List Profile
profilesDecoder: Decoder Profiles
profilesDecoder = 
    Decode.list profileDecoder

type alias JsonResults =
    {
        results: List Profile
    }

Main.elm

module GetProfile exposing (..)

import Browser
import Html exposing (..)
import Html.Events exposing (onClick)

import Http 
import RemoteData exposing (..)
import Model exposing (Profile, Profiles, JsonResults)
import Http exposing (Response(..))

type alias Model = 
    {
        results: WebData JsonResults
    }



init: Model
init = 
    {results = Loading}

type Msg
    = ResultsResponse (WebData JsonResults)


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ResultsResponse response ->
            ( { model | results = response }
            , Cmd.none
            )

getProfiles : Cmd Msg
getProfiles = 
    Http.get
        { url = "https://randomuser.me/api/"
        , expect =
            Http.expectJson
                (RemoteData.fromResult >> ResultsResponse)
                Model.resultsDecoder
        }


viewProfile : Profile -> Html msg
viewProfile profile =
    div
        []
        [ h2 [] [ text profile.email ]
        , p [] [ text profile.gender ]
        ]

viewProfiles : JsonResults -> Html msg
viewProfiles jsonresult =
    div [] (List.map viewProfile jsonresult.results)


view : Model -> Html msg
view model =
    case model.results of
        NotAsked ->
            div [] [ text "Initializing" ]

        Loading ->
            div [] [ text "Loading" ]

        Failure _ ->
            div [] [ text "Network Error"]

        Success results ->
            viewProfiles results

main : Program () Model Msg
main =
    Browser.element
        { init = \_ -> ( init, getProfiles )
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }

Solution

  • Try using Debug.log to get more information about the error:

            Failure err ->
                let
                    _ = Debug.log "Failed" err
                in
                div [] [ text "Network Error"]
    

    It would be very difficult to pin down the decoding error otherwise.