Automatically de/serialize JSON with Purescript-Foreign-Generics

Justin Woo
InstructorJustin Woo
Share this video with your friends

Social Share Links

Send Tweet

Deserializing and serializing JSON manually is a boring, error-prone way to be able to handle JSON. This example shows how you can use Generics in Purescript to automatically derive de/serializers so that once you've written your type definition, you only need a few lines of code to handle JSON.

[00:00] Let's start with a blank file that's just our main module, and we'll import Prelude, bring in our Eff Monad, and bring in some stuff for console, like log and console effect. Then we'll just have log "Hello world."

[00:14] Once we run this, we'll see in the output below, it says Hello word. Let's start with a new type myRecord. This new type MyRecord wil be constructed with MyRecord, and it will have a single field a of Int.

[00:30] With this we can derive an instance for a genericMyRecord. We'll use the Generic with reps, so just make sure you bring in the rep one, and the MyRecord and the underscore for the representation. We'll do an instance showMyRecord with this, so we'll say show and MyRecord, and this has a single method show, which we can define the terms of the generic. We'll bring in genericShow that has the generic rep constraint.

[01:02] We can continue on by doing instance isForeignMyRecord and this is going to use isForeign from Foreign class. Using this IsForeign MyRecord where read= readGeneric, which also has this Generic rep constraint, we're going to define it with defaultOptions, but then we're also going to specify that we want to unwrap the single constructors.

[01:35] Likewise, it can define an instance AsForeignMyRecord, so this is going to use AsForeign, from the same package, and MyRecord, where we're going to have a method write. This is going to use toForeignGeneric, using the Generics rep, and we're going to supply it the same options as above.

[02:01] Let's try turning our record into a JSON string, so we're going to define a toJSONString that's going to use write. This is going to have the AsForeign constraint, and take anything that has the AsForeign constraint met, and convert into Foreign object. But then in order to actually get a string representation of this Foreign object, we're going to have to use unsafeStringify, and this will turn anything of a into a string.

[02:30] Let's try logging this out. We're going to use log with toJSONString, and we're going to supply it a MyRecord. When we run this, we'll see in the bottom output, that this has generated the JSON {"a":1}. Let's try reading a JSON string into our datatype. We're going to use fromJSONString, and this will be defined in terms of readJSON, and you'll see it has a constraint IsForeign, and it turns a string into an F a.

[03:00] An F is a type alias for our results, using the Except monad. We're going to have to run this exception. By using the runExcept, we're going to turn our Except into an Either.

[03:12] The left side of the either represents the error, and the right side is the result that we want. To use this fromJSONString method, we're going to have to find the target type that we want. So we're going to have to define eMyRecord as an Either, with the wildcard, and then MyRecord as the result that we want.

[03:30] So eMyRecord will be fromJSONString, and then we'll define a JSONString """{"a":1}""". Now let's log out this eMyRecord and see what comes out.

[03:51] You'll see that the Right side has come out with the MyRecord of {a:1}. If we provide invalid JSON such as {"a":true}, then you'll see that the result ends up being the Left side, with the ErrorAtProperty "a" of a TypeMismatch between Int and Boolean.