Elm JSON decoding into list of objects: BadBody error - json

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.

Related

Unexpected end-of-input: expected close marker for Object while consuming messages from Kafka topic

I am trying to consume messages from kafka-topic in which json contents are not serialized. The topic has been produced with actual JSON without serialization like below.
JSON:
{
"guests": [
{
"guest_ref_id": "000000012331202",
"ids": {
"profile_ids": [
"1234"
]
}
}
],
"case_id": "500g000000Tw5ggAAB",
"case_creation_ts": 1580512345,
"state_of_origin": "CA"
}
Now, when I try to consume the message from the topic using below configuration and listener-code, I am getting serialization exception as Unexpected end-of-input: expected close marker for Object
application.yml
topics:
input:
datasource: test-topic
---
kafka:
bootstrap:
servers: localhost:9092
consumers:
consumer:
key:
deserializer: org.apache.kafka.common.serialization.StringDeserializer
value:
deserializer: org.apache.kafka.connect.json.JsonDeserializer
Listener Code:
#Topic(value = ["\${topics.input.datasource}"])
fun receiveNotifications(
#Suppress("UNUSED_PARAMETER") #KafkaKey keys: List<String?>,
#MessageBody notifications: List<GuestDTO>,
topics: List<String>,
partitions: List<Int>,
offsets: List<Long>,
kafkaConsumer: Consumer<String, GuestDTO>
) = runBlocking {
notifications.forEach { notification ->
// business logic
}
logger.info("Delete Request Processed -> Commiting Offset")
kafkaConsumer.commitSync()
}
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class GuestDTO(
var guests: List<Guest>? = null,
var caseId: String = ""
)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class Guest(
var guestRefId: String = "",
var ids: Map<String, List<String>>? = null
)
I am not sure exactly how to handle this, the above setup works when the json messages are serialized and produced to the topic without any issues.
But the catch is there is no control over for me to produce the serialized JSON message. So, looking for a way to consume non-serialized JSON messages from kafka topics. Thanks in advance for the help to be provided!
If the data isn't valid JSON, then you must use StringDeserializer, then try-catch the JSON parsing yourself.
Your other option is to introduce a Schema Registry, but if you don't control the producers, then it won't fully solve the problem

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

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
}

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"]

Decode nested JSON value in Elm (0.18)

Given the following types:
type alias Wrapper =
{ data : Data }
type alias Data =
{ name : String }
And the following JSON:
{"data": {"name": "Keith"}}
How can I write a decoder which will allow me to turn an HTTP response into an instance of the Wrapper type alias?
I've tried a number of approaches using the core libraries, Json.Decode.Pipeline and Json.Decode.Extra, without finding a workable solution.
Here's my latest attempt:
dataDecoder =
succeed Data
|> andMap (field "name" Decode.string)
wrapperDecoder =
succeed Wrapper
|> andMap (field "data" dataDecoder)
Which results in:
BadPayload "Expecting an object with a field named name but instead got: {\"data\":{\"name\":\"foo\"}}" { status = { code = 200, message = "OK" }, headers = Dict.fromList [("cache-control","max-age=0, private, must-revalidate"),("content-type","application/json; charset=utf-8")], url = "http://localhost:5000//users/foo", body = "{\"data\":{\"name\":\"foo\"}}" }
EDIT:
This wound up being an end-user problem. I was passing the correct decoder to Http.post, but Http.send wasn't actually calling the function wrapping Http.post. Doh.
Your decoders work fine against your example input, but the error message you are getting leads me to believe you are trying to use dataDecoder in your Http call rather than wrapperDecoder, since the error message is looking for a field named name.
While succeed and andMap can be used to construct your decoder, you can get by with map:
dataDecoder : Decoder Data
dataDecoder =
Decode.map Data (field "name" string)
wrapperDecoder : Decoder Wrapper
wrapperDecoder =
Decode.map Wrapper (field "data" dataDecoder)
As Chad Gilbert wrote, your decoders are fine: https://ellie-app.com/kDX99XRbta1/0
To doublecheck, add type annotations to your decoders:
dataDecoder : Decoder Data
dataDecoder = ...
wrapperDecoder : Decoder Wrapper
wrapperDecoder = ...
If you're really using wrapperDecoder (Http.post apiUrl body wrapperDecoder), there's one more possibility for an error: that your API endpoint returns data with a different shape, something like:
{"data": {"data": {"name": "foo"}}}
Can you doublecheck this? (in Chrome's Web Inspector etc.)