首页 > 解决方案 > 如何将 JSON 值解压缩为标记的联合类型?

问题描述

我有一些来自 firebase 的 JSON 看起来像这样

{
  "type": "added",
  "doc": {
    "id": "asda98j1234jknkj3n",
    "data": {
      "title": "Foo",
      "subtitle": "Baz"
    }
  }
}

类型可以是"added""modified"之一"removed"Doc包含一个id和一个data字段。该data字段可以是任何形状,我能够正确解码。

我想像这样使用联合类型来表示这些值,

type alias Doc data =
    (String, data)

type DocChange doc
    = Added doc
    | Modified doc
    | Removed doc

这里的Doc类型别名表示doc上面 JSON 中的字段中包含的值。DocChange代表整个事物。如果类型是 say "added",那么 JSON 必须解码成Added doc等等。我不明白如何解码联合类型。

我认为andThenfrom 的功能Json.Decode看起来像我需要的,但我无法正确使用它。

标签: jsondecodeelmunion-types

解决方案


首先,您似乎想将doc参数限制DocChange为 a Doc,因此您可能应该像这样定义它:

type DocChange data
    = Added (Doc data)
    | Modified (Doc data)
    | Removed (Doc data)

否则,您将不得不DocChange (Doc data)在函数中反复指定类型注释,这很快就会变得烦人,而且嵌套越多就越糟糕。无论如何,我继续使用您定义的类型:

decodeDocData : Decoder DocData
decodeDocData =
    map2 DocData
        (field "title" string)
        (field "subtitle" string)


decodeDoc : Decoder data -> Decoder (Doc data)
decodeDoc dataDecoder =
    map2 Tuple.pair
        (field "id" string)
        (field "data" dataDecoder)


decodeDocChange : Decoder data -> Decoder (DocChange (Doc data))
decodeDocChange dataDecoder =
    field "type" string
        |> andThen
            (\typ ->
                case typ of
                    "added" ->
                        map Added
                            (field "doc" (decodeDoc dataDecoder))

                    "modified" ->
                        map Modified
                            (field "doc" (decodeDoc dataDecoder))

                    "removed" ->
                        map Removed
                            (field "doc" (decodeDoc dataDecoder))

                    _ ->
                        fail ("Unknown DocChange type: " ++ typ)
            )

这里的诀窍是先解码"type",然后使用andThen打开它并选择合适的解码器。在这种情况下,“类型”中的形状是相同的,但可能不是,这种模式也可以灵活地处理不同的形状。如果您绝对确定它们不会发散,则可以将其简化为仅选择构造函数并保持其余解码通用。


推荐阅读