packages feed

RJson 0.3.2 → 0.3.3

raw patch · 3 files changed

+192/−15 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Text.RJson: instance [overlap ok] (Data FromJsonD t, Data TranslateFieldD t) => FromJson t
+ Text.RJson: fromJson :: (FromJson a) => a -> JsonData -> Either String a
+ Text.RJson: instance [overlap ok] (Data FromJsonD t, TranslateField t) => FromJson t
- Text.RJson: genericFromJson :: (Data FromJsonD a, Data TranslateFieldD a) => a -> JsonData -> Either String a
+ Text.RJson: genericFromJson :: (Data FromJsonD a, FromJson a, TranslateField a) => a -> JsonData -> Either String a

Files

+ README view
@@ -0,0 +1,175 @@+This is some documentation on how to use the library.  I wrote a blog+post about an older version of the library which may contain some+useful information on using syb-with-class:++http://lingnerd.blogspot.com/2007/12/pushing-haskells-type-system-to-limits.html++------------------------------------------------------------------------++Not all features of the library are covered in this document yet.+Unfortunately, the haddock documentation will not build at the moment+because of the Template Haskell code in RJson.hs. You might want to+look at Text/RJson.hs to check out the Haddock comments for the+methods of ToJson and FromJson.++------------------------------------------------------------------------++Suppose we have the following datatypes:++    data TestRecord2 = TestRecord2 {+       _c :: Int,+       _d :: String+    } deriving Show++    data TestRecord1 = TestRecord1 {+       _a :: String,+       _b :: TestRecord2+    } deriving Show++In order to use RJson, we first have to derive instances of Data and+Typeable for these types.  The following options/modules are required:++    {-# OPTIONS_GHC+     -XTemplateHaskell+     -XFlexibleInstances+     -XMultiParamTypeClasses+     -XFlexibleContexts+     -XUndecidableInstances #-}+    import Text.RJson+    import Data.Generics.SYB.WithClass.Basics+    import Data.Generics.SYB.WithClass.Derive++The following Template Haskell code can be used to derive the+instances automatically:++    $(derive[''TestRecord1, ''TestRecord2])++Now we can use the 'toJson' function to serialize TestRecord1 and+TestRecord2 structures. For example, the expression++    toJson (TestRecord1 { _a="foo", _b=TestRecord2 { _c=5, _d="bar"}})++will evaluate to the following JsonData object:++    {"a":"foo","b":{"c":5,"d":"bar"}++You can just pass this object to 'show' to convert it to a string, or+use the 'toJsonString' utility function. The current implementation of+'show' outputs ASCII-only strings, using "\uXXXX" escape sequences for+unicode characters.  Note that the initial underscores have been+stripped from the field names.  This is the default behavior, but we+could override it by adding an instance to the TranslateField class:++    instance TranslateField TestRecord1 where+        translateField _dummy x = x+    instance TranslateField TestRecord2 where+        translateField _dummy x = x++Now if we call 'toJson', the underscores will not be removed:++    {"_a":"foo","_b":{"_c":5,"_d":"bar"}++The 'fromJson' function is used to deserialize a JsonData object.+Usually, it is easier to use 'fromJsonString', which parses a string+to a JsonData object and then passes the result to 'fromJson'. The+following expression will evaluate to Just the same TestRecord1+structure that we passed to 'toJson' earlier:++    fromJsonString (undefined :: TestRecord1) "{\"_a\":\"foo\",\"_b\":{\"_c\":5,\"_d\":\"bar\"}}"+    --> Right (TestRecord1 { _a="foo", _b=TestRecord2 { _c=5, _d="bar"}})++The first parameter of 'fromJsonString' (and 'fromJson') is a dummy+value specifying the type of the record which is being deserialized.+Note that the preceding instance of TranslateField is in effect here+(the JSON object keys begin with underscores).++The 'fromJsonString' function assumes that the string it is passed is+a true unicode string. For this reason, if you have obtained your JSON+String using the standard Haskell IO libraries, you may not get the+correct behavior with unicode strings (since your String will be a+sequence of bytes rather than code points). It is usually better to+get the raw JSON data into a ByteString and then use+'fromJsonByteString', which automatically detects and decodes unicode+strings.++The JsonData type has the following definition:++    data JsonData = JDString String                          |+                    JDNumber Double                          |+                    JDArray [JsonData]                       |+                    JDBool Bool                              |+                    JDObject (Data.Map.Map String JsonData)++You can implement custom serialization and deserialization behavior+by adding instances to the ToJson and FromJson classes respectively.+Suppose that we have the following enum type:++    data Direction = Forward | Back | Left | Right deriving Show+    $(derive[''Direction])++As will be explained shortly, the default serialization behavior is+for the values of this enum to be converted to empty JSON lists, which+is probably not what you want. In order to convert them to and from+the appropriate strings, the following instance definitions can be+added:++    instance ToJson Direction where+        toJson North   = JDString "north"+        toJson South   = JDString "south"+        toJson East    = JDString "east"+        toJson West    = JDString "west"+    instance FromJson Direction where+        fromJson _dummy (JDString "north")  = Right North+        fromJson _dummy (JDString "south")  = Right South+        fromJson _dummy (JDString "east")   = Right East+        fromJson _dummy (JDString "west")   = Right West+        fromJson _dummy _                   = Left "Deserialization error for 'Direction'"++In fact, RJson provides 'enumToJson' and 'enumFromJson' functions+which automate the definition of instances of this sort. The preceding+instance definitions could equivalently be written as follows:++    instance ToJson Direction where+        toJson = enumToJson firstCharToLower+    instance FromJson Direction where+        fromJson = enumFromJson firstCharToUpper++The first arguments to 'enumToJson' and 'enumFromJson' are+(String->String) functions used for converting Haskell enum+constructor names to JSON strings and vice versa. The functions+'firstCharToUpper' and 'firstCharToLower' are provided by RJson.++Default serialization behavior is as follows:++    Haskell primitive types  <-->  Corresponding JSON type+    Haskell records          <-->  JSON objects+    Haskell tupels           <-->  Heterogenous JSON arrays+    Haskell algebraic types  <-->  JSON array of arguments given to+                                   constructor. First constructor+                                   always used when deserializing.+                                   (Not a very useful default.)++Both ToJson and FromJson have some other methods which can be used to+customize serialization behavior (check the Haddock+documentation). For example, you can specify default field values for+JSON objects.  Note that there is no default implementation of the+'toJson' or 'fromJson' methods, so if you are overriding other methods+in an instance declaration of ToJson or FromJson, you can set 'toJson'+and 'fromJson' to 'genericToJson' and 'genericFromJson' respectively+in order to get the default behavior.++The 'Union' type can be used to implement a kind of crude inheritance+for Haskell record types.  The type has a single binary constructor+('Union'). Unions are serialized by serializing each of the arguments+to the constructor, then merging the resulting JSON objects into a+single object.  If any of the arguments of the constructor does not+serialize to a JSON object then a runtime error will occur.  To create+unions of more than two records, just use `Union` as an infix+constructor.  Type synonyms are defined for complex unions of this+kind (Union3 a b c, Union4 a b c d, etc. etc.)++WARNING: Record types with strict constructors will lead to runtime+errors when using 'fromJson' ('toJson' will still work fine). This is+because it seems to be necessary to temporarily create records with+dummy field values. If the fields are strict, these dummy values get+evaluated, leading to an exception being raised.
RJson.cabal view
@@ -1,5 +1,5 @@ Name:          RJson-Version:       0.3.2+Version:       0.3.3 Cabal-Version: >= 1.2 License:       BSD3 License-File:  LICENSE@@ -8,8 +8,11 @@ Category:      Text Synopsis:      A reflective JSON serializer/parser. Stability:     experimental+Extra-Source-Files:   README+Build-Type:    Simple  Description:+  See included README for some examples.   This package uses the Scrap Your Boilerplate With Class approach   to generics to implement a reflective Json serializer and deserializer.   Nested record types can be automatically converted to corresponding
Text/RJson.hs view
@@ -6,7 +6,6 @@     -XUndecidableInstances     -XTemplateHaskell     -cpp #-}- module Text.RJson (TranslateField,                    TranslateFieldD,                    translateField,@@ -25,6 +24,7 @@                    objectDefaults,                    parseJsonString,                    parseJsonByteString,+                   fromJson,                    fromJsonString,                    fromJsonByteString,                    genericFromJson,@@ -194,9 +194,7 @@ toJsonProxy :: Proxy ToJsonD toJsonProxy = error "'toJsonProxy' value should never be evaluated!" --- Again, note inclusion of translateField from TranslateField,--- and TranslateField qualification on variable 't' (in addition--- to the expected ToJson qualification).+-- Again, note inclusion of translateField from TranslateField. instance ToJson t => Sat (ToJsonD t) where     dict = ToJsonD { toJsonD          = toJson,                      excludeD         = exclude,@@ -301,7 +299,7 @@     toJson = genericToJson  -- Instances for tuples up to n=7 (this limit it is set by the non-existence of Typeable8).--- Tuples are converted to (hetrogenous) JSON lists.+-- Tuples are converted to (heterogenous) JSON lists. #define I(x) ToJson x, Typeable x instance (I(a), I(b)) => ToJson (a, b) where     toJson (a,b) = JDArray [toJson a, toJson b]@@ -345,6 +343,7 @@ fromJsonProxy :: Proxy FromJsonD fromJsonProxy = error "'fromJsonProxy' should never be evaluated!" +-- Note inclusion of translateField from TranslateField. instance FromJson t => Sat (FromJsonD t) where     dict = FromJsonD { fromJsonD = fromJson,                        objectDefaultsD = objectDefaults,@@ -399,7 +398,7 @@         in           if isRight r1 && isRight r2              then Right $ Union (fromRight r1) (fromRight r2)-             else Left "Bad fromJson conversion: error constructing subpart of union"+             else Left "Bad fromJson conversion: error constructing subpart of union (did not serialize to object)"     fromJson _ _ = Left "Bad fromJson conversion: attempt to convert non-object to Union"  tuperror :: Int -> Either String a@@ -484,18 +483,18 @@  -- TODO: Again, uninformative name. -- TODO: Some code duplication here.-m2 :: (Data FromJsonD a) => M.Map String JsonData -> a -> ErrorWithState String (M.Map String JsonData, [String]) a-m2 defaults dummy = do+m2 :: (Data FromJsonD a, TranslateField a) => M.Map String JsonData -> (String -> String) -> a -> ErrorWithState String (M.Map String JsonData, [String]) a+m2 defaults transFunc dummy = do   (m, sl) <- lift get   (case sl of      []     -> throwError "Bad fromJson conversion: Not enough fields in JSON object to satisfy constructor"      (f:fs) -> do        lift $ put (m, fs)-       let stripped = translateFieldD'' dict dummy f+       let stripped = transFunc f        (case M.lookup stripped m of           Nothing ->             case M.lookup stripped defaults of-              Nothing -> throwError $  "Bad fromJson conversion: Required field not present in JSON object: "+              Nothing -> throwError $  "Bad fromJson conversion: Required field not present in JSON object: " ++ stripped               Just v  ->                 case fromJsonD dict dummy v of                   Left e  -> throwError e@@ -505,7 +504,7 @@               Left e  -> throwError e               Right x -> return x)) -genericFromJson :: (Data FromJsonD a, Data TranslateFieldD a) => a -> JsonData -> Either String a+genericFromJson :: (Data FromJsonD a, FromJson a, TranslateField a) => a -> JsonData -> Either String a genericFromJson dummy (JDArray l) =     case datarep (dataTypeOf fromJsonProxy dummy) of       AlgRep (c:_) ->@@ -520,7 +519,7 @@             -- Can't use fromConstrM because we need to get dummy values of the             -- appropriate type for each argument of the constructor. This is unfortunate,             -- since it means that we get runtime errors for records with strict fields.-            fs -> evalState (runErrorT (gmapM fromJsonProxy (m2 (objectDefaultsD dict dummy)) (fromConstr fromJsonProxy c))) (m, fs)+            fs -> evalState (runErrorT (gmapM fromJsonProxy (m2 (objectDefaultsD dict dummy) (translateFieldD'' dict dummy)) (fromConstr fromJsonProxy c))) (m, fs)       AlgRep _     -> Left "Bad fromJson conversion: Type with no constructors!"       _            -> Left "Bad fromJson conversion: Non-algebraic datatype given to 'genericFromJson'" genericFromJson _ _ = Left "Bad fromJson conversion: Expecting JSON object or array"@@ -540,7 +539,7 @@        else Left "Constructor name not recognized in enumFromJson" enumFromJson _ _ _ = Left "Non-string given to enumFromJson"           -instance (Data FromJsonD t, Data TranslateFieldD t) => FromJson t where+instance (Data FromJsonD t, TranslateField t) => FromJson t where     fromJson = genericFromJson  @@ -576,7 +575,7 @@         -- If we can't figure it out, guess at UTF-8.         else "UTF-8" --- Converts a String of byte values of given encoding to a String of unicode code points.+-- Converts a ByteString to a String of unicode code points. toHaskellString :: EncodingName -> B.ByteString -> String toHaskellString enc source =     stripBOM $ map chr (pairBytes (B.unpack bs))