Elm декодирование неизвестной структуры json

Я только начал работать с Elm, чтобы сделать некоторые прототипы интерфейсов, используя API Rest, над которым я работаю. В общем случае API возвращает "разумные" структуры данных, которые могут быть декодированы, поскольку ключи и типы значений хорошо известны, но несколько типов ресурсов возвращают data запись, которая просто имеет сырой json, который не имеет предопределенной структуры.

все, что я прочитал до сих пор, похоже, предполагает, что вы знаете структуру данных, которые вы декодируете, тогда как в plain 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. в настоящее время возможно обрабатывать неизвестные, возможно, глубоко вложенные и гетерогенные данные json в Elm?
  2. если да, можете ли вы дать мне ключевую концепцию или интуицию высокого уровня о том, как автор(ы) намеревался декодировать такие данные?

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 необходима JsArray и JsObject конструкторы, потому что они определяются рекурсивно.

эта структура должна обрабатывать все, что вы бросаете на нее, и это будет до остальной части вашей программы, чтобы решите, что делать с таким гибким типом.

редактировать

как отметил @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
    ]

на чадом отличный ответ, логический тип отсутствует. Вот полный модуль, способный обрабатывать логические значения:

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
        ]