Parsing complex foreign objects in PureScript


While trying to write a PureScript wrapper for the aws-sdk package, and in specific for the S3 interface, I was in need to parse the information coming back from the library calls.

For example, a call to listBuckets returns this kind of object:

{ Buckets: [{ Name: "String", CreationDate: "Date object" }],
  Owner: { DisplayName: "String", ID: "String" }}

I was interested in the buckets array only, so at first I tried finding a solution using purescript-foreign.

I modeled my bucket data type, together with its IsForeign instance:

newtype Bucket = Bucket
    { name :: String
    , creationDate :: Either String DateTime
    }

instance bucketIsForeign :: IsForeign Bucket where
    read value = do
        name <- readProp "Name" value
        creationDate <- readProp "CreationDate" value
        pure $ Bucket { name, readDate creationDate }

-- readDate is a function I write to concert js Date objects to ps DateTime objects.

I was then stuck when I tried to use my new instance to parse the bigger response object. The compiler didn’t like the fact that I wasn’t parsing the whole object. At first my solution was to write a data type representing the whole response, together with its IsForeign instance.

Thanks to this I was able to get my buckets array, but there was a lot of boilerplate involved, and I was scared that this approach would become unmanageable with more complex response objects.

I’m not sure if it’s possible to find a better solution using just purescript-foreign, but I found a nice one enough using purescript-foreign-lens.

This is the example found in the library repo:

doc :: Foreign
doc = toForeign { paras: [ { word: "Hello" }, { word: "World" } ] }

-- | This `FoldP` extracts all words appearing in a structure like the one above.
words :: forall r. Monoid r => FoldP r Foreign String
words = prop "paras"
    <<< array
    <<< traversed
    <<< prop "word"
    <<< string

main :: forall e. Eff (console :: CONSOLE | e) Unit
main = traverse_ log (doc ^.. words)

It parses the object, resulting in a list of strings. It was almost what I needed, I just had to find a way to replace that last string with a function that parsed buckets.

string is defined as:

string :: forall r. Monoid r => FoldP r Foreign String
string = to readString <<< traversed

readString is a function from purescript-foreign that reads foreign strings. I already had my bucketIsForeign instance defined, so I crossed my fingers and wrote:

bucket :: forall r. Monoid r => FoldP r Foreign Bucket
bucket = to read <<< traversed

buckets :: forall r. Monoid r => FoldP r Foreign Bucket
buckets = prop "Buckets"
      <<< array
      <<< traversed
      <<< bucket

And then, luckily, I was able to get a List Bucket by using response ^.. buckets.

Related Posts

Adding static files to Yesod

Planning a simple Reddit clone with Yesod

React-router-dom bindings for Reason

Post requests with PureScript Affjax and Argonaut

Simple ajax calls to an API example

Functor, Applicative e Monad illustrati

Una guida chiara

Arbitrary length lists with QuickCheck

Using sized to build arbitrary length lists for QuickCheck

PureScript Impressions

Trying to put some sense in JavaScript

Clojure Examples

Toggling DELL 9350's touchpad on Ubuntu 15.10

How to set up a hotkey to toggle DELL 9350's touchpad on Ubuntu 15.10

Using the Asus MB168B+ with Ubuntu 15.10

How to edit the driver install script to make it work on Ubuntu 15.10+ (and other distros)