Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 986 of the free egghead.io lessons, plus get PureScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Automatically de/serialize JSON with Purescript-Foreign-Generics

4:09 PureScript lesson by

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.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

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.

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."

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?