Skip to content

Instantly share code, notes, and snippets.

@kofno
Last active October 7, 2015 19:03
Show Gist options
  • Save kofno/f5f34f03895e74727bc9 to your computer and use it in GitHub Desktop.
Save kofno/f5f34f03895e74727bc9 to your computer and use it in GitHub Desktop.

Parsing JSON with Purescript

I just wrote a lambda that responds to S3 events. I wrote it in Node. The JSON strcture of an S3 event is fairly complex. It is even worse when the event comes over SNS.

Handling the events in Node was error prone and testing took a while. Also, SNS will sometimes send test events that are imcomplete. This results in many failures due to missing keys. Undefined errors are not your friend.

I wanted to parse the JSON and handle error conditions more gracefully.

In this example, I'm only interested in the bucket and key of the S3 object. The parser wades through the other data and builds an S3 data type. If the bucket and key aren't present, or the JSON is malformed, then an error is returned as a Left. If the errors aren't handled then the program won't compile.

{
"Records": [
{
"eventVersion": "2.0",
"eventTime": "1970-01-01T00:00:00.000Z",
"requestParameters": {
"sourceIPAddress": "127.0.0.1"
},
"s3": {
"configurationId": "testConfigRule",
"object": {
"eTag": "d41d8cd98f00b204e9800998ecf8427e",
"key": "photos/1483/02671d5188895686ce28f1d13530322a/original/cave_scenery.png",
"size": 1024
},
"bucket": {
"arn": "arn:aws:s3:::foo",
"name": "foo",
"ownerIdentity": {
"principalId": "EXAMPLE"
}
},
"s3SchemaVersion": "1.0"
},
"responseElements": {
"x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD",
"x-amz-request-id": "C3D13FE58DE4C810"
},
"awsRegion": "us-east-1",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "EXAMPLE"
},
"eventSource": "aws:s3"
}
]
}
module Main where
import Prelude
import Data.JSON
import Data.JSON.S3
import Node.FS.Async
import Node.Encoding
import Data.Either
import Control.Monad.Eff.Console
decode' :: String -> Either String Records
decode' = eitherDecode
results :: Either String Records -> String
results (Left err) = show err
results (Right s3) = show s3
parse' :: String -> String
parse' = (results <<< decode')
main = do
readTextFile UTF8 "example.json" $ \x -> do
case x of
Left err -> log (show err)
Right x' -> log (parse' x')
module Data.JSON.S3 where
import Prelude
import Data.Array
import Data.JSON
data Records = Records (Array S3)
data S3= S3 { bucket :: String
, key :: String
}
instance showS3 :: Show S3 where
show (S3 { bucket = b, key = k }) =
"S3 { bucket: \"" ++ b ++ "\", key: \"" ++ k ++ "\" }"
instance showRecords :: Show Records where
show (Records xs) = "Records " ++ show xs
instance recordsFromJSON :: FromJSON Records where
parseJSON (JObject o) = Records <$> (o .: "Records")
parseJSON _ = fail "Records parse failed"
instance s3FromJSON :: FromJSON S3 where
parseJSON (JObject o) = do
s3 <- o .: "s3"
b <- (s3 .: "bucket") >>= (.: "name")
k <- (s3 .: "object") >>= (.: "key")
return $ S3 { bucket: b, key: k }
parseJSON _ = fail "S3 parse failed"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment