easily encode/decode records
cdepillabout opened this issue · 8 comments
Is there a way to easily encode and decode records?
For instance, I have the following type:
newtype Register = Register { email :: String
, password :: String
}
I'd like to use purescript-generics for the encoding and decoding like below:
derive instance genericRegister :: Generic Register
instance encodeJsonRegister :: EncodeJson Register where
encodeJson = gEncodeJson
But when running the following code:
let register = Register { email: "email@email.com", password: "foobar" }
in show $ encodeJson res
I get the following output: {"values":[{"password":"foobar","email":"email@email.com"}],"tag":"Register"}
I was expecting the output to look like this: {"password":"foobar","email":"email@email.com"}
I tried to change the EncodeJson instance like the following:
instance encodeJsonRegister :: EncodeJson Register where
encodeJson (Register reg) = gEncodeJson reg
But that just gave me the following compiler error:
Error found:
Error in value declaration encodeJsonRegister:
Error at /opt/src/src/Main.purs line 44, column 1 - line 46, column 1:
Error in module Main:
No instance found for
Data.Generic.Generic { password :: String
, email :: String
}
The error makes sense, but I was hoping it would be easier to define encodeJson for records. Is there an easy way? I don't want to have to write out something like this for every type:
instance encodeJsonRegister :: EncodeJson Register where
encodeJson (Register reg)
= "email" := reg.email
~> "password" := reg.password
~> jsonEmptyObject
Maybe I've just been spoiled by aeson :-(
Perhaps we could add a special case in gEncodeJSON/gDecodeJSON for ADTs with single constructors and single argument, so that they would be simply encoding/decoding that argument.
How does that sound?
At the moment gEncodeJson/gDecodeJson is just a direct, unambiguous, and very obvious (though a bit too much structured) mapping between GenericSpine and JSON.
To be honest, I didn't really intend it to be used as something "human consumable", the primary target are the cases where you just need an isomorphic serialization/deserialization (like if you want to save something to localstorage).
Making an exception for newtypes would break that property, and gDecodeJSON would become ambiguous.
For example if you have:
newtype Natural = Natural IntYou probably don't want gDecodeJson "-55" :: Either String Natural to return you Right (Natural (-55)).
I guess I'll just write my own version of gEncodeJSON.
It would be nice to have a function similar to deriveJSON that could take options on how to do the encoding/decoding.
Then you would be able to write something like this:
myOpts :: ArgonautCodecOptions
myOpts ...
derive instance genericRegister :: Generic Register
instance encodeJsonRegister :: EncodeJson Register where
encodeJson = encodeJsonWithOptions myOpts
instance decodeJsonRegister :: DecodeJson Register where
decodeJson = decodeJsonWithOptions myOpts
I am currently working on genericEncodeJson which takes options like Haskell's aeson does. I plan to file a pull request later in the evening. Unwrapping newtype like data is currently not supported, but it should be easy to add.
Ok, after further investigation, this modified example from @cdepillabout does not work:
newtype Register = Register RegisterImpl
type RegisterImpl = { email :: String
, password :: String
}
-- instance genericRegisterImpl :: Generic RegisterImpl
instance encodeJsonRegister :: EncodeJson Register where
encodeJson (Register reg) = gEncodeJson reg
Nor does it work, if you add:
instance genericRegisterImpl :: Generic RegisterImpl
or
instance genericRegisterImpl :: Generic Object ( email :: String , password :: String )
this seems to be related to:
I was not aware of this problem, ok I think I have to implement the "unwrapUnaryRecords" option.
Can this be closed? It doesn't serve the use case of a type having all *code-able non-Generic fields however.
Yes, I believe this can be considered fixed with the unwrapUnaryRecords option.
Thanks!