Elm解码未知json结构

Elm decoding unknown json structure

我刚刚开始使用 Elm 使用 Rest API 进行一些前端原型设计。一般来说,API returns "reasonable" 数据结构可以被解码,因为键和值类型是众所周知的,但是一些资源类型 return a 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. 如果是这样,您能给我关于作者打算如何解码此类数据的关键概念或高级直觉吗?

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

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
    ]

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
        ]