Elm - декодирование Json с динамическими ключами

Я хочу декодировать файл Json, который будет выглядеть так:

{ 'result': [
    {'id': 1, 'model': 'online', 'app_label': 'some_app_users'}, 
    {'id': 2, 'model': 'rank', 'app_label': 'some_app_users'}, 
]}

или вот так:

{ 'result': [
    {'id': 1, 'name': 'Tom', 'skills': {'key': 'value', ...}, {'key': 'value', ...}},
    {'id': 1, 'name': 'Bob', 'skills': {'key': 'value', ...}, {'key': 'value', ...}},
]}

По сути, содержимое в result представляет собой список dicts с одинаковыми ключами - но я не знаю эти ключи заранее и не знаю их типов значений (int, string, dict и т. Д. .).

Цель - показать содержимое таблиц базы данных; Json содержит результат SQL-запроса.

Мой декодер выглядит так (не компилируется):

tableContentDecoder : Decode.Decoder (List dict)
tableContentDecoder =
    Decode.at [ "result" ] (Decode.list Decode.dict)

Я использую это так:

Http.send GotTableContent (Http.get url tableContentDecoder)

Я получаю эту ошибку:

Функция list ожидает, что аргумент будет: Decode.Decoder (Dict.Dict String a)

Но это: Decode.Decoder a -> Decode.Decoder (Dict.Dict String a)

Какой правильный синтаксис для использования декодера dict? Это сработает? Я не нашел универсального декодера Вяза ...


person François Constant    schedule 05.01.2018    source источник


Ответы (2)


Decode.list - это функция, которая принимает значение типа Decoder a и возвращает значение типа Decoder (List a). Decode.dict также является функцией, которая принимает значение типа Decoder a, которое возвращает декодер Decoder (Dict String a). Это говорит нам о двух вещах:

  • Нам нужно передать значение декодера в Decode.dict, прежде чем мы передадим его в Decoder.list
  • Dict может не соответствовать вашему варианту использования, поскольку Dicts может отображаться только между двумя фиксированными типами и не поддерживает значения вложенности, такие как 'skills': {'key': 'value', ...}

В Elm нет универсального декодера. Мотивация для этого связана с гарантией Elm «отсутствия ошибок времени выполнения». Имея дело с внешним миром, Elm должен защищать свою среду выполнения от возможных внешних сбоев, ошибок и т. Д. Основной механизм Elm для этого - типы. Elm позволяет вводить только те данные, которые правильно описаны, и тем самым исключает возможность ошибок, которые может внести универсальный декодер.

Поскольку ваша основная цель - отображать контент, может сработать что-то вроде Dict String String, но это зависит от того, насколько глубоко вложены ваши данные. Вы можете реализовать это, немного изменив свой код: Decode.at [ "result" ] <| Decode.list (Decode.dict Decode.string).

Другая возможность - использовать Decode.value и Decode.andThen для проверки значений, указывающих, из какой таблицы мы читаем.

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

-- represents the different possible tables
type TableEntry
    = ModelTableEntry ModelTableFields
    | UserTableEntry  UserTableFields
    | ... 

-- we will use this alias as a constructor with `Decode.map3`
type alias ModelTableFields =
    { id       : Int
    , model    : String
    , appLabel : String
    }

type alias UserTableFields =
    { id : Int
    , ...
    }

tableContentDecoder : Decoder (List TableEntry)
tableContentDecoder =
    Decode.value 
        |> Decode.andThen 
            \value ->
                let
                    tryAt field = 
                        Decode.decodeValue
                            (Decode.at ["result"] <| 
                                Decode.list <|
                                Decode.at [field] Decode.string)
                            value
                in  
                    -- check the results of various attempts and use
                    -- the appropriate decoder based on results
                    case ( tryAt "model", tryAt "name", ... ) of
                        ( Ok _, _, ... ) ->
                            decodeModelTable

                        ( _, Ok _, ... ) ->
                            decodeUserTable

                        ...

                        (_, _, ..., _ ) ->
                            Decode.fail "I don't know what that was!"

-- example decoder for ModelTableEntry
-- Others can be constructed in a similar manner but, you might
-- want to use NoRedInk/Json.Decode.Pipline for more complex data
decodeModel : Decoder (List TableEntry)
decodeModel  =
    Decode.list <|
       Decode.map3 
           (ModelTableEntry << ModelTableFields)
           (Decode.field "id" Decode.int)
           (Decode.field "model" Decode.string)
           (Decode.field "app_label" Decode.string) 

decodeUser : Decoder (List TableEntry)
decodeUser = 
    ...

Справедливо сказать, что это намного больше, чем требуется на большинстве других языков для синтаксического анализа JSON. Однако это дает возможность использовать внешние данные, не беспокоясь об исключениях.

Можно подумать, что Вяз заставляет вас делать всю работу заранее. Где другие языки могут позволить вам начать работу быстрее, но сделайте меньше, чтобы помочь вам получить стабильную реализацию.

person Tyler Nickerson    schedule 05.01.2018
comment
Спасибо, @Tyler. Я понимаю причину использования Elm Decoder. Я использовал его нормально с предопределенной полезной нагрузкой, но мой вопрос здесь действительно о динамическом (отображение содержимого случайных таблиц). Я пробовал с Decode.at [ "result" ] <| (Decode.list (Decode.dict Decode.string)), и он компилируется, и HTTP-запрос в порядке (выполняется код в ветке Ok), но результат, который я получаю в моей модели, представляет собой пустой список ... Я не знаю, как его отлаживать . - person François Constant; 05.01.2018
comment
Я нашел способ - действительно близкий к твоему решению. Позже обновлю свой вопрос с более подробной информацией. - person François Constant; 05.01.2018

Я не мог понять, как заставить Decode.dict работать, поэтому я изменил свой Json и разделил столбцы и результаты:

data={
    'columns': [column.name for column in cursor.description],
    'results': [[str(column) for column in record] for record in cursor.fetchall()]
}

Мне также пришлось преобразовать все результаты в String, чтобы упростить задачу. Например, Json будет иметь 'id': "1".

С Json, сделанным таким образом, код Elm действительно прост:

type alias QueryResult =
    { columns : List String, results : List (List String) }

tableContentDecoder : Decode.Decoder QueryResult
tableContentDecoder =
    Decode.map2
        QueryResult
        (Decode.field "columns" (Decode.list Decode.string))
        (Decode.field "results" (Decode.list (Decode.list Decode.string)))
person François Constant    schedule 05.01.2018