The first task is to write generic code that can serialize a Data instance to JSON. This has probably been done somewhere else, but I need to learn, right? So I took a deep breath and dived into Data.Generics.
I quickly rounded up the issues I would face. Most notably, Strings and lists are accessed without any syntaxic sugar, so to speak: Strings are lists of Char, and lists are made of two fields, the head and the rest. Of course, you say, but from that I need to regenerate String and lists of JSON Value objects.
So, to start, how to recognize Strings to treat them differently than other algrebraic types:
isString :: Data a => a -> Bool
isString a = (typeOf a) == (typeOf "")
There's probably better ways to do that, just tell me (-:
Lists (that are not strings) can be recognized with abstract representations of constructors, which are equals regardless of the actual type of elements in the list
isList :: Data a => a -> Bool
| isString a = False
| otherwise = (typeRepTyCon (typeOf a)) == (typeRepTyCon $ typeOf (::[Int]))
Now, transforming a list of the form (head,rest) to [head1,head2...]
jsonList :: Data a => a -> [JSON.Value]
concat $ gmapQ f l
where f b
| (isList b)= jsonList b
| otherwise = [objToJson b]
For each element (the actual number depends on whether we're the empty list or not) we either reapply the same method, if it's the inner list or we simply transform to JSON
And then the actual method on objects:
objToJson :: Data a => a -> JSON.Value
objToJson o | isString o=JSON.String (fromJust $ ((cast o)::(Maybe String)))
objToJson o | isList o=JSON.Array (jsonList o)
objToJson o | otherwise=
case (constrRep c) of
AlgConstr _-> JSON.Object (Data.Map.fromList(zip (constrFields c) (gmapQ objToJson o)))
StringConstr s -> JSON.String s
FloatConstr f -> JSON.Number f
IntConstr i -> JSON.Number (fromIntegral i)
We first handle Strings, then list, then general objects using constrRep to distinguish between algebraic types that create JSON objects with the proper field names (using constrFields) and other types for JSON primitives.
And that's it for the serialization! The Generics package is not that hard to use but you have to look up examples to figure out how actually use the functions like gmapQ and such...
Now, I have to work on the opposite process: given a type and JSON data, reconstruct the objects... More Haskell fun!