Как сделать декодер с дополнительным элементом? - PullRequest
4 голосов
/ 05 февраля 2020

Я застрял с декодером, который должен декодировать массив [ 9.34958, 48.87733, 1000 ] в Point, где индекс 2 (повышение) не является обязательным.

type alias Point =
    { elev : Maybe Float
    , at : Float
    , lng : Float
    }

Поэтому я создал следующий декодер:

fromArrayDecoder : Decoder Point
fromArrayDecoder =
    map3 Point
        (index 2 Decode.float |> Decode.maybe)
        (index 1 Decode.float)
        (index 0 Decode.float)

Теперь моя проблема заключается в том, что этот декодер успешно работает, когда индекс 2 отсутствует или имеет любой тип, например, строку et c. Но я хочу, чтобы он был успешным, только если отсутствует elev, а не если он неправильного типа. Есть ли способ сделать это?

Ответы [ 2 ]

4 голосов
/ 05 февраля 2020

Если под словом «отсутствует» вы подразумеваете, что значение может быть null, вы можете просто использовать nullable вместо maybe:

fromArrayDecoder : Decoder Point
fromArrayDecoder =
  map3 Point
    (index 2 Decode.float |> Decode.nullable)
    (index 1 Decode.float)
    (index 0 Decode.float)

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

fromTwoArrayDecoder : Decoder Point
fromTwoArrayDecoder =
  map3 (Point Nothing)
    (index 1 Decode.float)
    (index 0 Decode.float)

fromThreeArrayDecoder : Decoder Point
fromThreeArrayDecoder =
  map3 Point
    (index 2 Decode.float |> Decode.map Just)
    (index 1 Decode.float)
    (index 0 Decode.float)

fromArrayDecoder : Decoder Point
fromArrayDecoder =
  oneOf
    [ fromThreeArrayDecoder
    , fromTwoArrayDecoder
    ]

Просто не забудьте сначала попробовать 3-элементный декодер, так как 2-элементный декодер также преуспеет в 3-элементном массиве, но обратное - нет.

3 голосов
/ 06 февраля 2020

Я согласен, что факт Json.Decode.maybe, который дает вам Nothing на неправильном значении, а не на пропущенном одном, удивителен.

elm-json-decode-pipeline может работать так, как вы хотите, не слишком многословно.

> d = Decode.succeed Point 
|   |> optional "2" (Decode.float |> Decode.map Just) Nothing 
|   |> required "1" Decode.float 
|   |> required "0" Decode.float
|
> "[1, 2, \"3\"]" |> Decode.decodeString d
Err (Failure ("Json.Decode.oneOf failed in the following 2 ways:\n\n\n\n(1) Problem with the given value:\n    \n    \"3\"\n    \n    Expecting a FLOAT\n\n\n\n(2) Problem with the given value:\n    \n    \"3\"\n    \n    Expecting null") <internals>)
    : Result Decode.Error Point
> "[1, 2, 3]" |> Decode.decodeString d
Ok { at = 2, elev = Just 3, lng = 1 }
    : Result Decode.Error Point
> "[1, 2]" |> Decode.decodeString d
Ok { at = 2, elev = Nothing, lng = 1 }
    : Result Decode.Error Point

(из ошибки видно, что под капотом он использует oneOf как в glennsl's ответ .)

Единственная потенциально удивительная вещь здесь - это то, что вам нужно передавать строки, а не индексы int, так как не существует определенной c версии для списков, но вы можете получить доступ к индексам списков как хотя они являются именами полей. Это означает, что эта версия немного отличается тем, что она не выдаст ошибку, если вы можете создать объект с именами числовых полей, а не массивом, но я не могу представить, что это действительно проблема. Более реальная проблема - это может сделать ваши сообщения об ошибках менее точными:

> "[0]" |> Decode.decodeString (Decode.field "0" Decode.int)
Ok 0 : Result Decode.Error Int
> "[]" |> Decode.decodeString (Decode.field "0" Decode.int)
Err (Failure ("Expecting an OBJECT with a field named `0`") <internals>)
    : Result Decode.Error Int
> "[]" |> Decode.decodeString (Decode.index 0 Decode.int)
Err (Failure ("Expecting a LONGER array. Need index 0 but only see 0 entries") <internals>)

Обратите внимание, что вам все равно нужно избегать использования Json.Decode.maybe. Может быть заманчиво написать optional "2" (Decode.maybe Decode.float) Nothing, что приведет к тому же поведению, что и вы изначально.

...