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"]
Related
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
}
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.
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") ]
I'm trying to extract certain fields from Scala object before converting to Json. Is there an easy way to do this.
It would also work if i could make a new Json with certain fields from a Json.
You can simply extract out the value of a Json and scala gives you the corresponding map. Example:
var myJson = Json.obj(
"customerId" -> "xyz",
"addressId" -> "xyz",
"firstName" -> "xyz",
"lastName" -> "xyz",
"address" -> "xyz"
)
Suppose you have the Json of above type. To convert it into map simply do:
var mapFromJson = myJson.value
This gives you a map of type : scala.collection.immutable.HashMap$HashTrieMap
Hard to say without more details. Suppose that you have the following Scala case class...
case class SomeObject(customerId: Long, addressId: Long, firstName: String, lastName: String, address: String)
...and that you wanted to extract the 'firstName', 'lastName', and address fields and then convert the object to Json. Using play-json you could define an implicit conversion on the companion object for the SomeObject class...
object SomeObject {
implicit val someObjectWrites = new Writes[SomeObject] {
def writes(object: SomeObject) = Json.obj(
"firstName" -> object.firstName,
"lastName" -> object.lastName,
"address" -> object.address
)
}
}
Then you could just use the code as follows:
val obj = SomeObject(12345, 678910, "John", "Doe", "My Address")
val json = Json.toJson(obj)
Note that there are probably other JSON libraries, besides play-json, that support similar functionality.
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.