Kurt Mueller
Kurt Mueller

Reputation: 3224

Valid F# functions for returning anonymous records with different structures

I'm writing an F# library to call an HTTP REST API. The JSON body for the HTTP request is different based on how much or what type of information the request should send back.

For example, below is code to create a json body, using anonymous records, for an HTTP request to list out files in a project, https://documentation.dnanexus.com/developer/api/search#api-method-system-finddataobjects.

type Request = {
    ApiToken: ApiToken
    ProjectId: ProjectId
    StartingAt: ObjectId option
}

module Request =
    let toJson request =
        let (ProjectId projectId) = request.ProjectId
        let baseJson = {| scope = {| project = projectId
                                     recurse = true |}
                          describe = true |}

        match request.StartingAt with
            | Some (ObjectId objectId) ->
                {| baseJson with starting = {| project = projectId
                                               id = objectId |} |}

            | None ->
                baseJson

This code does not compile. Instead, in the last line of this example, the compiler throws the following error:

This anonymous record does not have enough fields. Add the missing fields [starting].F# Compiler(1)

Is it possible to write a function that returns anonymous records with different structures?

Upvotes: 1

Views: 201

Answers (1)

Isaac Abraham
Isaac Abraham

Reputation: 3502

This isn't a problem with anonymous records per se - it's that there's no clear return type of toJson - depending on the branch, you return a different type.

You have a few options:

  1. Make starting an object, which can be set to null on the None branch:
        match request.StartingAt with
            | Some (ObjectId objectId) ->
                {| baseJson with starting = box {| project = projectId
                                                   id = objectId |} |}

            | None ->
                {| baseJson with starting = null |}
  1. Perform the json serialization within this function:
        match request.StartingAt with
            | Some (ObjectId objectId) ->
                serialize
                    {| baseJson with starting = {| project = projectId
                                                   id = objectId |} |}

            | None ->
                serialize baseJson

where serialize is a function that takes any 'T and returns a string.

  1. The last option is to unify the two branches with a discriminated union. However, in this case I think that's not worthwhile as you're serializing this record anyway.

The main thing is that both "branches" of the match expression have to return the same type. This is the same for any branching expression in F# e.g. match or if / else. And it applies for all types - primitives, records, tuples, unions etc.

Upvotes: 3

Related Questions