Add ability to "fail" inside the object decoder
Currently, when inside the object decoder user cannot fail in it.
For example in this code, the user needs to "escape" the object decoder space in order to be able to use Decode.fail
type Bar =
{
Name : string
}
module Bar =
let decoder : Decoder<Bar> =
Decode.object (fun get ->
{
Name = get.Required.Field "name" Decode.string
}
)
type Vee =
{
Blah : string
}
module Vee =
let decoder : Decoder<Vee> =
Decode.object (fun get ->
{
Blah = get.Required.Field "blah" Decode.string
}
)
type Wrapper =
{
Bar : Bar option
Vee : Vee option
}
module Wrapper =
let decoder : Decoder<Wrapper> =
Decode.field "type" Decode.string
|> Decode.andThen (
function
| "foo" ->
Decode.object (fun get ->
let barOpt = get.Optional.Field "bar" Bar.decoder
let veeOpt = get.Optional.Field "vee" Vee.decoder
barOpt, veeOpt
)
|> Decode.andThen (fun (barOpt, veeOpt) ->
match barOpt, veeOpt with
| Some bar, None ->
{
Bar = Some bar
Vee = None
}
|> Decode.succeed
| None, Some vee ->
{
Bar = None
Vee = Some vee
}
|> Decode.succeed
| Some _, Some _ ->
Decode.fail "Both 'bar' and 'vee' cannot be set at the same time"
| None , None ->
Decode.fail "At least one of 'bar' and 'vee' should be set"
)
| invalid ->
sprintf "'%s' is an invalid type for Wrapper" invalid
|> Decode.fail
)
The problem solved by the code above is:

Let me see if I understood, we want to map JSON structures that can variate, allowing a decoder to fail and opt for another choice, is that correct?
If that's the case I did something on that line at work a few weeks ago. I had an algebraic data type that would return a structure for case A and another for case B, then I had to check it and let it fail on the decoding. The way I solved it was with a computation expression. Here is the snippet:
static member Decoder(okDecoder: Decoder<'A>) : Decoder<APIMessage<'A>> =
let decode = DecodeBuilder()
Decode.oneOf [
decode {
let! error = Decode.object (fun get -> get.Required.Raw ApplicationError.Decoder)
return Failure error
} |> ``Decoder.Run``
decode {
let! response = Decode.object (fun get -> get.Required.Raw okDecoder)
return Response response
} |> ``Decoder.Run``
]
So, explaining the code, I opted to receive a generic decoder for the OK case (which can variate into many shades), while the other option is always ApplicationError (hence the lack of a errorDecoder). The computation expression does the job of unpacking the Decode.object for me, so I just see which one is valid.
If that seems ok I can replicate it by adding a similar computation expression to the library. That could even be an opportunity to mimic a little the behavior of FParsec adding some infix operators. What do you think @MangelMaxime?
Hum, here the problem was that we needed to validate that only a specific set of the fields was set.
From the example, you provided I am unsure of what the decoders are doing. If you want to try explain it, I found that in general having the Domain Types + Decoders + different JSON with the expected results helps.
About adding, new syntax to Thoth.Json. In general, I am not opposed to it, I decide on case by case. For example, this is how the object decoder style has been added.
One good thing about Thoth.Json is that people can enhance it via a library and/or in their own project directly if needed.