Elm:在 JSON 中解析状态代码(带有潜在的错误消息)

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

我在用 Elm 解析 JSON 时遇到了一些困难。我似乎没有掌握基础知识,但出于某种原因,这一小块 JSON 让我感到困惑。

场景如下:我将发布到 JSON API,它将 return 使用以下两种格式之一:

如果请求成功:

{
    "status": "success",
    "post": { ... }
}

如果失败:

{
    "status": "error",
    "message": "Some error message"
}

我正在尝试将此 JSON 响应编码为这些数据类型:

type RequestStatus = Success | Error String


type alias CreatePostResponse =
    { status : RequestStatus
    , post : Maybe Post }

到目前为止,我运气不好。我一直在浏览 JSON.Decode tutorial and this Thoughtbot article 作为指南,但似乎都没有把我带到正确的地方。这是我目前的代码:

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

显然这会产生各种类型错误并且无法编译,但我一直无法想出一个好方法将字符串从 "message" 字段获取到 RequestStatus类型。

有没有人有什么想法?

这是一小段工作代码,应该可以满足您的需求。为了具体起见,我冒昧地假设了 Post 类型的基本形状。

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

首先,我将 RequestStatusCreatePostResponse 类型从里到外翻转:而不是 CreatePostResponse 具有 RequestStatusMaybe Post,这需要为了保持彼此同步,RequestStatus 类型本身模拟了一个事实,即成功有一个 post,而错误没有。新的 RequestStatus 读起来更像惯用的 Elm。

然后我从上到下接近解码:

  1. 为了将一些JSON解码为RequestStatus,我们首先将JSON对象中的"status"属性反序列化为一个字符串,然后我们根据 "status" 的内容反序列化其余部分。这样做的惯用方法是使用 andThen(看起来你知道这一点,但还没有完全完善它与其余部分的配合方式)。这转化为:

    decodeStatus : Decoder RequestStatus
    decodeStatus =
        ("status" := string) `andThen` requestStatusData
    
  2. andThen 解码一条记录,然后将该记录传递给一个函数,该函数完成其余的解码,因此 requestStatusData 需要看起来像:

    requestStatusData: String -> Decoder RequestStatus
    

    requestStatusDataSuccess PostError String。所以我们需要两个分支,然后与状态 "success""error" 相关联(加上一个默认值以捕获格式错误的状态):

    requestStatusData status =
        case status of
        "success" -> makeSuccessWithPost
        "error" -> makeErrorWithString
        _ -> fail <| status ++ " is not a valid value for request status"
    
  3. 我们填写 makeSuccessWithPostmakeErrorWithString 实现。 Json.Decode 中的 objectN 函数提供了一种解码组件的功能,然后将它们提供给构造函数(或其他函数):

    object1 Success ("post" := post)
    

    首先使用定义为 post 的解码器解码 "post" 属性,然后在结果上调用 object1 (Success) 的第一个参数.同样,

    object2 Post ("title" := string) ("body" := string)
    

    使用string解码器解码"title",然后使用string解码器解码"body",然后用两个解码字符串调用Post函数作为它的论点。所以我们最终得到:

    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"
    
  4. 最后一步是填写post解码器,上面说的是object2.

  5. 的标准应用

总的来说,我认为我已经以相当标准、惯用的 Elm 风格处理了这个问题,但我是新手,我可能会犯一些错误。它确实有效!最后一点:我认为 RequestStatus 类型实际上不是必需的; Elm 核心库中的 Result 类型捕捉了具有成功和失败模式的类型的概念。你可以使用

Result String Post

而不是 RequestStatus 而不会丢失任何功能(您需要稍微更改 requestStatusData 但我会将其留作 reader... 的练习)。