Reading JSON using Text.JSON

Hello, I have this JSON string that I have put in a text file, test.json: {"coordinates": [0.0, 1.0]} using GHCi, I can read it like this:
s <- readFile "test.json" let r = decode s :: Result JSValue r Ok (JSObject (JSONObject {fromJSObject = [("coordinates",JSArray [JSRational False (0 % 1),JSRational False (1 % 1)])]}))
I want to affect this array of two Double to the type Coordinates being defined like this: type Coordinates = (Double, Double) Using this type I can create a Node: data Node = Node Coordinates Number deriving (Eq, Ord, Show) The problem is that I have no idea what to do with decoded JSValue. It seems I have to define an instance of JSON to read it. Does this mean using pattern matching to extract the values ? What do I do with the Result type ? Should I get rid of it like this: fromResult :: Result JSValue -> JSValue fromResult (Ok js) = js fromResult _ = JSNull and work with the raw JValue ? These questions might be trivial, but I know only imperative languages, which is why I like so much to learn Haskell and functional programming, lots to learn and discover :) Many thanks in advance, Adrien

I wrote down some stuff from my first look at json, maybe it can help you in getting further? http://therning.org/magnus/archives/719 /M On Sun, May 15, 2011 at 09:51:28PM +0200, Adrien Haxaire wrote:
Hello,
I have this JSON string that I have put in a text file, test.json:
{"coordinates": [0.0, 1.0]}
using GHCi, I can read it like this:
s <- readFile "test.json" let r = decode s :: Result JSValue r Ok (JSObject (JSONObject {fromJSObject = [("coordinates",JSArray [JSRational False (0 % 1),JSRational False (1 % 1)])]}))
I want to affect this array of two Double to the type Coordinates being defined like this:
type Coordinates = (Double, Double)
Using this type I can create a Node:
data Node = Node Coordinates Number deriving (Eq, Ord, Show)
The problem is that I have no idea what to do with decoded JSValue. It seems I have to define an instance of JSON to read it. Does this mean using pattern matching to extract the values ?
What do I do with the Result type ? Should I get rid of it like this:
fromResult :: Result JSValue -> JSValue fromResult (Ok js) = js fromResult _ = JSNull
and work with the raw JValue ?
These questions might be trivial, but I know only imperative languages, which is why I like so much to learn Haskell and functional programming, lots to learn and discover :)
Many thanks in advance, Adrien
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
-- Magnus Therning OpenPGP: 0xAB4DFBA4 email: magnus@therning.org jabber: magnus@therning.org twitter: magthe http://therning.org/magnus Perl is another example of filling a tiny, short-term need, and then being a real problem in the longer term. -- Alan Kay

On 15 May 2011 23:20, Magnus Therning
I wrote down some stuff from my first look at json, maybe it can help you in getting further?
FWIW, rather than: readJSON (JSObject obj) = let jsonObjAssoc = fromJSObject obj in do para <- mLookup "para" jsonObjAssoc >>= readJSON seeAlso <- mLookup "GlossSeeAlso" jsonObjAssoc >>= readJSON return $ GlossDef { glossDefPara = para , glossDefSeeAlso = seeAlso } You can just write: readJSON object = do obj <- readJSON object GlossDef <$> valFromObj "para" obj <*> valFromObj "GlossSeeAlso" obj

On Mon, May 16, 2011 at 01:38:02AM +0200, Christopher Done wrote:
On 15 May 2011 23:20, Magnus Therning
wrote: I wrote down some stuff from my first look at json, maybe it can help you in getting further?
FWIW, rather than:
readJSON (JSObject obj) = let jsonObjAssoc = fromJSObject obj in do para <- mLookup "para" jsonObjAssoc >>= readJSON seeAlso <- mLookup "GlossSeeAlso" jsonObjAssoc >>= readJSON return $ GlossDef { glossDefPara = para , glossDefSeeAlso = seeAlso }
You can just write:
readJSON object = do obj <- readJSON object GlossDef <$> valFromObj "para" obj <*> valFromObj "GlossSeeAlso" obj
FWIW, read the first comment ;-) I still consider the monadic style easier to read than the applicative so that's what I usually start with. /M -- Magnus Therning OpenPGP: 0xAB4DFBA4 email: magnus@therning.org jabber: magnus@therning.org twitter: magthe http://therning.org/magnus I invented the term Object-Oriented, and I can tell you I did not have C++ in mind. -- Alan Kay

On 16 May 2011 20:59, Magnus Therning
readJSON object = do obj <- readJSON object GlossDef <$> valFromObj "para" obj <*> valFromObj "GlossSeeAlso" obj
FWIW, read the first comment ;-)
I still consider the monadic style easier to read than the applicative so that's what I usually start with.
I was more referring to the fact that you're re-implementing valFromObj and needlessly pattern matching.

On Mon, May 16, 2011 at 09:07:25PM +0200, Christopher Done wrote:
On 16 May 2011 20:59, Magnus Therning
wrote: readJSON object = do obj <- readJSON object GlossDef <$> valFromObj "para" obj <*> valFromObj "GlossSeeAlso" obj
FWIW, read the first comment ;-)
I still consider the monadic style easier to read than the applicative so that's what I usually start with.
I was more referring to the fact that you're re-implementing valFromObj and needlessly pattern matching.
Indeed I am, thanks for pointing that out. /M -- Magnus Therning OpenPGP: 0xAB4DFBA4 email: magnus@therning.org jabber: magnus@therning.org twitter: magthe http://therning.org/magnus Perl is another example of filling a tiny, short-term need, and then being a real problem in the longer term. -- Alan Kay

Hello, Thanks for your help and advices, I managed to read the sample file I have ! At the moment I can read a node like this : instance JSON Node where readJSON object = do obj <- readJSON object coords <- valFromObj "coordinates" obj number <- valFromObj "number" obj return (Node coords number) showJSON (Node coords number) = makeObj [("coordinates",showJSON coords),("number", showJSON number)] I will take a look at how to extend it, as I will have much more than nodes to read. Still, I like the solution using <$>, <*>, etc, and would like to use it. I will need to read more about Applicative typeclasses before, though. But that's why I wanted to learn Haskell: I have many things to discover :) The only thing that I do not understand yet, is why I can't use the 'Node' data which I defined in another module. If the Node data declaration is in the file in which I read the JSON, it is ok. If I import the module in which I want to place it, ghc tells me that the data constructor 'Node' is not in scope. in Input.hs: import Elements (Node) and in Elements.hs: module Elements ( Node(..) , Element(..) , Material(..) ) where [...] data Node = Node Coordinates Number deriving (Eq, Ord, Show) Regarding pattern matching, I really like it. I am just discovering that it is way more powerful than I first thought! To recap, the clouds which where on this JSON handling are gone, now it is mainly practicing it which will make me do precisely what I want with it. Thanks a lot again for your tutorial, help and comments. This is very cheerful, and mmakes me as a part of the community, even if I am just starting :) best regards, Adrien Le 16/05/2011 21:21, Magnus Therning a écrit :
On Mon, May 16, 2011 at 09:07:25PM +0200, Christopher Done wrote:
On 16 May 2011 20:59, Magnus Therning
wrote: readJSON object = do obj<- readJSON object GlossDef<$> valFromObj "para" obj <*> valFromObj "GlossSeeAlso" obj
FWIW, read the first comment ;-)
I still consider the monadic style easier to read than the applicative so that's what I usually start with.
I was more referring to the fact that you're re-implementing valFromObj and needlessly pattern matching.
Indeed I am, thanks for pointing that out.
/M
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

On Mon, May 16, 2011 at 10:40:23PM +0200, Adrien Haxaire wrote:
Hello,
The only thing that I do not understand yet, is why I can't use the 'Node' data which I defined in another module. If the Node data declaration is in the file in which I read the JSON, it is ok. If I import the module in which I want to place it, ghc tells me that the data constructor 'Node' is not in scope.
in Input.hs: import Elements (Node)
This only imports the Node type and not its constructors. If you want to import its constructors as well you have to write import Elements (Node(..)) (just like in the export list of the Elements module). -Brent

This isn't submitted as an answer to your question but having used Text.JSON
before in a project, for this one or maybe the next project you work on, you
might want to consider using Aeson. I find the API way nicer to work with
and it is apparently quite performant.
On Sun, May 15, 2011 at 12:51 PM, Adrien Haxaire
Hello,
I have this JSON string that I have put in a text file, test.json:
{"coordinates": [0.0, 1.0]}
using GHCi, I can read it like this:
s <- readFile "test.json" let r = decode s :: Result JSValue r Ok (JSObject (JSONObject {fromJSObject = [("coordinates",JSArray [JSRational False (0 % 1),JSRational False (1 % 1)])]}))
I want to affect this array of two Double to the type Coordinates being defined like this:
type Coordinates = (Double, Double)
Using this type I can create a Node:
data Node = Node Coordinates Number deriving (Eq, Ord, Show)
The problem is that I have no idea what to do with decoded JSValue. It seems I have to define an instance of JSON to read it. Does this mean using pattern matching to extract the values ?
What do I do with the Result type ? Should I get rid of it like this:
fromResult :: Result JSValue -> JSValue fromResult (Ok js) = js fromResult _ = JSNull
and work with the raw JValue ?
These questions might be trivial, but I know only imperative languages, which is why I like so much to learn Haskell and functional programming, lots to learn and discover :)
Many thanks in advance, Adrien
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
-- Michael Xavier http://www.michaelxavier.net

On Sun, May 15, 2011 at 09:51:28PM +0200, Adrien Haxaire wrote:
The problem is that I have no idea what to do with decoded JSValue. It seems I have to define an instance of JSON to read it. Does this mean using pattern matching to extract the values ?
It's correct that you should define an instance of JSON to have a to/from JSON mapping for your data type. It's not required, but it's a very good pattern. All parsing is done using the instances. As you can see, λ> :t decode decode :: (JSON a) => String -> Result a internally, this function uses the JSON class's readJSON method. The return value is thus polymorphic. λ> decode "1" :: Result JSValue Ok (JSRational False (1 % 1)) λ> decode "1" :: Result Integer Ok 1 I have this JSON string that I have put in a text file, test.json:
{"coordinates": [0.0, 1.0]}
There is a tuple instance of JSON that you can use to parse the JSON tuple. instance (JSON a, JSON b) => JSON (a, b) λ> decode "[1,2]" :: Result (Double,Double) Ok (1.0,2.0) And for parsing objects, you use the JSObject data type: λ> decode "{\"coordinates\":[1,2]}" :: Result (JSObject (Integer,Integer)) Ok (JSONObject {fromJSObject = [("coordinates",(1,2))]}) Once you're done parsing you have either Ok or Error and you handle it accordingly. data Node = Node Coordinates Number deriving (Eq, Ord, Show) As far as implementing an instance for your data type goes, you write it like this: instance JSON Node where readJSON object = do obj <- readJSON object coords <- valFromObj "coordinates" obj return (Node coords 0) showJSON (Node coords integer) = makeObj [("coordinates",showJSON coords)] And then you can use decode for your data type: λ> decode "{\"coordinates\":[1,2]}" :: Result Node Ok (Node (1.0,2.0) 0) λ> encode (Node (1.0,2.0) 0) "{\"coordinates\":[1,2]}" To make the code nicer to read and feel more declarative, you can use the Applicative instance of JSON: readJSON object = do obj <- readJSON object Node <$> valFromObj "coordinates" obj <*> pure 0 This is equivalent to the lines above. It's nicer like this because with more parameters to the type, it still remains readable. Let's say you want to include the number in the JSON parsing, no problem: readJSON object = do obj <- readJSON object Node <$> valFromObj "coordinates" obj <*> valFromObj "number" obj λ> decode "{\"coordinates\":[1,2],\"number\":42}" :: Result Node Ok (Node (1.0,2.0) 42) Or let's say you want to make number optional. That's easy with the Alternative instance of JSON: readJSON object = do obj <- readJSON object Node <$> valFromObj "coordinates" obj <*> (valFromObj "number" obj <|> pure 0) λ> decode "{\"coordinates\":[1,2],\"number\":42}" :: Result Node Ok (Node (1.0,2.0) 42) λ> decode "{\"coordinates\":[1,2]}" :: Result Node Ok (Node (1.0,2.0) 0) And finally, for extracting the value, as you basically figured out above, you just pattern match on the Ok/Error: λ> case decode "{\"coordinates\":[1,2]}" of Ok (Node (x,y) n) -> do putStrLn $ "Coords: " ++ show (x,y) putStrLn $ "Number: " ++ show n Error msg -> do putStrLn $ "Failed: " ++ msg Coords: (1.0,2.0) Number: 0 That's pretty much the whole pattern. Or if you hate pattern matching, you can define a maybe/either-like function: result :: (String -> b) -> (a -> b) -> Result a -> b result f g (Error x) = f x result f g (Ok a) = g a λ> result (const $ putStrLn ":(") (putStrLn.show) $ (decode "{\"coordinates\":[1,2]}" :: Result Node) Node (1.0,2.0) 0

Wow, that's a very nice tutorial, thanks a lot Christopher for taking time to explain it to me with so much details. Í really appreciate it. I will try to have it working tonight (ie 20:00 GMT +1) and let you know. Michael, aeson looks interesting. I may give it a try if/when needed, but for know I prefer to focus on the Text.JSON module to learn Haskell, as it is the default one. I keep you updated as soon as I have it working (or not), and thanks again for the help ! Adrien On Mon, 16 May 2011 01:37:50 +0200, Christopher Done wrote:
On Sun, May 15, 2011 at 09:51:28PM +0200, Adrien Haxaire wrote:
It seems I have to define an instance of JSON to read it. Does
> The problem is that I have no idea what to do with decoded JSValue. this
mean using pattern matching to extract the values ?
It's correct that you should define an instance of JSON to have a to/from JSON mapping for your data type. It's not required, but it's a very good pattern. All parsing is done using the instances. As you can see,
λ> :t decode decode :: (JSON a) => String -> Result a
internally, this function uses the JSON class's readJSON method. The return value is thus polymorphic.
λ> decode "1" :: Result JSValue Ok (JSRational False (1 % 1)) λ> decode "1" :: Result Integer Ok 1
I have this JSON string that I have put in a text file, test.json:
{"coordinates": [0.0, 1.0]}
There is a tuple instance of JSON that you can use to parse the JSON tuple.
instance (JSON a, JSON b) => JSON (a, b)
λ> decode "[1,2]" :: Result (Double,Double) Ok (1.0,2.0)
And for parsing objects, you use the JSObject data type:
λ> decode "{"coordinates":[1,2]}" :: Result (JSObject (Integer,Integer)) Ok (JSONObject {fromJSObject = [("coordinates",(1,2))]})
Once you're done parsing you have either Ok or Error and you handle it accordingly.
data Node = Node Coordin
d; padding-left: 1ex; "> Eq, Ord, Show)
As far as implementing an instance for your data type goes, you write it like this:
instance JSON Node where readJSON o
(Node coords integer) = makeObj [("co t;,showJSON coords)]
And then you can use decode for your data type:
λ> decode "{"coordinates":[1,2]}" :: Result Node Ok (Node (1.0,2.0) 0) λ> encode (Node (1.0,2.0) 0) "{"coordinates":[1,2]}"
To make the code nicer to read and feel more declarative, you can use the Applicative instance of JSON:
readJSON object = do obj decode "{"coordinates":[1,2]}" :: Result Node Ok (Node (1.0,2.0) 0)
And finally, for extracting the value, as you basically figured out above, you just pattern match on the Ok/Error:
λ> case decode "{"coordinates":[1,2]}" of Ok (Node (x,y) n) -> do putStrLn $ "Coords: " ++ show (x,y) putStrLn $ "Number: " ++ show n Error msg -> do putStrLn $ "Failed: " ++ msg Coords: (1.0,2.0) Number: 0
That's pretty much the whole pattern.
Or if you hate pattern matching, you can define a maybe/either-like function:
result :: (String -> b) -> (a -> b) -> Result a -> b result f g (Error x) = f x result f g (Ok a) = g a
λ> result (const $ putStrLn ":(") (putStrLn.show) $ (decode "{"coordinates":[1,2]}" :: Result Node) Node (1.0,2.0) 0
participants (5)
-
Adrien Haxaire
-
Brent Yorgey
-
Christopher Done
-
Magnus Therning
-
Michael Xavier