【问题标题】:Elm decoding unknown json structureElm解码未知json结构
【发布时间】:2017-04-11 01:33:42
【问题描述】:

我刚刚开始与 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 甚至会在运行时检查您期望的所有属性是否存在于该对象中。

标签: json decode elm


【解决方案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
        ]

【讨论】:

    【解决方案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.lazyJsArrayJsObject 构造函数所必需的,因为它们是递归定义的。

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

    编辑

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

    【讨论】:

    • 只想评论您可以使用表单:例如D.map JsString D.string。至少对我来说更容易阅读。
    • 谢谢,@Tosh!好点子,一个单独的 success monadic bind 是一个很好的气味,一个简单的 map 应该也可以工作。
    猜你喜欢
    • 2017-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-07
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    相关资源
    最近更新 更多