榆树解码未知的json结构

时间:2016-11-27 03:25:48

标签: json decode elm

我刚刚开始与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. 如果是这样,你能否给出关于作者如何将这样的数据解码的关键概念或高级直觉?

2 个答案:

答案 0 :(得分:9)

是的,可以编写通用解码器。您可以先定义一个包含所有可能的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
    ]
JsArrayJsObject构造函数需要

Json.Decode.lazy,因为它们是递归定义的。

这个结构应该处理你抛出的任何东西,并且由你的程序的其余部分来决定如何使用这种灵活的类型。

修改

正如@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
    ]

答案 1 :(得分:1)

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
        ]