从不一致的JavaScript对象创建PureScript记录

dav*_*ers 11 purescript

假设我的PureScript代码中的用户记录具有以下类型:

{ id        :: Number
, username  :: String
, email     :: Maybe String
, isActive  :: Boolean
}
Run Code Online (Sandbox Code Playgroud)

CommonJS模块源自PureScript代码.导出的用户相关函数将从外部JavaScript代码调用.

在JavaScript代码中,"用户"可以表示为:

var alice = {id: 123, username: 'alice', email: 'alice@example.com', isActive: true};
Run Code Online (Sandbox Code Playgroud)

email可能是null:

var alice = {id: 123, username: 'alice', email: null, isActive: true};
Run Code Online (Sandbox Code Playgroud)

email 可省略:

var alice = {id: 123, username: 'alice', isActive: true};
Run Code Online (Sandbox Code Playgroud)

isActive可以省略,在这种情况下,假设true:

var alice = {id: 123, username: 'alice'};
Run Code Online (Sandbox Code Playgroud)

id 遗憾的是有时候是一个数字字符串:

var alice = {id: '123', username: 'alice'};
Run Code Online (Sandbox Code Playgroud)

上面的五个JavaScript表示是等效的,应该生成等效的PureScript记录.

如何编写一个带有JavaScript对象并返回用户记录的函数?它将使用null /省略的可选字段的默认值,将字符串强制id转换为数字,如果缺少必填字段或值类型错误则抛出.

我能看到的两种方法是在PureScript模块中使用FFI或在外部JavaScript代码中定义转换函数.后者似乎毛茸茸:

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');
  }
  // ...
}
Run Code Online (Sandbox Code Playgroud)

我不确定FFI版本是如何工作的.我还没有使用过效果.

对不起,这个问题不是很清楚.我还没有足够的理解来确切知道我想知道的是什么.

dav*_*ers 8

我已经提出了一个解决方案.我敢肯定,很多可以改进,如改变的类型toUserJson -> Either String User和保存的错误信息.如果您能看到改进此代码的任何方法,请发表评论.:)

除了一些核心模块之外,该解决方案还使用PureScript-Argonaut.

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
         }
Run Code Online (Sandbox Code Playgroud)

更新:我根据Ben Kolera 的要点改进了上面的代码.


gb.*_*gb. 6

你有没有看过purescript-foreignhttps://github.com/purescript/purescript-foreign)?我想这就是你在这里寻找的东西。

  • 一种方法是创建一个像`data SoN = S String | 这样的类型。N Number`,然后使用 `&lt;|&gt;` 运算符为类型 `SoN` 编写一个 `IsForeign` 实例以组合两种选择:`read f = S &lt;$&gt; readString f &lt;|&gt; N &lt;$&gt; readNumber f` (2认同)