Trying to read JSON from file using Aeson - json

Here's my code:
import Data.Aeson
import Control.Applicative
import Control.Monad
import Data.Text
import GHC.Generics
import qualified Data.ByteString.Lazy as B
data JSON' =
JSON' {
foo :: !Text,
int :: Int
} deriving (Show, Generic)
instance FromJSON JSON'
instance ToJSON JSON'
jsonFile :: FilePath
jsonFile = "test.json"
getJSON :: IO B.ByteString
getJSON = B.readFile jsonFile
main :: IO ()
main = do
-- Get JSON data and decode it
d <- (eitherDecode <$> getJSON) :: IO (Either String [JSON'])
-- If d is Left, the JSON was malformed.
-- In that case, we report the error.
-- Otherwise, we perform the operation of
-- our choice. In this case, just print it.
case d of
Left err -> putStrLn err
Right ps -> print ps
test.json looks liket his:
-- test.json
{
"foo": "bar",
"int": 1
}
When I run this code I get this error:
Can't make a derived instance of ‘Generic JSON'’:
You need DeriveGeneric to derive an instance for this class
In the data declaration for ‘JSON'’
So far the documentation for Aeson, like all documentation on Hackage, is not helpful at all. I have no idea what I'm doing wrong. So far it seems like I'm reading a file into a bytestring, transforming it into a "tree" like data structure, and then printing one leaf per node. My code is straight from this link
What am I doing wrong?
UPDATE
After adding the language extension declaration to the top of the file
{-# LANGUAGE DeriveGeneric #-}
I'm getting this error:
Error in $: expected [a], encountered Object
Not sure what this means.

you need to put {-# LANGUAGE DeriveGeneric #-} on the top of your file.
On a side note the documentation of aeson says
instance ToJSON Person where
-- ...
toEncoding = genericToEncoding defaultOptions
instance FromJSON Person
-- No need to provide a parseJSON implementation.
I think you probably also forgot the toEncoding line in your ToJSON.

Add this to the top of the file
{-# LANGUAGE DeriveGeneric #-}
The ability to use derive (Generic) is a language extension, and you have to tell GHC that you want this to be turned on.

Related

Reading nested JSON data encoded as a nested string with Aeson

I have this weird JSON to parse containing nested JSON ... a string. So instead of
{\"title\": \"Lord of the rings\", \"author\": {\"666\": \"Tolkien\"}\"}"
I have
{\"title\": \"Lord of the rings\", \"author\": \"{\\\"666\\\": \\\"Tolkien\\\"}\"}"
Here's my (failed) attempt to parse the nested string using decode, inside an instance of FromJSON :
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Maybe
import GHC.Generics
import Data.Aeson
import qualified Data.Map as M
type Authors = M.Map Int String
data Book = Book
{
title :: String,
author :: Authors
}
deriving (Show, Generic)
decodeAuthors x = fromJust (decode x :: Maybe Authors)
instance FromJSON Book where
parseJSON = withObject "Book" $ \v -> do
t <- v .: "title"
a <- decodeAuthors <?> v .: "author"
return $ Book t a
jsonTest = "{\"title\": \"Lord of the rings\", \"author\": \"{\\\"666\\\": \\\"Tolkien\\\"}\"}"
test = decode jsonTest :: Maybe Book
Is there a way to decode the whole JSON in a single pass ? Thanks !
A couple problems here.
First, your use of <?> is nonsensical. I'm going to assume it's a typo, and what you actually meant was <$>.
Second, the type of decodeAuthors is ByteString -> Authors, which means its parameter is of type ByteString, which means that the expression v .: "author" must be of type Parser ByteString, which means that there must be an instance FromJSON ByteString, but such instance doesn't exists (for reasons that escape me at the moment).
What you actually want is for v .: "author" to return a Parser String (or perhaps Parser Text), and then have decodeAuthors accept a String and convert it to ByteString (using pack) before passing to decode:
import Data.ByteString.Lazy.Char8 (pack)
decodeAuthors :: String -> Authors
decodeAuthors x = fromJust (decode (pack x) :: Maybe Authors)
(also note: it's a good idea to give you declarations type signatures that you think they should have. This lets the compiler point out errors earlier)
Edit:
As #DanielWagner correctly points out, pack may garble Unicode text. If you want to handle it correctly, use Data.ByteString.Lazy.UTF8.fromString from utf8-string to do the conversion:
import Data.ByteString.Lazy.UTF8 (fromString)
decodeAuthors :: String -> Authors
decodeAuthors x = fromJust (decode (fromString x) :: Maybe Authors)
But in that case you should also be careful about the type of jsonTest: the way your code is written, its type would be ByteString, but any non-ASCII characters that may be inside would be cut off because of the way IsString works. To preserve them, you need to use the same fromString on it:
jsonTest = fromString "{\"title\": \"Lord of the rings\", \"author\": \"{\\\"666\\\": \\\"Tolkien\\\"}\"}"

Haskell Aeson json encoding bytestrings

I need to serialize a record in Haskell, and am trying to do it with Aeson. The problem is that some of the fields are ByteStrings, and I can't work out from the examples how to encode them. My idea is to first convert them to text via base64. Here is what I have so far (I put 'undefined' where I didn't know what to do):
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.Aeson as J
import qualified Data.ByteString as B
import qualified Data.ByteString.Base64 as B64
import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import qualified GHC.Generics as G
data Data = Data
{ number :: Int
, bytestring :: B.ByteString
} deriving (G.Generic, Show)
instance J.ToJSON Data where
toEncoding = J.genericToEncoding J.defaultOptions
instance J.FromJSON Data
instance J.FromJSON B.ByteString where
parseJSON = undefined
instance J.ToJSON B.ByteString where
toJSON = undefined
byteStringToText :: B.ByteString -> T.Text
byteStringToText = E.decodeUtf8 . B64.encode
textToByteString :: T.Text -> B.ByteString
textToByteString txt =
case B64.decode . E.encodeUtf8 $ txt of
Left err -> error err
Right bs -> bs
encodeDecode :: Data -> Maybe Data
encodeDecode = J.decode . J.encode
main :: IO ()
main = print $ encodeDecode $ Data 1 "A bytestring"
It would be good if it was not necessary to manually define new instances of ToJSON and FromJSON for every record, because I have quite a few different records with bytestrings in them.
parseJson needs to return a value of type Parser B.ByteString, so you just need to call pure on the return value of B64.decode.
import Control.Monad
-- Generalized to any MonadPlus instance, not just Either String
textToByteString :: MonadPlus m => T.Text -> m B.ByteString
textToByteString = case B64.decode (E.encodeUtf8 x) of
Left _ -> mzero
Right bs -> pure bs
instance J.FromJSON B.ByteString where
parseJSON (J.String x) = textToByteString x
parseJSON _ = mzero
Here, I've chosen to return mzero both if you try to decode anything other than a JSON string and if there is a problem with the base-64 decoding.
Likewise, toJSON needs just needs to encode the Text value you create from the base64-encoded ByteString.
instance J.ToJSON B.ByteString where
toJSON = J.toJSON . byteStringToText
You might want to consider using a newtype wrapper instead of defining the ToJSON and FromJSON instances on B.ByteString directly.

Custom record to json key conversion in Haskell using Aeson library

Below code does not work for me. Can anyone explain how to solve and avoid below kind of errors in Haskell
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
module Test where
import GHC.Generics
import Data.Aeson.Types
import Data.Aeson
data Person = Person { personId :: Int , personName :: String} deriving (Show, Generic)
instance ToJSON Person where
toJson p = [
"id" .= personId p,
"name" .= personName p
]
instance FromJSON Person
I am getting following error. I am not able to figure out the issue here.
Prelude> :load src/User/Test
[1 of 1] Compiling Test ( src\User\Test.hs, interpreted )
src\User\Test.hs:13:5: error:
`toJson' is not a (visible) method of class `ToJSON'
Failed, modules loaded: none.
The name of the method is toJSON, not toJson. Identifiers are case-sensitive in Haskell. You can find this in the aeson documentation for the ToJSON class.

Aeson Generics compilation error with manual parseJSON function

I've created my own parseJSON function for my Valuation type. Unfortunately, I get compile errors about there not being a "Generic" version of Valuation and I'm not sure what to make of it. I've read and re-read as many aeson tutorials as I can and none of them seem to mention this. I've made my Valuation type an instance of the FromJSON class and provided my own implemention of the parseJSON function, but for some reason, the compiler seems to think that it's an implementation of the other parseJSON function, and I don't know why or how to fix it. Any help would be very much appreciated.
C:\Users\John\GitHub\haskell_projects\learning\src\Lib.hs:17:10:
error:
* No instance for (GHC.Generics.Generic Valuation)
arising from a use of aeson-0.11.2.1:Data.Aeson.Types.Class.$dmparseJSON'
* In the expression:
aeson-0.11.2.1:Data.Aeson.Types.Class.$dmparseJSON
In an equation forparseJSON':
parseJSON = aeson-0.11.2.1:Data.Aeson.Types.Class.$dmparseJSON
In the instance declaration for `FromJSON Valuation'
-- While building package learning-0.1.0.0 using:
C:\Users\John\AppData\Roaming\stack\setup-exe-cache\x86_64-windows\setup-Simple-Cabal-1.24.0.0-ghc-8.0.1.exe
--builddir=.stack-work\dist\b7fec021 build lib:learning exe:learning-exe --ghc-options " -ddump-hi -ddump-to-file"
Process exited with code: ExitFailure 1
Here's the code:
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{- The DuplicateRecordFields language extension allows records to use the same name for field labels. Without it, all the records in this module would need to have unique names for all their fields.
-}
module Lib
( someFunc,
) where
import Data.Time.Calendar
import Control.Lens
import Data.Aeson.Lens (_String, _Object, key)
import Network.Wreq
import Data.Aeson (Value(..), FromJSON, (.:), (.=), withObject)
data Valuation = Valuation {valued_on :: Day, price :: Double}
instance FromJSON Valuation where
parseJSON = withObject "valuation" $ \o -> do
query <- o .: "query"
results <- query .: "results"
quote <- results .: "quote"
price <- quote .: "Open"
return Valuation{valued_on=today, price=price}

Haskell aeson package basic usage

Working my way through Haskell and I'm trying to learn how to serialized to/from JSON.
I'm using aeson-0.8.0.2 & I'm stuck at basic decoding. Here's what I have:
file playground/aeson.hs:
{-# LANGUAGE OverloadedStrings #-}
import Data.Text
import Data.Aeson
data Person = Person
{ name :: Text
, age :: Int
} deriving Show
instance FromJSON Person where
parseJSON (Object v) = Person <$>
v .: "name" <*>
v .: "age"
parseJSON _ = mzero
main = do
let a = decode "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person
print "aa"
ghc --make playground/aeson.hs yields:
[1 of 1] Compiling Main ( playground/aeson.hs,
playground/aeson.o )
playground/aeson.hs:13:35: Not in scope: `'
playground/aeson.hs:14:40: Not in scope: `<*>'
playground/aeson.hs:17:28: Not in scope: `mzero'
Any idea what I'm doing wrong?
Why is OverloadedString needed here?
Also, I have no idea what <$>, <*>, or mzero are supposed to mean; I'd appreciate tips on where I can read about any of these.
You need to import Control.Applicative and Control.Monad to get <$>, <*> and mzero. <$> just an infix operator for fmap, and <*> is the Applicative operator, you can think of it as a more generalized form of fmap for now. mzero is defined for the MonadPlus class, which is a class representing that a Monad has the operation
mplus :: m a -> m a -> m a
And a "monadic zero" element called mzero. The simplest example is for lists:
> mzero :: [Int]
[]
> [1, 2, 3] `mplus` [4, 5, 6]
[1, 2, 3, 4, 5, 6]
Here mzero is being used to represent a failure to parse. For looking up symbols in the future, I recommend using hoogle or FP Complete's version. Once you find the symbol, read the documentation, the source, and look around for examples of its use on the internet. You'll learn a lot by looking for it yourself, although it'll take you a little while to get used to this kind of research.
The OverloadedStrings extension is needed here because the Aeson library works with the Text type from Data.Text instead of the built-in String type. This extension lets you use string literals as Text instead of String, just as the numeric literal 0 can be an Int, Integer, Float, Double, Complex and other types. OverloadedStrings makes string literals have type Text.String.IsString s => s instead of just String, so it makes it easy to use alternate string-y types.
For <$> you need to import Control.Applicative and for mzero you need to import Control.Monad.
You can determine this by using the web-based version of hoogle (http://www.haskell.org/hoogle) or the command line version:
$ hoogle '<$>'
$ hoogle mzero