Elm解码未知的JSON结构

12

我刚开始使用Elm进行前端原型设计,并使用我正在开发的Rest API。总体而言,该API返回"合理"的数据结构,可以解码,因为键和值类型是众所周知的,但是有几个资源类型返回一个data条目,其中只有没有预定结构的原始JSON。

到目前为止,我阅读的所有内容似乎都假设您知道要解码的数据结构,而在普通js中,循环遍历键并反映类型以确定应在运行时如何处理它们相对容易。我还没有看到解决这种类型数据在Elm中应如何处理的明确方法。

例如,

{
  "name":"foo",
  "data": {
    "bar": [{"baz":123}, "quux"]
  },
  ...
}

我想知道是否目前有可能使用类似的方法解析data条目的值。

function go(obj)
    for key in keys(foo)
        if foo[key] is an object
            go(foo[k])
        else if foo[key] is an array
            map(go, foo[k])
        ...

具体来说:

  1. 目前是否有可能在 Elm 中处理未知、可能深度嵌套和异构的 json 数据?
  2. 如果可以,你能否给我提供作者旨在如何解码这样的数据的关键概念或高层次直觉?

恐怕接收一个你不知道的结构并不像 Elm 那样。在 Elm 中,你总是期望一个对象包含某些属性,甚至 Elm 在运行时会检查你期望的所有属性是否存在于该对象中。 - rishat
2个回答

13

是的,编写一个通用解码器是可能的。您可以首先定义一个包含所有可能的Json类型的union类型:

type JsVal
  = JsString String
  | JsInt Int
  | JsFloat Float
  | JsArray (List JsVal)
  | JsObject (Dict String JsVal)
  | JsNull

现在您可以使用 Json.Decode.oneOf 来尝试每一种可能性。

import Json.Decode as D exposing (Decoder)
import Dict exposing (Dict)

jsValDecoder : Decoder JsVal
jsValDecoder =
  D.oneOf
    [ D.string |> D.andThen (D.succeed << JsString)
    , D.int |> D.andThen (D.succeed << JsInt)
    , D.float |> D.andThen (D.succeed << JsFloat)
    , D.list (D.lazy (\_ -> jsValDecoder)) |> D.andThen (D.succeed << JsArray)
    , D.dict (D.lazy (\_ -> jsValDecoder)) |> D.andThen (D.succeed << JsObject)
    , D.null JsNull
    ]
Json.Decode.lazy用于JsArrayJsObject的构造,因为它们被递归地定义。
这个结构应该能够处理您提供的任何内容,剩下的将由您的程序决定如何处理这种灵活的类型。 编辑 正如@Tosh指出的那样,可以使用map而不是andThen后跟一个succeed来简化此解码器:
jsValDecoder : Decoder JsVal
jsValDecoder =
  D.oneOf
    [ D.map JsString D.string
    , D.map JsInt D.int
    , D.map JsFloat D.float
    , D.list (D.lazy (\_ -> jsValDecoder)) |> D.map JsArray
    , D.dict (D.lazy (\_ -> jsValDecoder)) |> D.map JsObject
    , D.null JsNull
    ]

2
只是想评论一下,你可以使用一个表单:例如 D.map JsString D.string。对我来说,这样阅读起来会更容易一些。 - Tosh
谢谢,@Tosh!好主意。单个success单子绑定是一个很好的提示,说明简单的map同样适用。 - Chad Gilbert

4

Chad's出色的回答中,布尔类型缺失。这里有一个完整的模块,能够处理布尔值:

module Data.JsonValue exposing (JsonValue(..), decoder)

import Dict exposing (Dict)
import Json.Decode as Decode
    exposing
        ( Decoder
        , dict
        , string
        , int
        , float
        , list
        , null
        , oneOf
        , lazy
        , map
        , bool
        )


type JsonValue
    = JsonString String
    | JsonInt Int
    | JsonFloat Float
    | JsonBoolean Bool
    | JsonArray (List JsonValue)
    | JsonObject (Dict String JsonValue)
    | JsonNull


decoder : Decoder JsonValue
decoder =
    oneOf
        [ map JsonString string
        , map JsonInt int
        , map JsonFloat float
        , map JsonBoolean bool
        , list (lazy (\_ -> decoder)) |> map JsonArray
        , dict (lazy (\_ -> decoder)) |> map JsonObject
        , null JsonNull
        ]

谢谢您的回答,但是如何使用它呢?您能提供一个例子吗?我的意思是我有这些数据,但是如何使用这些数据构建HTML呢? - Ivan Pruchai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接