This gist shows how you could go about defining ReasonML types based on a GraphQL schema that reference each other, as well as a recursive decoder function used to parse a JSON response into the correct type.
/* Use Reason's ability to define recursive types with the `and` keyword */ | |
type movie = { | |
id: option(string), | |
name: option(string), | |
rating: option(Js.null(string)), | |
runTime: option(Js.null(int)), | |
actors: option(list(actor)), | |
} | |
and actor = { | |
id: option(string), | |
name: option(string), | |
movies: option(list(movie)), | |
}; | |
/* Next we'll write a recursive decoder. We need to define the different possible | |
types we're trying to define. We also need a generic "node" type so that our decoder | |
is always returning the same type of thing. Finally we'll use the "%identity" to allow | |
converting things to/from our generic "node" type */ | |
type node; | |
external toNode : 'a => node = "%identity"; | |
external fromNode : node => 'a = "%identity"; | |
type gqlType = | |
| Movie | |
| Actor; | |
let rec decode: (gqlType, Js.Json.t) => node = | |
(t, json) => | |
switch (t) { | |
| Movie => | |
Json.Decode.{ | |
id: optional(field("id", string)), | |
title: optional(field("name", string)), | |
rating: optional(field("rating", nullable(string))), | |
runTime: optional(field("runTime", nullable(int))), | |
actors: | |
optional( | |
field("actors"), | |
list(json => decode(Actor, json) |> fromNode), | |
), | |
} | |
|> toNode | |
| Actor => | |
Json.Decode.{ | |
id: optional(field("id"), string), | |
name: optional(field("id"), string), | |
movies: | |
optional( | |
field("movies"), | |
list(json => decode(Movie, json) |> fromNode), | |
), | |
} | |
|> toNode | |
}; | |
/* Now we'll define a way to more easily use the decoder by making sure it returns the type | |
we're actually wanting... */ | |
type decoder('graphqlType) = Js.Json.t => 'graphqlType; | |
let decodeType: (gqlType, Js.Json.t) => 'a = | |
(t, j) => decode(t, j) |> fromNode; | |
/* I'd still like to reference things by their module to keep my naming conventions a little | |
cleaner. So I'll still have a Movies.re file that looks like this: */ | |
open GQLTypes; | |
type t = movie; | |
let decode: decoder(t) = decodeType(Movie); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment