Reading different data type in JSON/Haskell

This is something which I encountered today while writing a server in Haskell. We were getting the value of key “age” in our JSON file as a string, though we wanted to have it as an Integer. After much deliberation, wasting a whole day and as well as discussing on IRC, the below solution finally clicked.

Such a situation may arise in following circumstances:

  • The JSON file we are getting is from different tasks and each is binding variables differently. In one, a variable could be stored as Integer value whereas, in other as String, bool etc.
  • Some dumb JSON wrapper refuses to identify the type and encodes all information as String.

We were fetching a JSON file from a different server to process it on ours, but unfortunately the wrapper there would bind “age” value as a string. This created a ruckus in our data type which we had defined to be as Integer. Sure, one solution could have been to declare it as Text, but then we would not have been able to perform mathematical operations on it.

The variables and references being used here are different from ones in real time, but good enough to explain the hurdle and solution.

I had a data type UserProfile created as follow

data UserProfile = UserProfile { firsName :: String,
                                 lastName :: String,
                                 age      :: Int
                               } deriving (Show, Generic)

The challenge was that the json was encoding age as a string value. Something like this:

{“firstName” : “abc”,”lastName”:”xyz”,”age”:”22″}

The string data type of age was not at all convenient for me since I required it as an Int to perform mathematical operations on it. Certainly, implementing your own FromJSON instance is the answer. However, I could find very less information as to how to ‘implement’ such a conversion. After much head banging, and reading a variety of answers, the following solution clicked.

instance FromJSON UserProfile where
  parseJSON = withObject "userprofile" $ \o -> do
        firstName <- o .: "firstName"
        lastName <- o .: "lastName"
        age <- asum[
          o .: "age",
          do s <- o .: "age"
             case readMaybe s of
               Nothing -> fail "Not a number"
               Just x  -> return x]
        return UserProfile{..}

We would use a lambda function to bind object values to our fields. The magic we are concerned with happens inside the asum block. asum is a Parser defined in Data.Foldable. We may use any other as well. We check if the object is of type Int, if so we are happy. Then we have a do block which uses a readMaybe to read the object value for “age” key. readMaybe is better than read because it acts like a fail-safe with the help of Maybe. Note that this do block is inside the asum.  We have used a parser because (.:) returns a type of Parser. Hence, while performing our tricks with “age” field, we should be careful about returning a Parser.

One more point, to facilitate our return statement in the instance you would have to add the following pragma

{-# LANGUAGE RecordWildCards #-}

The UserProfile{..} annotation binds the values to the records present inside the data type and we dont have to explicitly tell the same. Some thing of the following fashion: UserProfile{firstName = firstName, lastName = lastName, age = age}. To do this, we must tell the compiler beforehand by including the pragma (That we are more lazy than you Haskell!!)

I hope the highlighted code helps you. If you find any grammatical/technical corrections, please do inform. (While learning Haskell, I have learned to be lazy just like it.)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s