I am struggling to figure out an issue with manipulating JSON with Aeson lenses. My task is as simple as to add a key to a nested object in JSON. I was able to change the existing keyby means of:
> :set -XOverloadedStrings
> import Control.Lens
> import Data.Aeson
> import Data.Aeson.Lens
> "{ \"a\": { \"b\": 10 } }" & key "a" . key "b" .~ String "jee"
"{\"a\":{\"b\":\"jee\"}}"
But when I try to make it deal with the new key, it just silently fails to add it:
> "{ \"a\": { \"b\": 10 } }" & key "a" . key "c" .~ String "jee"
"{\"a\":{\"b\":10}}"
Certainly it's me doing something wrong, but I figure I'm out of mana to understand what exactly.
Would you kindly point me in the right direction?
Thank you!
As dfeuer noted, at can insert into maps, while key and ix merely traverse elements if they exist. We can do the following:
> "{ \"a\": { \"b\": 10 } }" & key "a" . _Object . at "c" ?~ String "foo"
"{\"a\":{\"b\":10,\"c\":\"foo\"}}
at is a lens focusing on Maybe element-s, and we can insert by setting to Just some element, and remove by setting to Nothing. at "c" ?~ String "foo" is the same as at "c" .~ Just (String "foo").
If we want to do nested inserts, we can use non to define a default value to be inserted:
> "{ \"a\": { \"b\": 10 } }" & key "a" . _Object . at "c" . non (Object mempty) . _Object . at "d" ?~ String "foo"
"{\"a\":{\"b\":10,\"c\":{\"d\":\"foo\"}}}"
This is a mouthful, so we can factor some parts out:
> let atKey k = _Object . at k
> "{ \"a\": { \"b\": 10 } }" & key "a" . atKey "c" . non (Object mempty) . atKey "d" ?~ String "foo"
key is based on ix, whose documentation indicates it isn't powerful enough to do what you want and points to Control.Lens.At.at. I'm pretty sure that should do the trick for you. The basic idea is that you start with the _Object prism to turn the JSON text into an object, then use at key to get a lens into that field as a Maybe. You can then change it to Just what you want.
This will work very well as long as all the objects along the path you wish to take exist. If you want to (potentially) start from nothing and create a chain of single-field objects, you will likely find things more annoying. Fortunately, you probably don't need to do this.
Related
I have a following programming language grammar:
data Expr = ...
data Stmt = SExpr Expr | SBlock Block | SLet Fundef | ...
data Block = Block [Stmt]
data Fundef = Fundef String [String] Block
data TopDef = TopFun Fundef
With following example syntax:
function long_function_name () = {
let g() = {
{
h()
};
3
}
}
I am trying to use HughesPJ pretty library to create a pretty printer for this language. My attempts so far look like:
instance Pretty Stmt where
pPrint = \case
SExpr e -> pPrint e
SBlock b -> pPrint b
SLet f -> text "let" <+> pPrint f
instance Pretty Block where
pPrint (Block stmts) = lbrace $+$
nest 2 (vcat (punctuate semi (map pPrint stmts))) $+$
rbrace
instance Pretty Fundef where
pPrint (Fundef name args body) = pPrint name <> parens (...) <+> text "=" <+> pPrint body
instance Prettty TopDef where
pPrint (TopFun f) = text "function" <+> pPrint f
The problem is, I want to have { in the same line as the function declaration, but it always makes the indentation of the following lines relative to the column of the bracket instead of being absolute. Should be visible in the pretty print of the example above;
function long_function_name () = {
let g() = {
{
h()
};
3
}
}
Why does it happen and how should I tackle this problem? I would like to avoid as much code duplication as possible.
You’re writing <+> before the body, so the $+$ vertical concatenation is entirely within that horizontal concatenation of the function line, hence it’s all indented. I believe the way to do what you want with pretty is to explicitly match on the block, since it’s part of the vertical layout, i.e.:
pPrint (Fundef name args (Block stmts)) = vcat
[ pPrint name <> parens (...) <+> text "=" <+> lbrace
, nest 2 (vcat (punctuate semi (map pPrint stmts)))
, rbrace
]
The more modern pretty-printing libraries like prettyprinter make this a little easier: nest (or indent, or hang) handles the indentation of lines following the first line in a vertical layout, so you can put the nest around the opening brace and body, and the closing brace outside the nesting, like so:
"prefix" <+> vcat
[ nest 4 $ vcat
[ "{"
, "body"
]
, "}"
]
⇓
prefix {
body
}
(NB. you can use OverloadedStrings like this instead of wrapping literals in text.)
But that won’t work with pretty, which seems to be designed to align the heck out of everything.
I also recommend prettyprinter for its other advantages, for example, a group function that allows you to express “put this on one line if it fits”, which is extremely helpful for making formatting robust & responsive to different rendering contexts.
I'm trying to write a function that does a LIKE sql query based on a parameter. I'm using Selda. I had this working before parameterizing the function:
selectBookQuery :: Query s (Row s GoodreadsBook)
selectBookQuery = do
book <- select goodreadsBooks
restrict ((book ! #title) `like` ("%Wheel of Time%"))
return book
But, when I try to use the t param, like this:
selectBookQuery :: String -> Query s (Row s GoodreadsBook)
selectBookQuery t = do
book <- select goodreadsBooks
restrict ((book ! #title) `like` (T.pack $ "%" ++ t ++ "%"))
return book
I get this error:
Couldn't match expected type ‘Col s Text’ with actual type ‘Text’
I think I have a rough idea of what's happening. The string literal that I'm using in the first example is being coerced through the magic of OverloadedStrings to a Col s Text, whereas once I make it a Text or String, it doesn't get coerced into a Col s Text.
Hoogle was really helpful here, when I realized I have a Text but needed a Col s Text, hoogling Text -> Col s Text gave me the text function from Selda. So all I had to change was:
restrict ((book ! #title) `like` S.text (T.pack $ "%" ++ t ++ "%"))
where S is a qualified import of Database.Selda.
using gogol package,
follow example got
> exampleGetValue
-- ValueRange' {_vrValues = Just [String "2018/1/1",String "2018/1/2"], _vrRange = Just "'\24037\20316\34920\&1'!A1:1", _vrMajorDimension = Just VRMDRows}
> exampleGetValue >>= return . view vrValues
-- [String "2018/1/1",String "2018/1/2"]
> mapM_ (print) (exampleGetValue >>= return . view vrValues)
String "2018/1/1"
String "2018/1/2"
Why there will be a string of words
How to do I can only show
2018/1/1
2018/1/2
Take a look at
[String "2018/1/1",String "2018/1/2"]
the result of
> exampleGetValue >>= return . view vrValues
Here the strings you are interested in, like "2018/1/1" are contained in another datatype String, which has, I assume, an automatically derived show instance, which will print the name of the Data constructor String.
You need to unpack the strings somehow to get rid of the printing of the word String.
As this is stackoverflow, and we are considered to provide answers, I will give you one possibility now, but before you read it, try to do it yourself:
unpackString (String w) = w
mapM_ (print . unpackString) (exampleGetValue >>= return . view vrValues)
You have to determine the type signature for unpackString yourself, as you didn't provided any types.
I am a beginner at Haskell and I am trying to use https://hackage.haskell.org/package/json-0.9.1/docs/Text-JSON.html to parse a JSON document.
In my task, i am given a JSON document, and I would like to return the value corresponding to "index" , for example in the following case:
{
"root": [
{
"root1": 157538
},
{
"root2": [
{
"leaf21": 3
}
]
},
{
"root3": [
{
"leaf31": "somestring"
},
{
"index": "foundit"
}
]
}
]
}
To be specific: if presented with a JSON document and a path like "root" -> "root3" -> "index" exists, I would like to return "foundit", else I would like to return Nothing. Everything else in the document is arbitrary: root3,root2,root1,root may or may not exist etc.
Now I can do this using lots of case statements and patterns matches, but having read https://wiki.haskell.org/All_About_Monads, I am wondering if there is a better way using something similar to the Maybe Monad and the sheep-cloning example, however i am not sure how to write the bind function ...
[in my real case the value I seek is actually 19-deep in the document so I have lots of case statements]
Please could you suggest how to use Monads to do this ?
Yes those case statements are not necessary
Your guesses are correct - but monads are not the correct answer in this case (no pun intended1).
This is a great job for Traversals, Prisms and Lenses - and of course the great aeson-library and lens-aeson.
{-# LANGUAGE OverloadedStrings #-}
module Test where
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Monoid ((<>))
jsonString = "{\"root\":[{\"root1\":157538}"
<> ",{\"root2\":[{\"leaf21\":3}]}"
<> ",{\"root3\":[{\"leaf31\":\"somestring\"}"
<> ",{\"index\":\"foundit\"}]}]}"
val :: Maybe Value
val = decode jsonString
indexMaybe :: Maybe Value
indexMaybe = val ^? _Just . key "root" . values
. key "root3" . values
. key "index"
So what does this do?
decode transforms a ByteString into a Maybe Value - Maybe because parsing might fail!
then the (^?) operator previews a traversal - i.e. it goes through the JSON Object and follows the json path you give;
For this you have at least to know the path to "index", if this path is unknown, you'd have to invest a bit more research into lenses/prisms/traversals or do a simple tree search on the parsed object.
Here is a spoiler for those who lack time to implement a search for "index" in a json object:
search :: Text -> Value -> Maybe Value
search txt o#(Object o') = let f y#(Just x) _ = y
f _ v = search txt v
in case o ^? key txt of
Nothing -> foldl' f Nothing o'
x -> x
search txt a#(Array a') = let f y#(Just x) _ = y
f _ v = search txt v
in foldl' f Nothing a'
search _ _ = Nothing
Alternatives
As #MarkSeeman already mentioned - a simple text search, might be much more efficient.
1: okay maybe a little bit
So I had a location class
data Location = Location {
title :: String
, description :: String
}
instance Show Location where
show l = title l ++ "\n"
++ replicate (length $ title l) '-' ++ "\n"
++ description l
Then I changed it to use Data.Text
data Location = Location {
title :: Text
, description :: Text
}
instance Show Location where
show l = T.unpack $
title l <> "\n"
<> T.replicate (T.length $ title l) "-" <> "\n"
<> description l
Using criterion, I benchmarked the time taken by show on both the String and Data.Text implementations:
benchmarks = [ bench "show" (whnf show l) ]
where l = Location {
title="My Title"
, description = "This is the description."
}
The String implementation took 34ns, the Data.Text implementation was almost six times slower, at 170ns
How do I get Data.Text working as fast as String?
Edit: Silly mistakes
I'm not sure how this happened, but I cannot replicate the original speed difference: now for String and Text I get 28ns and 24ns respectively
For the more aggressive bench "length.show" (whnf (length . show) l) benchmark, for String and Text, I get 467ns and 3954ns respectively.
If I use a very basic lazy builder, without the replicated dashes
import qualified Data.Text.Lazy.Builder as Bldr
instance Show Location where
show l = show $
Bldr.fromText (title l) <> Bldr.singleton '\n'
-- <> Bldr.fromText (T.replicate (T.length $ title l) "-") <> Bldr.singleton '\n'
<> Bldr.fromText (description l)
and try the original, ordinary show benchmark, I get 19ns. Now this is buggy, as using show to convert a builder to a String will escape newlines. If I replace it with LT.unpack $ Bldr.toLazyText, where LT is a qualified import of Data.Text.Lazy, then I get 192ns.
I'm testing this on a Mac laptop, and I suspect my timings are getting horribly corrupted by machine noise. Thanks for the guidance.
You can't make it as fast, but you can speed it up some.
Appending
Text is represented as an array. This makes <> rather slow, because a new array has to be allocated and each Text copied into it. You can fix this by converting each piece to a String first, and then concatenating them. I imagine Text probably also offers an efficient way to concatenate multiple texts at once (as a commenter mentions, you can use a lazy builder) but for this purpose that will be slower. Another good option might be the lazy version of Text, which probably supports efficient concatenation.
Sharing
In your String-based implementation, the description field doesn't have to be copied at all. It's just shared between the Location and the result of showing that Location. There's no way to accomplish this with the Text version.
In the String case you are not fully evaluating all of the string operations - (++) and replicate.
If you change your benchmark to:
benchmarks = [ bench "show" (whnf (length.show) l) ]
you'll see that the String case takes around 520 ns - approx 10 times longer.