When decoding Json, there is usually a hard trade off
This attempt decoding technique is a way to get the good of both.
The result of decoding an Attempt gives you the value you sought
and a list of errors it accumulated along the way.
A marker that this Attempt value Dangerously cannot be safely decoded straight to
a value, and it may fail in a big way that doesnt deliver a value at all. In addition,
if it does succeed decoding a value, it still preserves the Decode.Error is found
along the way.
type alias User =
{ id : Int
, name : Maybe String
}
decodeUser : Decode.Value -> Result Decode.Error ( User, List Decode.Error )
decodeUser json =
let
attemptUser : Attempt User Dangerously
attemptUser =
Attempt.succeed User
|> Attempt.risk (Decode.field "id" Decode.int)
|> Attempt.try (Decode.map Just <| Decode.field "name" Decode.string) Nothing
in
Attempt.decodeValueDangerously attemptUser jsonA marker that this Attempt value Safely can be safely decoded straight to
a value, without an intermediate Result Decode.Error value. In this example
below, attemptInt will always give you an OperatingSystem, and it may or
may not give you an error too.
attemptOperatingSystem : Attempt OperatingSystem Safely
attemptOperatingSystem =
Attempt.fromDecoderWithDefault
(Decode.field "operating_system" OperatingSystem.decoder)
OperatingSystem.NotDetected
( value, errors ) =
Attempt.decodeValueSafely attemptOperatingSystemTry a decoder, and if it fails, fallback Safely to a fallback value
type alias Flags =
{ landingPageUrl : String }
attemptFlags : Attempt Flags Safely
attemptFlags =
Attempt.succeed Flags
|> Attempt.try Decode.string (Route.toString Route.home)Try a decoder, and risk it failing, thereby transforming your attempt into a Dangerous one
type Id
= Id Int
attemptId : Attempt Id Dangerous
attemptId =
Attempt.succeed Id
|> Attempt.risk Decode.intApply a safe attempt to a pipeline. Kind of like a more general form of try
try : Decoder a -> a -> Attempt (a -> b) safety -> Attempt b safety
try decoder fallback =
applySafely (fromDecoderWithFallback decoder fallback)
type alias Flags =
{ landingPageUrl : String }
attemptFlags : Attempt Flags Safely
attemptFlags =
Attempt.succeed Flags
|> Attempt.applySafely (fromDecoderWithFallback Decode.string (Route.toString Route.home))Apply a dangerous attempt to a pipeline, turning the rest of the pipeline into a dangerous attempt.
Kind of like a more general form of risk.
risk : Decoder a -> Attempt (a -> b) safety -> Attempt b Dangerously
risk decoder =
applyDangerously (fromDecoder decoder)
type Id
= Id Int
attemptId : Attempt Id Dangerous
attemptId =
Attempt.succeed Id
|> Attempt.risk Decode.intUse a decoder in an attempt. Since the decoder might fail, the resulting
attempt is a Dangerous one.
Convert a value into an attempt, presumably to use it in a pipeline. Since the value is provided outright the Attempt is safe
Use a decoder in an attempt, with a fallback. Since a fallback is provided, the attempt is safe
Take a dangerous attempt, and specify how it should fall back, thereby making it a safe attempt
Attempt to decode json that is under a certain field, much like Decode.field. By checking
under a field, you risk the field not being present in the first place, so using field is always Dangerous
type alias User =
{ id : Int
, name : Maybe String
, car : Car
}
type alias Car =
{ plateNumber : Maybe String
, miles : Maybe Int
}
attemptCar : Attempt Car Safely
attemptCar =
Attempt.fromValue Car
|> Attempt.try (Decode.map Just <| Decode.field "plate_number" Decode.string) Nothing
|> Attempt.try (Decode.map Just <| Decode.field "miles" Decode.int) Nothing
attemptUser : Attempt User Dangerously
attemptUser =
Attempt.fromValue User
|> Attempt.risk (Decode.field "id" Decode.int)
|> Attempt.try (Decode.map Just <| Decode.field "name" Decode.string) Nothing
|> Attempt.applyDangerously (Attempt.field "car" attemptCar)Convert the value of an Attempt from one type to another
Just like decodeValueDangerously, except it takes a String instead of a Decode.Value. Just like
the difference between Decode.decodeValue and Decode.decodeString.
Just like decodeValueSafely, except it takes a String instead of a Decode.Value. Just like
the difference between Decode.decodeValue and Decode.decodeString.
The final step in an attempt to decode json. This function takes a dangerous attempt,
and therefore it can possibly fail. If it does succeed, the Ok case is (value, List Decode.Error),
containing both the desired value and the List Decode.Error of non-critical errors it encountered
along the way.
The final step in an attempt to decode json. This function takes a safe attempt, and therefore can never fail. It will pass along both the value you intended to decode, and a list of whatever errors it encountered along the way.
An attempt at decoding a particular
value. Thesafetyparameter describes if this kind of attempt will be able to recover or if it will fail hard.