Parse top-level value with Aeson - haskell

I'm trying to parse JSON values with Aeson and I have no problem (so far) parsing objects or arrays, but I can't get Aeson to parse JSON documents that are just strings.
As I understand, since RFC 7159 values are legal JSON documents, and Aeson supports that since 0.9.0.0 (I'm using 0.9.0.1), so it should work. For example, I'm wrapping an API that returns strings as top-level JSON documents for many of its calls, and would like to newtype those strings for some static typing safety:
newtype Bar = Bar String deriving (Eq, Show)
instance FromJSON Bar where
parseJSON (String v) = pure (Bar $ T.unpack v)
parseJSON _ = mzero
If I try to decode something:
decode "JustSomeRandomString" :: Maybe Bar
all I get is Nothing in return.
Any ideas what I'm doing wrong? Of course, I could handle API calls that return strings as JSON documents without Aeson, but would like to keep things uniform!

Try decode "\"JustSomeRandomString\"" :: Maybe Bar

Related

How to parse a JSON string using Aeson that can be one of two different types

I'm currently struggling to parse some JSON data using the aeson library. There are a number of properties that have the value false when the data for that property is absent. So if the property's value is typically an array of integers and there happens to be no data for that property, instead of providing an empty array or null, the value is false. (The way that this data is structured isn't my doing so I'll have to work with it somehow.)
Ideally, I would like to end up with an empty list in cases where the value is a boolean. I've created a small test case below for demonstration. Because my Group data constructor expects a list, it fails to parse when it encounters false.
data Group = Group [Int] deriving (Eq, Show)
jsonData1 :: ByteString
jsonData1 = [r|
{
"group" : [1, 2, 4]
}
|]
jsonData2 :: ByteString
jsonData2 = [r|
{
"group" : false
}
|]
instance FromJSON Group where
parseJSON = withObject "group" $ \g -> do
items <- g .:? "group" .!= []
return $ Group items
test1 :: Either String Group
test1 = eitherDecode jsonData1
-- returns "Right (Group [1,2,4])"
test2 :: Either String Group
test2 = eitherDecode jsonData2
-- returns "Left \"Error in $.group: expected [a], encountered Boolean\""
I was initially hoping that the (.!=) operator would allow it to default to an empty list but that only works if the property is absent altogether or null. If it were "group": null, it would parse successfully and I would get Right (Group []).
Any advice for how to get it to successfully parse and return an empty list in these cases where it's false?
One way to solve this problem is to pattern match on the JSON data constructors that are valid for your dataset and raise invalid for all others.
For instance, you could write something like this for that particular field, keeping in mind that parseJSON is a function from Value -> Parser a:
instance FromJSON Group where
parseJSON (Bool False) = Group <$> pure []
parseJSON (Array arr) = pure (Group $ parseListOfInt arr)
parseJSON invalid = typeMismatch "Group" invalid
parseListOfInt :: Vector Value -> [Int]
parseListOfInt = undefined -- build this function
You can see an example of this in the Aeson docs, which are pretty good (but you kind of have to read them closely and a few times through).
I would probably then define a separate record to represent the top-level object that this key comes in and rely on generic deriving, but others may have a better suggestion there:
data GroupObj = GroupObj { group :: Group } deriving (Eq, Show)
instance FromJSON GroupObj
One thing to always keep in mind when working with Aeson are the core constructors (of which there are only 6) and the underlying data structures (HashMap for Object and Vector for Array, for instance).
For example, in the above, when you pattern match on Array arr, you have to be aware that you're getting a Vector Value there in arr and we still have some work to do to turn this into a list of integers, which is why I left that other function parseListOfInt undefined up above because I think it's probably a good exercise to build it?

Contextual generation of JSON in Haskell using Aeson

I have a complex nested data structure that I would like to convert to JSON in different ways depending on some provided context. My use case is that my server contains the full state of the world state, but depending on which client is asking for it I want to provide a redacted copy. Ideally I'd like to write something like:
instance ToJSON MyNestedType where
toJSON x = do
currentUser <- ask
return $ if owner x == currentUser then (defaultToJson x) else (toJSON "REDACTED")
encodeWithReader (UserId 123) myDataStructure
Looking at the type of toJSON :: a -> Value this doesn't appear to be possible with plain Aeson. What would be a good option for doing this? A couple of options I was thinking about:
Implementing my own typeclass ToJSONReader, and having a default implementation that simply passes through to ToJSON and overriding for the types that need redacting. Something like (this doesn't compile, just pseudocode. I don't actually know how to make this work.):
class ToJSONReader a where
toJSONReader :: a -> Reader b Value
instance ToJSON a => ToJSONReader a where
toJSONReader x = return $ toJSON x
instance ToJSONReader MyNestedType where
toJSONReader x = do
currentUser <- ask
return $ if owner x == currentUser then (toJSON x) else (toJSON "REDACTED")
Rather than use encode directly, use toJSON to get the intermediate Value then write code to redact that (kind of gross).
Extend my type to include tags for redaction, then pre-process a copy of the type before converting to JSON.
Create a new complex parent type RedactedMyType and duplicate the structure of the original type, but subbing in redacted options in the ADT as needed. Pretty gross also.
Does anyone have any recommendations?

Haskell ADTs with aeson

I've been fighting with a simple ADT, trying to get it to round-trip back and forth to JSON, but I've had no luck, no matter how I try to massage or modify the type. What am I missing?
When it compiles, I always get the same runtime error:
> let t = Fahrenheit
> fromJSON $ toJSON t
Error "when expecting a (), encountered Object instead"
Trying this just gives me "Nothing", presumably because of the same error: decode $ encode t
I've tried to follow these sources, but I can't seem to get around this runtime error, no matter what I try:
Haskell :: Aeson :: parse ADT based on field value
https://www.fpcomplete.com/user/Geraldus/algebraic-data-types-adts-with-aeson
Here's one form of the code I'm using. At first, I tried to use this as a type embedded in another type, but when that didn't work I added the "value" key to try to make parsing of this easier (no luck).
data TemperatureType = Celsius
| Fahrenheit
deriving (Show,Read,Typeable,Data,Eq)
-- This doesn't work either
-- $(deriveJSON defaultOptions ''TemperatureType)
instance ToJSON TemperatureType where
toJSON Fahrenheit = object [ "value" .= String "Fahrenheit" ]
toJSON Celsius = object [ "value" .= String "Celsius" ]
instance FromJSON TemperatureType where
parseJSON (Object x) = toTemperatureType <$> x .: "value"
toTemperatureType :: Text -> TemperatureType
toTemperatureType "Fahrenheit" = Fahrenheit
toTemperatureType "Celsius" = Celsius
Haskell need help from you about the type of your expression result since in the current call it's not possible to infer it:
> fromJSON $ toJSON t :: Result TemperatureType

How can I easily express that I don't care about a value of a particular data field?

I was writing tests for my parser, using a method which might not be the best, but has been working for me so far. The tests assumed perfectly defined AST representation for every code block, like so:
(parse "x = 5") `shouldBe` (Block [Assignment [LVar "x"] [Number 5.0]])
However, when I moved to more complex cases, a need for more "fuzzy" verification arised:
(parse "t.x = 5") `shouldBe` (Block [Assignment [LFieldRef (Var "t") (StringLiteral undefined "x")] [Number 5.0]])
I put in undefined in this example to showcase the field I don't want to be compared to the result of parse (It's a source position of a string literal). Right now the only way of fixing that I see is rewriting the code to make use of shouldSatisfy instead of shouldBe, which I'll have to do if I don't find any other solution.
You can write a normalizePosition function which replaces all the position data in your AST with some fixed dummyPosition value, and then use shouldBe against a pattern built from the same dummy value.
If the AST is very involved, consider writing this normalization using Scrap-Your-Boilerplate.
One way to solve this is to parametrize your AST over source locations:
{-# LANGUAGE DeriveFunctor #-}
data AST a = ...
deriving (Eq, Show, Functor)
Your parse function would then return an AST with SourceLocations:
parse :: String -> AST SourceLocation
As we derived a Functor instance above, we can easily replace source locations with something else, e.g. ():
import Data.Functor ((<$))
parseTest :: String -> AST ()
parseTest input = () <$ parse input
Now, just use parseTest instead of parse in your specs.

parseJSON class method with dependent type

and thanks in advance for the help :)
Here's the problem I'm trying to solve:
I have a type (MyType) and I wrote a JSON parser for it (Using aeson library), and this parser depends on another value (Config):
import Data.Aeson
data MyType = MyType Text
data Config = Config Text
parseMyType :: Config -> Value -> Parser MyType
parseMyType (Config f) (Object o) = do (String v) <- o .: f
return $ MyType v
What I really wanted to write a FromJSON instance for it... but the parseJSON only depends on the Value (cannot have Config):
instance FromJSON MyType where
parseJSON :: Value -> Parser MyType
parseJSON = ???
I'm wondering if it's possible to use type class in this case. I'm probably missing some type trick... or maybe there's a language extension I can use?
Thank you!
If you want to "magic up" an instance declaration based on runtime information, this is possible through the reflection library, though it is some rather deep magic and there is probably a nicer way: https://www.fpcomplete.com/user/thoughtpolice/using-reflection

Resources