I agree that the fact Json.Decode.maybe
giving you Nothing
on a wrong value rather than just a missing one is surprising.
elm-json-decode-pipeline
can work in the way you want without getting too verbose.
> 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
(You can see from the error that under the hood it is using oneOf
like in glennsl's answer.)
The only potentially surprising thing here is that you need to pass strings rather than int indexes, as there isn't a specific version for lists but you can access list indexes as though they are field names. This does mean that this version is subtly different in that it will not throw an error if you can an object with number field names rather than an array, but I can't imagine that really being an issue. The more real issue is it could make your error messages less accurate:
> "[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>)
Note that you do still have to to avoid using Json.Decode.maybe
. It may be tempting to write optional "2" (Decode.maybe Decode.float) Nothing
which will result in the same behaviour as you originally got.