Elm JSON Decoder for non-string JSON Array [closed] - json

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I'm in the process of learning Elm and have run into a wall with JSON decoders. I am building an Elm frontend that I want to feed URLs from a backend API, which returns the following JSON array of URL strings:
["http://www.example.com?param=value","http://www.example2.com?param=value"]
If I try to parse this with a List String decoder in Elm, it fails because the above is not quoted or escaped as a string. My example decoder:
stringListDecoder : Json.Decode.Decoder (List String)
stringListDecoder = Json.Decode.list Json.Decode.string
myArrDec2 = D.list D.string
D.decodeString myArrDec2 ["http://www.example.com?param=value","http://www.example2.com?param=value"]
This fails because the JSON array is seen in Elm as a string list, as opposed to a flat string that the Json.Decode.decodeString function accepts. If I were to quote and escape the array to work with this decoder, it would be in this format:
"[\"http://www.example.com?param=value\",\"http://www.example2.com?param=value\"]"
How can I write an Elm decoder that can parse the unquote/unescaped JSON array in the format being returned by my API?
UPDATE
I think I failed to convey my question well enough; for that I apologize. The API is returning this value (which is valid JSON):
["http://www.example.com?param=value","http://www.example2.com?param=value"]
I'm grabbing this JSON with an Http.get and trying to run the decoder on the result, which fails because it is not a string.
-- HTTP
getUrls : Cmd Msg
getUrls =
Http.get
{ url = "http://127.0.0.1:3000/request" -- This is the endpoint returning ["http://www.example.com?param=value","http://www.example2.com?param=value"]
, expect = Http.expectJson GotUrls urlDecoder
}
urlDecoder : Decoder (List String)
urlDecoder =
Json.Decode.list Json.Decode.string
Since Elm cannot accept this JSON without escaping it as a string, could I use the Http.get call to convert it to one prior to parsing with my decoder? Or am I just missing something obvious due to my inexperience with Elm? Please let me know if I can clarify further and thank you for trying to help!
FINAL EDIT
It turns out after using Robert's excellent example that my own was failing due to an unrelated issue. Being new to Elm's HTTP package I was unaware of the Http.NetworkError msg, which Robert's Http error function handles. This led me to this question and ultimately revealed a CORS misconfiguration in the API.

Bare JSON cannot exist within Elm modules. When writing example JSON to be decoded in Elm, it must be wrapped in some other representation.
However, that representation is specific to Elm; it is not what the decoder expects to receive from the API.
Your decoder should work when the backend sends the literal JSON ["a","b"].
In code:
module Example exposing (..)
import Json.Decode as JD
stringListDecoder : JD.Decoder (List String)
stringListDecoder =
JD.list JD.string
{-| Bare JSON cannot exist inside Elm syntax; it must be wrapped in something
else. In this case, in a string.
-}
jsonFromApi : String
jsonFromApi =
"[ \"http://www.example.com?param=value\", \"http://www.example2.com?param=value\" ]"
decoded : Result JD.Error (List String)
decoded =
JD.decodeString stringListDecoder jsonFromApi
expectedResult : Result JD.Error (List String)
expectedResult =
Result.Ok [ "http://www.example.com?param=value", "http://www.example2.com?param=value" ]
decodesAsExpected : Bool
decodesAsExpected =
decoded == expectedResult
EDIT: Here's an example suitable to run with elm reactor:
src/request.json
["http://www.example.com?param=value","http://www.example2.com?param=value"]
src/Main.elm
module Main exposing (..)
import Browser
import Html exposing (Html, code, div, p, pre, span, text)
import Http
import Json.Decode as JD
type alias Model =
{ data : RemoteData Http.Error (List String) }
type RemoteData err a
= NotAsked
| Loading
| Loaded a
| Failed err
type Msg
= GotUrls (Result Http.Error (List String))
initialModel =
{ data = NotAsked }
getUrls : Cmd Msg
getUrls =
Http.get
{ url = "/src/request.json"
-- This is the endpoint returning ["http://www.example.com?param=value","http://www.example2.com?param=value"]
, expect = Http.expectJson GotUrls urlDecoder
}
urlDecoder : JD.Decoder (List String)
urlDecoder =
JD.list JD.string
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GotUrls result ->
case result of
Ok strings ->
( { model | data = Loaded strings }, Cmd.none )
Err error ->
( { model | data = Failed error }, Cmd.none )
view : Model -> Html Msg
view model =
div []
(case model.data of
NotAsked ->
[ text "no data yet" ]
Loading ->
[ text "loading" ]
Failed err ->
[ text "Failed... ", show err ]
Loaded strings ->
strings |> List.map (\s -> p [] [ text s ])
)
show : Http.Error -> Html Msg
show error =
case error of
Http.BadUrl string ->
span [] [ text "Bad Url: ", text string ]
Http.Timeout ->
text "Timeout"
Http.NetworkError ->
text "Network error"
Http.BadStatus int ->
span [] [ text "Bad Status: ", int |> String.fromInt |> text ]
Http.BadBody string ->
span [] [ text "Bad Body: ", pre [] [ code [] [ text string ] ] ]
main : Program () Model Msg
main =
Browser.element
{ init = always ( initialModel, getUrls )
, view = view
, update = update
, subscriptions = always Sub.none
}

Related

Elm JSON decoding into list of objects: BadBody error

I'm super new to elm and I am currently having problem with parsing a HTTP response, which is in the form of JSON. It looks something like this:
[
{
"title":"Songs, Merry and Sad",
"author":"John Charles McNeill"
},
{
"title":"The Ambassadors",
"author":"Henry James"
}
]
Basically, my web app sends a request to my backend, which responds with a JSON file as such. I have created this type alias:
type alias Book =
{ title : String
, author : String}
In addition, here is my HTTP request:
search : Model -> Cmd Msg
search model =
Http.post
{ url = "http://0.0.0.0:5000/search"
, body = Http.multipartBody [Http.stringPart "keyword" model.keyword]
, expect = Http.expectJson GotSearch searchDecoder
}
And here is my decoder:
searchDecoder : Decoder (List Book)
searchDecoder =
Json.Decode.list bookDecoder
bookDecoder : Decoder Book
bookDecoder =
map2 Book
(field "title" string)
(field "author" string)
Despite clearly stating expectJSON in my HTTP request, I get this BadBody error with the following message:
Problem with the value at json[0]:\n\n {\n \"title\": \"Songs, Merry and Sad\",\n \"author\": \"John Charles McNeill\"\n }\n\nExpecting a STRING
I'm not sure what's going on here. Help would be greatly appreciated since I'm super stuck here.

Access field of type after Json decode in elm

I'm trying to do some small steps with elm width decoding a json to an type. My type is e.g.:
type alias Project =
{ title : String
, description : String
}
and my function with the decode logic and display the title of a project.
render : (String, String) -> Html Msg
render (jsonString, otherString) =
let project = Decode.decodeString projectDecoder (jsonString)
in div [] [ text (project.title) ]
projectDecoder : Decode.Decoder Project
projectDecoder =
Decode.map2
Project
(Decode.at [ "title" ] Decode.string)
(Decode.at [ "description" ] Decode.string)
But I've got a error which indicate that the decoder will return a Error instead a project.
This is not a record, so it has no fields to access!
32| in div [] [ text (project.title) ]
This project value is a:
Result Decode.Error Project
But I need a record with a title field!
Reading the elm guide carefully will help to solve this problem: https://guide.elm-lang.org/error_handling/result.html
We need to handle both cases of Ok and Err with a case.
render : (String, String) -> Html Msg
render (jsonString, otherString) =
let project = Decode.decodeString projectDecoder (jsonString)
in
case project of
Ok status -> div [] [ text (status.title) ]
Err _ -> div [] [ text ("A error occurs") ]

Decoding errors in elm 0.19

I am new to elm and I having a very hard time parsing a json from html to elm and using it.
This is what I am trying to do:
In my html doc:
var app = Elm.Main.init({
node: document.getElementById("app"),
flags: questions
});
then in elm:
main =
Browser.element { init = init, update = update, subscriptions = subscriptions, view = view }
-- MODEL
type alias Model =
{
questionsToAnswer: List QuestionToAnswer
, currentQuestion: Int
, initializeGame: Bool
}
type alias QuestionToAnswer =
{
question: String
, a: String
, b: String
, c: String
, d: String
, answer: String
}
questionDecoder : Decoder QuestionToAnswer
questionDecoder =
map6 QuestionToAnswer
(field "question" string)
(field "A" string)
(field "B" string)
(field "C" string)
(field "D" string)
(field "answer" string)
init : Json.Decode.Value -> (Model, Cmd Msg)
init questions =
(Model (getQuestions questions) 0 True, Cmd.none)
getQuestions : Json.Decode.Value -> List QuestionToAnswer
getQuestions questions =
case(decodeValue questionDecoder questions) of
Ok question ->
[question]
_ ->
[ QuestionToAnswer "me" "me" "me" "me" "me" "me"]
My json looks like this:
{
"question": "In mobster lingo, if you 'sing like a canary' what are you doing?",
"A": "impersonating Sinatra",
"B": "talking to the cops",
"C": "killing an enemy",
"D": "betting on horses",
"answer": "B"
}
I am outputting all the response as debug.toString in the view just to see what is happening because I also don't know how to log the error that was being produced before. The compiler says my call to getQuestions questions produces a Json.Decode.error which I find difficult to believe because I think everything looks ok to me.
The error:
"{\"question\":\"In mobster lingo, if you 'sing like a canary' what are you doing?\",\"A\":\"impersonating Sinatra\",\"B\":\"talking to the cops\",\"C\":\"killing an enemy\",\"D\":\"betting on horses\",\"answer\":\"B\"}" Expecting an OBJECT with a field named question
It looks like you're passing a string in as flags, not a JSON object. If so, you can do one of two things:
1: Parse the JSON string on the JavaScript side, before passing it to Elm via flags:
var app = Elm.Main.init({
node: document.getElementById("app"),
flags: JSON.parse(questions)
});
The downside to this approach is that if an error happen during parsing it happens on the JavaScript-side and has to be handled there. If you need to deal with the error in Elm, you have to pass a more complex structure to flags that can represent both errors and success.
2: Use decodeString instead of decodeValue, and change the type of init and getQuestions accordingly:
init : String -> (Model, Cmd Msg)
init questions =
(Model (getQuestions questions) 0 True, Cmd.none)
getQuestions : String -> List QuestionToAnswer
getQuestions questions =
case (decodeString questionDecoder questions) of
Ok question ->
[question]
_ ->
[ QuestionToAnswer "me" "me" "me" "me" "me" "me"]

Nothing returned from Elm Http.send - just original model

I'm learning Elm and am making fair progress. But I can't get Http.send to work. The expected JSON string wasn't returned from the server, but it works directly in the browser. My JSON decoders look solid and my Http.send is as per the texts I've read. When compiling with --debug, the model isn't changed during a run. I'm green at debugging Elm, so I just created some dummy return models in update to see at which stage it's failing. In the code below, strictly for debugging purposes, I always get back companyTypeList1 ("DIDN'T RUN"), which appears to mean that my Cmd - listCompanyTypes - isn't being called at all. But I don't get any errors. Is there any reason the Cmd would not run or just return the initial model without doing anything? This is Elm 0.18. I can't find any topics on this problem in SO or the web, so I must be doing something stoopid. Any help would be appreciated.
import Http exposing (..)
import Json.Decode as Decode exposing (..)
import Objects.CompanyTypes.Model exposing (..)
import Objects.CompanyTypes.Msg exposing (..)
update : Msg -> List CompanyType -> ( List CompanyType, Cmd Msg )
update msg companyTypeList =
let
companyTypeList1 =
[ { id = 0, name = "DIDN'T RUN" } ]
companyTypeList2 =
[ { id = 0, name = "GOOD RETURN" } ]
companyTypeList3 =
[ { id = 0, name = "ERROR RETURN" } ]
in
case msg of
List ->
( companyTypeList1, listCompanyTypes )
ListResult (Ok newCompanyTypeList) ->
( companyTypeList2, Cmd.none )
ListResult (Err _) ->
( companyTypeList3, Cmd.none )
listCompanyTypes : Cmd Msg
listCompanyTypes =
Http.send ListResult (Http.get "http://laserver:8080/company_types" decodeCompanyTypes)
decodeCompanyTypes : Decoder (List CompanyType)
decodeCompanyTypes =
list decodeCompanyType
decodeCompanyType : Decoder CompanyType
decodeCompanyType =
map2 CompanyType
(field "id" int)
(field "name" string)
UPDATE: I read that CORS could be the problem so I'm checking it on my Apache server. I've enabled mod_header and added a 'Header set Access-Control-Allow-Origin "*"' entry to my site config. It's not working but I'll fiddle and report back.

Elm: Parsing a status code (with a potential error message) in JSON

I've been having a bit of difficulty wrapping my head around parsing JSON with Elm. I seem to have the basics down, but for some reason this small piece of JSON has left me stumped.
Here's the scenario: I am posting to a JSON API which will return with one of the two formats:
If the request is successful:
{
"status": "success",
"post": { ... }
}
If it fails:
{
"status": "error",
"message": "Some error message"
}
I'm trying to encode this JSON response into these data types:
type RequestStatus = Success | Error String
type alias CreatePostResponse =
{ status : RequestStatus
, post : Maybe Post }
So far I've had no luck. I've been looking through the JSON.Decode tutorial and this Thoughtbot article as guides, but neither seem to gotten me to the right place. Here's the code I have so far:
createPostResponse : Decoder CreatePostResponse
createPostResponse =
succeed CreatePostResponse
|: (("status" := string) `andThen` (("error" := string) `andThen` decodeStatus))
|: maybe ("post" := post)
decodeStatus : String -> String -> Decoder RequestStatus
decodeStatus status errorMessage =
succeed (case status of
"success" -> Success
"error" -> Error errorMessage
_ -> Error "unknown")
Obviously this produces all sorts of type errors and doesn't compile, but I haven't been able to come up with a good way to get the string from the "message" field into the RequestStatus type.
Does anyone have any ideas?
Here's a little working piece of code which should do what you are after. I've taken the liberty of assuming a basic shape for the Post type, for concreteness.
import Json.Decode exposing (..)
type alias Post = { title: String, body: String }
type RequestStatus = Success Post | Error String
post: Decoder Post
post = object2 Post ("title" := string) ("body" := string)
requestStatusData: String -> Decoder RequestStatus
requestStatusData status =
case status of
"success" -> object1 Success ("post" := post)
"error" -> object1 Error ("message" := string)
_ -> fail <| status ++ " is not a valid value for request status"
decodeStatus : Decoder RequestStatus
decodeStatus =
("status" := string) `andThen` requestStatusData
First, I turned the RequestStatus and CreatePostResponse types inside-out: instead of CreatePostResponse having a RequestStatus and a Maybe Post, which need to be kept in-sync with one another, the RequestStatus type itself models the fact that a success has a post, while an error does not. The new RequestStatus reads more like idiomatic Elm.
I then approached the decoding from the top-down:
To decode some JSON into a RequestStatus, we first deserialize the "status" property in the JSON object as a string, then we deserialize the rest based on what "status" was. The idiomatic way of doing this is with andThen (it looks like you knew this, but hadn't quite perfected the way it fit in with the rest). This translates to:
decodeStatus : Decoder RequestStatus
decodeStatus =
("status" := string) `andThen` requestStatusData
andThen decodes one record, then passes that record into a function which does the rest of the decoding, so requestStatusData needs to look like:
requestStatusData: String -> Decoder RequestStatus
requestStatusData is either a Success Post or a Error String. So we need two branches, then, associated with statuses "success" and "error" (plus a default to catch malformed statuses):
requestStatusData status =
case status of
"success" -> makeSuccessWithPost
"error" -> makeErrorWithString
_ -> fail <| status ++ " is not a valid value for request status"
We fill out the makeSuccessWithPost and makeErrorWithString implementations. The objectN functions in Json.Decode provide a facility to decode components, then feed them to a constructor (or other function):
object1 Success ("post" := post)
first decodes the "post" property using the decoder defined as post, then invokes the first argument of object1 (Success) on the result. Likewise,
object2 Post ("title" := string) ("body" := string)
decodes "title" using the string decoder, then decodes "body" using the string decoder, then invokes the Post function with the two decoded strings as its arguments. So we end up with:
requestStatusData status =
case status of
"success" -> object1 Success ("post" := post)
"error" -> object1 Error ("message" := string)
_ -> fail <| status ++ " is not a valid value for request status"
The last step is filling out the post decoder, which as mentioned above is a standard application of object2.
Overall, I think I've gone about this in fairly standard, idiomatic Elm style, but I'm quite new and I might have made some gaffes. It does work though! One last note: I would argue that the RequestStatus type is not actually necessary; the Result type in the Elm core library captures the idea of a type with a success and failure mode. You could use
Result String Post
instead of RequestStatus without losing any functionality (you'd need to change requestStatusData slightly but I'll leave that as an exercise for the reader...).