用于非字符串JSON数组的Elm JSON解码器

问题描述

我正在学习Elm,并且碰上了JSON解码器。我正在构建一个Elm前端,我想从后端API馈送URL,该API返回以下URL字符串的JSON数组:

["http://www.example.com?param=value","http://www.example2.com?param=value"]

如果我尝试使用Elm中的“列表字符串”解码器解析此字符串,则会失败,因为上面的内容未作为字符串引用或转义。我的示例解码器:

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

之所以失败,是因为在Elm中将JSON数组视为一个字符串列表,而不是Json.Decode.decodeString函数接受的平面字符串。如果我要引用并转义数组以与该解码器一起使用,则它将采用以下格式:

"[\"http://www.example.com?param=value\",\"http://www.example2.com?param=value\"]"

如何编写Elm解码器,以我的API返回的格式解析未引用/未转义的JSON数组?


更新 我认为我未能很好地传达我的问题;为此我深表歉意。 API返回此值(有效的JSON):

["http://www.example.com?param=value","http://www.example2.com?param=value"]

我正在使用Http.get捕获此JSON,并尝试对结果运行解码器,该解码器失败,因为它不是字符串。

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

由于Elm在不将其转义为字符串的情况下无法接受此JSON,我可以使用Http.get调用在将其解析为解码器之前将其转换为JSON吗?还是由于对Elm的经验不足,我只是错过了明显的东西?如果可以进一步说明,请告诉我,谢谢您的帮助!


最终编辑 事实证明,在使用罗伯特的出色示例之后,我自己由于一个不相关的问题而失败了。作为Elm的Http包的新手,我不知道Http.NetworkError消息,Robert的Http错误函数处理该消息。这导致我进入this question,并最终发现API中的CORS配置错误

解决方法

Elm模块中不能存在裸JSON。编写要在Elm中解码的示例JSON时,必须用其他表示形式包装。

但是,该表示特定于Elm;解码器希望从API接收到的不是

当后端发送文本JSON ["a","b"]时,您的解码器应该可以工作。


在代码中:

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

编辑:Here's an example适合与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
        }

running in elm-reactor