Assume I have User records in my PureScript code with the following type:
{ id :: Number
, username :: String
, email :: Maybe String
, isActive :: Boolean
}
A CommonJS module is derived from the PureScript code. Exported User-related functions will be called from external JavaScript code.
In the JavaScript code, a "user" may be represented as:
var alice = {id: 123, username: 'alice', email: '[email protected]', isActive: true};
email
may be null
:
var alice = {id: 123, username: 'alice', email: null, isActive: true};
email
may be omitted:
var alice = {id: 123, username: 'alice', isActive: true};
isActive
may be omitted, in which case it is assumed true
:
var alice = {id: 123, username: 'alice'};
id
is unfortunately sometimes a numeric string:
var alice = {id: '123', username: 'alice'};
The five JavaScript representations above are equivalent and should produce equivalent PureScript records.
How do I go about writing a function which takes a JavaScript object and returns a User record? It would use the default value for a null/omitted optional field, coerce a string id
to a number, and throw if a required field is missing or if a value is of the wrong type.
The two approaches I can see are to use the FFI in the PureScript module or to define the conversion function in the external JavaScript code. The latter seems hairy:
function convert(user) {
var rec = {};
if (user.email == null) {
rec.email = PS.Data_Maybe.Nothing.value;
} else if (typeof user.email == 'string') {
rec.email = PS.Data_Maybe.Just.create(user.email);
} else {
throw new TypeError('"email" must be a string or null');
}
// ...
}
I'm not sure how the FFI version would work. I haven't yet worked with effects.
I'm sorry that this question is not very clear. I don't yet have enough understanding to know exactly what it is that I want to know.
I've put together a solution. I'm sure much can be improved, such as changing the type of toUser
to Json -> Either String User
and preserving error information. Please leave a comment if you can see any ways this code could be improved. :)
This solution uses PureScript-Argonaut in addition to a few core modules.
module Main
( User()
, toEmail
, toId
, toIsActive
, toUser
, toUsername
) where
import Control.Alt ((<|>))
import Data.Argonaut ((.?), toObject)
import Data.Argonaut.Core (JNumber(), JObject(), Json())
import Data.Either (Either(..), either)
import Data.Maybe (Maybe(..))
import Global (isNaN, readFloat)
type User = { id :: Number
, username :: String
, email :: Maybe String
, isActive :: Boolean
}
hush :: forall a b. Either a b -> Maybe b
hush = either (const Nothing) Just
toId :: JObject -> Maybe Number
toId obj = fromNumber <|> fromString
where
fromNumber = (hush $ obj .? "id")
fromString = (hush $ obj .? "id") >>= \s ->
let id = readFloat s in if isNaN id then Nothing else Just id
toUsername :: JObject -> Maybe String
toUsername obj = hush $ obj .? "username"
toEmail :: JObject -> Maybe String
toEmail obj = hush $ obj .? "email"
toIsActive :: JObject -> Maybe Boolean
toIsActive obj = (hush $ obj .? "isActive") <|> Just true
toUser :: Json -> Maybe User
toUser json = do
obj <- toObject json
id <- toId obj
username <- toUsername obj
isActive <- toIsActive obj
return { id: id
, username: username
, email: toEmail obj
, isActive: isActive
}
Update: I've made improvements to the code above based on a gist from Ben Kolera.
Have you had a look at purescript-foreign
(https://github.com/purescript/purescript-foreign)? I think that's what you're looking for here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With