Как декодировать гетерогенный массив с оставшимися значениями в виде списка - PullRequest
0 голосов
/ 13 ноября 2018

Я хочу декодировать строку json, как показано ниже.

"[[\"aaa\",1,2,3,4],[\"bbb\",1,2,3]]"

и декодирование в список кортежей Elm.

[("aaa",[1,2,3,4]),("bbb",[1,2,3])] : List (String, List Int)

Как его расшифровать?

jsdecode=index 0 string
    |> andThen xxxxxxx??

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

Это не просто сделать, но прежде чем я начну прыгать, как это сделать, позвольте мне собрать серию мыслей о данных, которые мы пытаемся декодировать:

  • Мы декодируемсписок списков
  • Каждый список должен состоять из начальной строки и серии значений
  • Но на самом деле это может быть пустой список, без начальной строки, но с некоторыми значениями или исходной строкой инет значений

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

type alias Record =
    ( String, List Int )


type alias Model =
    List Record

jsonString : String
jsonString =
    "[[\"aaa\",1,2,3,4],[\"bbb\",1,2,3]]"

decoder : Decoder Model
decoder =
    Decode.list recordDecoder

Теперь нам нужно определить тип, который представляет, что список может содержать либо строки, либо целые числа

type EntryFlags
    = EntryId String
    | EntryValue Int


type RecordFlags
    = List EntryFlags

А теперьдля нашего декодера

recordDecoder : Decoder Record
recordDecoder =
    Decode.list
        (Decode.oneOf
            [ Decode.map EntryId Decode.string
            , Decode.map EntryValue Decode.int
            ]
        )
        |> Decode.andThen buildRecord

Итак, buildRecord берет этот список из EntryId String или EntryValue Int и создает искомую запись.

buildRecord : List EntryFlags -> Decoder Record
buildRecord list =
    case list of
        [] ->
            Decode.fail "No values were passed"

        [ x ] ->
            Decode.fail "Only key passed, but no values"

        x :: xs ->
            case buildRecordFromFlags x xs of
                Nothing ->
                    Decode.fail "Could not build record"

                Just value ->
                    Decode.succeed value

Как видите,мы имеем дело с множеством крайних случаев в нашем декодере.Теперь для последнего бита давайте проверим buildRecordFromFlags:

buildRecordFromFlags : EntryFlags -> List EntryFlags -> Maybe Record
buildRecordFromFlags idEntry valueEntries =
    let
        maybeId =
            case idEntry of
                EntryId value ->
                    Just value

                _ ->
                    Nothing

        maybeEntries =
            List.map
                (\valueEntry ->
                    case valueEntry of
                        EntryValue value ->
                            Just value

                        _ ->
                            Nothing
                )
                valueEntries
                |> Maybe.Extra.combine
    in
    case ( maybeId, maybeEntries ) of
        ( Just id, Just entries ) ->
            Just ( id, entries )

        _ ->
            Nothing

В этом последнем бите мы используем функцию из может быть-экстра , чтобы проверить, что все значения, следующие за начальнымEntryId действительно являются типом EntryValue.

Рабочий пример можно посмотреть здесь: https://ellie -app.com / 3SwvFPjmKYFa1

0 голосов
/ 13 ноября 2018

Здесь есть две подзадачи: 1. декодирование списка и 2. преобразование его в нужную вам форму.Вы можете сделать это, как подсказывает @SimonH, расшифровав список значений JSON, постобработав его и затем (или во время постобработки) декодировать внутренние значения.Вместо этого я предпочел бы сначала полностью декодировать его в пользовательский тип, а затем полностью выполнить постобработку в области типов Elm.

Итак, шаг 1, декодирование:

type JsonListValue
    = String String
    | Int Int

decodeListValue : Decode.Decoder JsonListValue
decodeListValue =
    Decode.oneOf
        [ Decode.string |> Decode.map String
        , Decode.int |> Decode.map Int
        ]


decoder : Decode.Decoder (List (List JsonListValue))
decoder =
    Decode.list (Decode.list decodeListValue)

Это базовый шаблон, который вы можете использовать для декодирования любого гетерогенного массива.Просто используйте oneOf, чтобы попробовать список декодеров по порядку, и сопоставьте каждое декодированное значение общему типу, обычно пользовательскому типу с простым конструктором для каждого типа значения.

Затем на шаге 2преобразование:

extractInts : List JsonListValue -> List Int
extractInts list =
    list
        |> List.foldr
            (\item acc ->
                case item of
                    Int n ->
                        n :: acc

                    _ ->
                        acc
            )
            []


postProcess : List JsonListValue -> Result String ( String, List Int )
postProcess list =
    case list of
        (String first) :: rest ->
            Ok ( first, extractInts rest )

        _ ->
            Err "first item is not a string"

postProcess сопоставит первый элемент с String, запустите extractInts с остальными, которые все должны быть Int с, затем соедините их вместе в кортеж, который выхочу.Если первый элемент не является String, он вернет ошибку.

extractInts сворачивает каждый элемент и добавляет его в список, если это Int, и игнорирует его в противном случае.Обратите внимание, что он не возвращает ошибку, если элемент не является Int, он просто не включает его.

Обе эти функции могли быть написаны так, чтобы либо завершиться сбоем, если значения не соответствуютк ожиданиям, как postProcess, или обрабатывать это "изящно", как extractInts.Я решил сделать один из них, просто чтобы проиллюстрировать, как вы могли бы сделать и то, и другое.

Затем, шаг 3, это собрать его вместе:

Decode.decodeString decoder json
    |> Result.mapError Decode.errorToString
    |> Result.andThen
        (List.map postProcess >> Result.Extra.combine)

Здесь Result.mapError используется дляошибка от декодирования для соответствия типу ошибки, которую мы получаем из postProcess.Result.Extra.combine - это функция от elm-community/result-extra, которая превращает List из Result с в Result из List, что очень удобно здесь.

...