Elm 以不同的格式解码 json

Elm decode json in different format

我尝试在 elm 中解码一些 json。
我收到的物体可能有两种不同的形状。
第一种情况:

{
    ...
    "ChipId": "NotSet"
    ...
}

第二种情况:

{
    ...
    "ChipId": {
      "Item": "0000000000"
    },
    ...
}

所以第一个可以很容易地用 field "ChipId" string 解码,但如果它是复杂的对象,它就会失败。 我已经用 Decode.andThen 试过了,但我无法解决它。

感谢您的帮助!

更新 1 - 失败的解码器

我尝试的方法是使用 Maybe.

chipIdDecoder : Decoder String
chipIdDecoder =
    let
        chipIdIdDecoder : Decoder String
        chipIdIdDecoder =
            field "ChipId" (field "Fields" (firstElementDecoder string))

        chooseChipId : Maybe String -> Decoder String
        chooseChipId c =
            case c of
                Just id ->
                    case id of
                        "ChipId" ->
                            chipIdIdDecoder

                        _ ->
                            succeed ""

                Nothing ->
                    fail "Chip Id is invalid"
    in
    nullable (field "ChipId" string)
        |> andThen chooseChipId

我想这里的问题是 Maybe 期望某些东西或 null 而不是某些东西或其他东西。 ^^

tl;dr: 使用 oneOf

在 Elm 中编写 json 解码器的一个好方法是从最小的部分开始,编写独立解码每个部分的解码器,然后向上移动到下一个级别并为此编写解码器将您已经制作的较小的部分放在一起。

这里,例如,我会首先编写一个解码器来分别处理 "ChipId" 的两种可能形式。第一个只是一个字符串,它当然是用 elm/json 开箱即用的,所以这很简单。另一个是具有单个字段的对象,我们将其解码为简单的 String:

chipIdObjectDecoder : Decoder String
chipIdObjectDecoder =
    field "Item" string

然后我们需要将它们放在一起,这似乎是您最费力的部分。这里 oneOf 函数来拯救我们,它的描述是:

Try a bunch of different decoders. This can be useful if the JSON may come in a couple different formats.

听起来正是我们需要的!要同时尝试 string 解码器和我们的 chipIdObjectDecoder 我们可以这样写:

eitherStringOrChipIdObject : Decoder String
eitherStringOrChipIdObject =
    oneOf [ string, chipIdObjectDecoder ]

最后我们需要解码 "ChipId" 字段本身:

field "ChipId" eitherStringOrChipIdObject

所有这些都放在一个函数中:

chipIdDecoder : Decoder String
chipIdDecoder =
    let
        chipIdObjectDecoder : Decoder String
        chipIdObjectDecoder =
            field "Item" string

        eitherStringOrChipIdObject : Decoder String
        eitherStringOrChipIdObject =
            oneOf [ string, chipIdObjectDecoder ]
    in
    field "ChipId" eitherStringOrChipIdObject

或者稍微简化一下,因为上面的内容相当冗长:

chipIdDecoder : Decoder String
chipIdDecoder =
    let
        chipIdObjectDecoder =
            field "Item" string
    in
    field "ChipId" (oneOf [ string, chipIdObjectDecoder ])

最后一点,因为不清楚您的代码是否过于简化。如果无法将 "ChipId" 对象简化为简单的字符串,则必须使用可以同时包含 StringChipIdObject 以及 map 的通用类型将值解码为该通用类型。 eitherStringOrChipIdObject 可能是这样的:

type alias ChipIdObject = { ... }

type ChipId
    = ChipIdString String
    | ChipIdObject ChipIdObject

eitherStringOrChipIdObject : Decoder ChipId
eitherStringOrChipIdObject =
    oneOf
        [ string |> map ChipIdString
        , chipIdObjectDecoder |> map ChipIdObject
        ]