Optional Attribute Access in Reason

November
13,
2018
ยท
graphql,
ocaml,
reason

One thing that you'll run into when interfacing with complex javascript objects in Reason, is that dealing with optional objects can be a huge pain. The most common place to run into this is when using the excellent graphql-ppx syntax plugin (often together with reason-apollo), because the result of a graphql query is a large complex javascript object, with many parts of it that are optional.

The problem

For example, say you have the following query:

query MyAwesomeQuery {
  user {
    bestFriend {
      profilePicture {
        url
      }
    }
  }
}

Where everything except the final url is optional.

The response type then looks like:

type queryResponse = {.
  "user": option({.
    "bestFriend": option({.
      "profilePicture": option({.
        "url": string
      }),
    }),
  }),
};

If you want to get the best friend's profile URL, it's quite a bit of code:

let url = switch (response##user) {
| None => None
| Some(user) => switch (user##bestFriend) {
  | None => None
  | Some(bestFriend) => switch (bestFriend##profilePicture) {
    | None => None
    | Some(profilePicture) => Some(profilePicture##url)
    }
  }
}

And this might be OK to write once, but it really adds up if you're doing lots of graphql queries in your app.

A solution!

Gregiorevda, an active community member and core reason constributor, proposed a modification to the reason parser that would add "safe call operators" to address exactly this use case. It's had a fair amount of discussion back and forth, and I personally was hesitant about making a syntax change like this without first thoroughly testing it out.

And so I made a PPX to test it out! You can install https://github.com/jaredly/get_in_ppx and experience the magic today.

This ppx includes two operators, that are only valid within the [%get_in ] form.

  • #?? is to be used when both sides are optional. E.g. the object on the left is optional, and the attribute you're getting out is also optional. option({. "attr": option(int)})
  • #? is to be used when only the object is optional, but the attribute you're getting out is not. e.g. option({. "attr": int})

For the example query above, you could do:

let url = [%get_in response##user#??bestFriend#??profilePicture#?url]

Installation

  • npm install get_in_ppx
  • add this line to your bsconfig.json: "ppx-flags": ["get_in_ppx/ppx"]

And you should be good to go! If you run into any issues, leave me some feedback on the github repo.