schmoopy
schmoopy

Reputation: 6649

F# WebApi return properly formatted json from a discriminated union

I was wondering what the easiest way would be to return clean json from the result of a discriminated union in WebApi? This use case is for business logic only and not used for http errors, etc - those get handled in the pipeline and get returned to user as usual (404, 500, etc)

For example:

type ServiceResult<'a> = { Message:string; Payload:'a }

type ServiceResponse<'a> =
    | Success of ServiceResult<'a>
    | Fail of ServiceResult<string>

Returns Either:

{
  "Case": "Fail",
  "Fields": [
    {
      "Message": "Error performing business logic, your x is not in the y.",
      "Payload": "I just couldnt do it"
    }
  ]
}

...or...

{
  "Case": "Success",
  "Fields": [
    {
      "Message": "",
      "Payload": { "FirstName": "Johnny", "LastName":"Smith" }
    }
  ]
}

Where I would like to have only the service result returned like:

{
  "Message": "",
  "Payload": { "FirstName": "Johnny", "LastName":"Smith" }
}

... or ...

{
  "Message": "Error during operation due to spite.",
  "Payload": "I just couldnt do it"
}

I have tried IdiomaticDuConverter: https://gist.github.com/isaacabraham/ba679f285bfd15d2f53e

but it didnt work. The closest i have seen is Microsoft.FSharpLu.Json but it doesnt have a MediaTypeFormatter to plug into the pipeline.

I know i can use Lu and create my own MediaTypeFormatter but I feel like there has to be an easier way (like some Json.Net option I am missing).

Can you point me in the right direction?

thanks :-)

Upvotes: 4

Views: 682

Answers (2)

Mark Seemann
Mark Seemann

Reputation: 233150

While there may be corner cases where the return representation you're asking about is the correct design, in general this isn't good HTTP API design. ASP.NET Web API returns HTTP responses, so the implication of returning

{
  "Success": false,
  "Message": "Error during operation due to spite.",
  "Payload": "I just couldnt do it"
}

is that this value is being returned as part of a 200 OK response. This puts an extra burden on clients, because they now have to handle not only HTTP errors (which can still happen), but also can't trust that 200 OK actually meant success.

Instead, if there's an error, return the appropriate HTTP status code.

This also solves the problem, and makes your F# code easy to write. In a Controller, you'd basically do something like this:

member this.Get() =
    let result = // produce the result somehow...
    match result with
    | Success x -> this.Ok x :> IHttpActionResult
    | Fail msg  -> this.BadRequest msg :> IHttpActionResult

For simplicity's sake, this example returns 400 Bad Request on a Fail case, but if this case represents an internal error, perhaps 500 Internal Server Error would be more appropriate.

Upvotes: 3

scrwtp
scrwtp

Reputation: 13577

You can write a custom JSON.NET serializer for your ServiceResponse type that would simply skip the case label and delegate serialization to the nested ServiceResult.

The real question here is why do you even have this problem? You already carry the success/failure status in the record, the DU doesn't offer any additional information. What's more, with the setup you have, you can easily represent seemingly undesirable states like this:

Success <| { Success = false; Message = "necktie"; Payload = "bacon" }

Why not just drop it and pass around the record only, avoiding the serialization problem altogether?

Upvotes: 1

Related Questions