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