Getting my feet wet with building stuff, and not being able to get Aeson to work properly I decided my new project is building a JSON parser. Very abstract since it is one way or another, so it wouldn't make sense to put all the code here.
The ByteString library lets me do what I need. Remove characters, replace stuff, but: I have a very hard time reconstructing it the exact way I took it apart. Data.Text however seems more appropriate for the job but when generated a lot of noise with /"/, \n etc.
What would be the best and fastest way to clear a file from all rubbish and restore the remaining parts to useful text? Very small part below. Remarks on the code are welcome. Learning here.
import Network.HTTP.Simple
import GHC.Generics
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
import Data.Text as T
import Data.Char
import Data.Text.Encoding as DTE
word8QuoteMark = fromIntegral (ord '"')
word8Newline = fromIntegral (ord '\n')
word8Backslash = fromIntegral (ord ':')
filterJson jsonData = B.filter (/= word8Backslash)
(B.filter (/= word8Newline)
(B.filter (/= word8QuoteMark) jsonData))
importJson :: IO ()
importJson = do
jsonData <- B.readFile "local.json"
output <- return (filterJson jsonData)
print $ (output)
Now the downside is, that if someone is called eg. François, it is now returned as Fran\195\167ois. I think I would need a lot more steps to do this in Data.Text, but correct me if I am wrong...
Note: i saw in a post that Daniel Wagner strongly advises against ByteString for text, but just for the sake of argument.
JSON is, by definition, a Unicode string that represents a data structure. What you get from B.readFile, though, is a raw byte string that you must first decode to get a Unicode string. To do that, you need to know what encoding was used to create the file. Assuming the file uses UTF-8 encoding, you can do something like
import Data.Text
importJson :: String -> IO Text
importJson name = do
jsonData <- B.readFile name
return (Data.Text.Encoding.decodeUtf8 jsonData)
Once you have a Text value, you can parse that into some data structure according to the JSON grammar.
Related
I have a csv at some filePath with two columns with no headers
john,304
sarah,300
...
I have been able to read the csv as such:
import Data.Csv as Csv
import Data.ByteString.Lazy as BL
import Data.Vector as V
...
results <- fmap V.toList . Csv.decode #(String,Integer) Csv.NoHeader <$> BL.readFile filePath
-- Right [("john",300),("sarah",302)]
If I have custom data type for the csv columns as such:
data PersonnelData = PersonnelData
{ name :: !String
, amount :: !Integer
} deriving (Show, Generic, Csv.FromRecord)
How can I modify the above to decode / read the file for this data type?
Where you have this:
Csv.decode #(String,Integer)
You are are using a visible type application to explicitly tell Csv.decode that its first type parameter should be the type (String, Integer). Let's have a look at the signature for decode to see what that means:
decode :: FromRecord a
=> HasHeader
-> ByteString
-> Either String (Vector a)
There's only one type parameter, and it's pretty clear from where it appears (in the output) that it is the type that decode is decoding into. So Csv.decode #(String,Integer) is a function that explicitly decodes CSV records to (String, Integer).
So the only change you need to make to your code is to explicitly tell it you want to decode to PersonnelData instead of (String, Integer). Just use Csv.decode #PersonnelData. (You need a FromRecord instance, but you already have provided that by deriving it)
I'm trying to navigate JSON values using Haskell, in GHCI. I can get a JSON payload from an API, with something like this:
import Network.HTTP.Simple
baseURL <- parseRequest "https://www.googleapis.com/books/v1/volumes"
let queryString = B8.pack $ unpack q
let request = setRequestQueryString [("q", Just queryString)] $ baseURL
resp <- httpJSON request
let body = getResponseBody resp :: Object
And that gives me an Object. That object (a HashMap) contains the key "items" whose value is an Array of Objects. I want to get the first object from taht array, then gets its volumeInfo, then its industryIdentifiers, then its isbn.
In Python I would do:
identifiers = body['items'][0]['industryIdentifiers']
isbn = [id['itentifier'] for id in identifiers if id['type'] == 'ISBN_10'][0]
Or in other words, just chain accessors. How can I do this in Haskell? I've tried something like ((body ! "items") !! 0) ! "volumeInfo") but I keep getting errors like Couldn't match expected type ‘[a]’ with actual type ‘Value’.
All the tutorials I can find just say to model the data by creating a complete picture of the data as a Haskell data structure, then writing a decoder to turn that JSON data into a Haskell data object. That seems like massive overkill in this case, when the data structure I'm getting from the API is way bigger than the bit that I need, which is just the ISBN.
How does one normally drill down through a big data structure in Haskell?
The preferred way is to not manually deal with JSON values, but instead parse them into a suitable Haskell type and then index into that, which is much safer: if the input doesn't conform to the expected format, you get a clear parsing error show up at which location in the data structure something is missing, instead of an obscure key-missing error somewhere deep in your code.
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
data GoogleBooksVolumes = GoogleBooksVolumes
{ items :: Array GoogleBooksVolume
, ...
} deriving (Generic, FromJSON, ToJSON)
data GoogleBooksVolume = GoogleBooksVolume
{ ...
, industryIdentifiers :: Array IndustryIdentifier
, ...
} deriving (Generic, FromJSON, ToJSON)
...
If you're going to ad-hoc index into the JSON object, your best bet is the aeson-lens package. That allows you to do something very similar – and similarly unsafe – as in Python.
{-# LANGUAGE OverloadedStrings #-}
Just identifiers = Just body ^. key "items" . nth 0 . key "industryIdentifiers"
Just isbn = head [ Just idf ^. key "identifier"
| idf <- identifiers
, Just idf ^. key "type" == Just (String "ISBN_10") ]
TBH this is even worse than in Python, because if a key fails to match you just get a Nothing result without any information at all what went wrong.
A safer option is to manually pattern-match at every decision where something could go wrong, but that is a lot of boilerplate.
The newest version of Data.Aeson changed the way that ToJSON and FromJSON work for simple types like:
data Permission = Read | Write
It used to be that the generic call:
instance ToJSON Permission where
...Would create JSON that looked like {"Read":[]} or {"Write":[]}.
But now it creates:
{tag:"Read",contents:"[]"}
Which makes sense but breaks code I have written. I wrote a toJSON part by hand to give the correct looking stuff but writing the fromJSON is confusing me.
Any ideas?
Thanks
You could control how datatype with all nullary constructors is encoded using allNullaryToStringTag field on Data.Aeson.Options. Set it to True and it will be encoded simply as string.
import Data.Aeson.Types (Options (..), defaultOptions)
data Permission = Read | Write
$(deriveToJSON (defaultOptions {allNullaryToStringTag = True}) ''Permission)
Take a look at Options definition, it contains other handy fields.
Since the value contained in the Object constructor for Data.Aeson.Value is just a strict HashMap, we can extract the keys from it and make a decision based on that. I tried this and it worked pretty well.
{-# LANGUAGE OverloadedStrings #-}
module StackOverflow where
import Data.Aeson
import Control.Monad
import Data.HashMap.Strict (keys)
data Permission = Read | Write
instance FromJSON Permission where
parseJSON (Object v) =
let ks = keys v
in case ks of
["Read"] -> return Read
["Write"] -> return Write
_ -> mzero
parseJSON _ = mzero
You can test it with decode "{\"Read\": []}" :: Maybe Permission. The mzero in parseJSON ensures that if something else is passed in, it'll just return Nothing. Since you seem to want to only check if there is a single key matching one of your two permissions, this is pretty straightforward and will properly return Nothing on all other inputs.
I've been trying to learn how to extract data from HTML files in Haskell, and have hit a wall. I'm not really experience with Haskell at all, and my previous knowledge is from Python (and BeatifulSoup for HTML parsing).
I'm using TagSoup to look at my HTML (seemed to be recommended) and sort of have a basic idea of how it works. Here's the basic segment of my code in question (self-contained, and outputs information for testing):
import System.IO
import Network.HTTP
import Text.HTML.TagSoup
import Data.List
main :: IO ()
main = do
http <- simpleHTTP (getRequest "http://www.cbssports.com/nba/scoreboard/20130310") >>= getResponseBody
let tags = dropWhile (~/= TagOpen "div" []) (parseTags http)
done tags where
done xs = case xs of
[] -> putStrLn $ "\n"
_ -> do
putStrLn $ show $ head xs
done (tail xs)
However, I'm not trying to get to any "div" tag. I want to drop everything prior to a tag in a format like this:
TagOpen "div" [("id","scores-1997830"),("class","scoreBox spanCol2")]
TagOpen "div" [("id","scores-1997831"),("class","scoreBox spanCol2 lastCol")]
I've tried writing it out:
let tags = dropWhile (~/= TagOpen "div" [("id", "scores-[0-9]+"), ("class", "scoreBox( spanCol[0-9]?)+( lastCol)?")]) (parseTags http)
But then it tries to find the literal [0-9]+. I haven't figured out a workaround with the Text.Regex.Posix module yet, and escaping the characters doesn't work. What's the solution here?
~== does not do regular expressions, you will have to write a matcher yourself, something along the lines of
import Data.Maybe
import Text.Regex
goodTag :: TagOpen -> Bool
goodTag tag = tag ~== TagOpen "div" []
&& fromAttrib "id" tag `matches` "scores-[0-9]+"
-- Just a wrapper around Text.Regex.matchRegex
matches :: String -> String -> Bool
matches string regex = isJust $ mkRegex regex `matchRegex` string
I'm working on simple Haskell programme that fetches a JSON string from a server, parses it, and does something with the data. The specifics are not really pertinent for the moment, the trouble I'm having is with parsing the JSON that is returned.
I get the JSON string back from the server as an IO String type and can't seem to figure out how to parse that to a JSON object.
Any help would be much appreciated :)
Here is my code thus far.
import Data.Aeson
import Network.HTTP
main = do
src <- openURL "http://www.reddit.com/user/chrissalij/about.json"
-- Json parsing code goes here
openURL url = getResponseBody =<< simpleHTTP (getRequest url)
Note: I'm using Data.Aeson in the example as that is what seems to be recommended, however I'd be more than willing to use another library.
Also any and all of this code can be changed. If getting the
Data.Aeson is designed to be used with Attoparsec, so it only gives you a Parser that you must then use with Attoparsec. Also, Attoparsec prefers to work on ByteString, so you have to alter the way the request is made slightly to get a ByteString result instead of a String.
This seems to work:
import Data.Aeson
import Data.Attoparsec
import Data.ByteString
import Data.Maybe
import Network.HTTP
import Network.URI
main = do
src <- openURL "http://www.reddit.com/user/chrissalij/about.json"
print $ parse json src
openURL :: String -> IO ByteString
openURL url = getResponseBody =<< simpleHTTP (mkRequest GET (fromJust $ parseURI url))
Here I've just parsed the JSON as a plain Value, but you'll probably want to create your own data type and write a FromJSON instance for it to handle the conversion neatly.