
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