JSON decoding: 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 = “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.

Hm. Your code looks fine to me, I can’t see the mistake.
I’d try to Debug this code by simplifying it: e.g. replace the Book datatype with a Json.Decode.Value for now, and see whether it parses the list successfully. Then try only parsing one key or so.

You’ll likely have further questions. For tighter feedback loops I can recommend the beginners or general channel on the elm slack: https://elmlang.herokuapp.com/

Hi, thank you so much for the help! Is it ok if I put my entire script here just in case?

Sure. You can also link to this post.

Please look up how to insert formatted code the next time though. This makes it easier for others to read and help. :slight_smile:

Oh I misunderstood you. Yeah for full scripts, I recommend pasting a runnable version to https://ellie-app.com/

Thank you so much! Here’s the full code:

module HomePage exposing (main)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http
import Json.Encode
import Json.Decode exposing (Decoder, map2, field, list, string)

type alias Book =
    { title : String
    , author : String}

-- VIEW
view : Model -> Html Msg
view model =
    div [ class "jumbotron" ]
        [ h1 [] [ text "Welcome to Gutenberg Project Book Recommender" ]
        , h2 [] [text "Find books similar to: "]
        , viewInput "text" "Keyword" model.keyword Keyword
        , button [ onClick SubmitForm ] [ text "Go!" ]
        , viewSearch model]

viewInput : String -> String -> String -> (String -> msg) -> Html msg
viewInput t p v toMsg =
  input [ type_ t, placeholder p, value v, onInput toMsg ] []

viewSearch: Model -> Html Msg
viewSearch model =
  case model.searchResponse of
    SearchLoading ->
      div []
        [ text "Waiting..." ]
    SearchFailure ->
      div []
        [ text "Search failed, please try again." ]
    SearchSuccess list ->
      div []
        [ text "Are you looking for..." ]
        -- this branch is for test purpose and incomplete

-- MODEL
type alias Model =
  { keyword : String
  , searchResponse : SearchStatus}

type SearchStatus
  = SearchLoading
  | SearchFailure
  | SearchSuccess (List Book)

initialModel : Model
initialModel =
    { keyword = ""
    , searchResponse = SearchLoading
    }

init _ =
  (initialModel, Cmd.none)

-- UPDATE
type Msg
  = Keyword String
  | NoOp
  | SubmitForm
  | GotSearch (Result Http.Error (List Book))

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
  case msg of
    Keyword keyword ->
      ( { model | keyword = keyword }, Cmd.none )
    NoOp ->
      ( model, Cmd.none )
    SubmitForm ->
      ( { model | searchResponse = SearchLoading }, search model)
    GotSearch result ->
      case result of
        Ok list ->
          ({ model | searchResponse = SearchSuccess list }, Cmd.none)
        Err error ->
          let
            _ = Debug.log "Error is " error
          in
            ({ model | searchResponse = SearchFailure }, Cmd.none)

-- HTTP
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
    }

searchDecoder : Decoder (List Book)
searchDecoder =
    Json.Decode.list bookDecoder

bookDecoder : Decoder Book
bookDecoder =
    map2 Book
        (field "title" string)
        (field "author" string)

-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none

main =
  Browser.element
    { init = init
    , update = update
    , subscriptions = subscriptions
    , view = view}

Still can’t see it. :frowning:

Can you try posting the raw response of the request to 0.0.0.0:5000/search? You can find the response text in the developer tools of your browser in the “network” tab after your app made the request.

Of course! Here it is:

“JSON”:[{“title”:“Songs, Merry and Sad”,“author”:“John Charles McNeill”},{“title”:“The Ambassadors”,“author”:“Henry James”}]

Ah! I think you need to wrap your decoder with field "JSON" :slight_smile:

searchDecoder : Decoder (List Book)
searchDecoder =
    field "JSON" (list bookDecoder)

Sorry, still got the same problem :frowning: Thank you so much though!

You can test your decoders like so, without having to involve Http:

https://ellie-app.com/9MqcYmv6JPga1

Paste the exact raw JSON output from your API in there (the sample variable) and see it you get Ok or Err. I tried to guess what your raw JSON is, and if I got it right your decoders seem to work!

Once you know your decoders work you can continue with Http. Hopefully breaking the task up in small steps will help you succeed.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.