【问题标题】:Creating PureScript records from inconsistent JavaScript objects从不一致的 JavaScript 对象创建 PureScript 记录
【发布时间】:2015-03-09 05:34:14
【问题描述】:

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

{ id        :: Number
, username  :: String
, email     :: Maybe String
, isActive  :: Boolean
}

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

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

var alice = {id: 123, username: 'alice', email: 'alice@example.com', isActive: true};

email 可能是null

var alice = {id: 123, username: 'alice', email: null, isActive: true};

email 可以省略:

var alice = {id: 123, username: 'alice', isActive: true};

isActive 可以省略,在这种情况下假定为true

var alice = {id: 123, username: 'alice'};

id 不幸的是有时是一个数字字符串:

var alice = {id: '123', username: 'alice'};

上面的五种 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');
  }
  // ...
}

我不确定 FFI 版本的工作原理。我还没有处理过效果。

很抱歉,这个问题不是很清楚。我还没有足够的了解来确切地知道我想知道什么。

【问题讨论】:

    标签: purescript


    【解决方案1】:

    作为gb。写道,这正是 Foreign 数据类型的构建目的。在我脑海中浮现:

    convert :: Foreign -> F User
    convert f = do
      id <- f ! "id" >>= readNumber
      name <- f ! "name" >>= readString
      email <- (f ! "email" >>= readNull >>= traverse readString) <|> pure Nothing
      isActive <- (f ! "isActive" >>= readBoolean) <|> pure true
      return { id, name, email, isActive }
    

    【讨论】:

      【解决方案2】:

      只是多一点 ffi

      module User where
      
      import Data.Maybe
      import Data.Function
      
      foreign import data UserExternal :: *
      
      type User =
        {
          id :: Number,
          username :: String,
          email :: Maybe String,
          isActive :: Boolean
        }
      
      type MbUser =
        {
          id :: Maybe Number,
          username :: Maybe String,
          email :: Maybe String,
          isActive :: Maybe Boolean 
        }
      
      foreign import toMbUserImpl """
      function toMbUserImpl(nothing, just, user) {
        var result = {},
            properties = ['username', 'email', 'isActive'];
      
        var i, prop;
        for (i = 0; i < properties.length; i++) {
          prop = properties[i];
          if (user.hasOwnProperty(prop)) {
            result[prop] = just(user[prop]);
          } else {
            result[prop] = nothing;
          }
        }
        if (!user.hasOwnProperty('id') || isNaN(parseInt(user.id))) {
          result.id = nothing;
        } else {
          result.id = just(user.id);
        }
        return result;
      }
      """ :: forall a. Fn3 (Maybe a) (a -> Maybe a) UserExternal MbUser
      
      toMbUser :: UserExternal -> MbUser
      toMbUser ext = runFn3 toMbUserImpl Nothing Just ext
      
      defaultId = 0
      defaultName = "anonymous"
      defaultActive = false
      
      userFromMbUser :: MbUser -> User
      userFromMbUser mbUser =
        {
          id: fromMaybe defaultId mbUser.id,
          username: fromMaybe defaultName mbUser.username,
          email: mbUser.email,
          isActive: fromMaybe defaultActive mbUser.isActive
        }
      
      userFromExternal :: UserExternal -> User
      userFromExternal ext = userFromMbUser $ toMbUser ext
      

      【讨论】:

        【解决方案3】:

        我已经制定了一个解决方案。我确信可以改进很多,例如将toUser 的类型更改为Json -&gt; 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
                 }
        

        更新:我根据 Ben Kolera 的 gist 对上面的代码进行了改进。

        【讨论】:

          【解决方案4】:

          你看过purescript-foreignhttps://github.com/purescript/purescript-foreign)吗?我认为这就是您要在这里寻找的。​​p>

          【讨论】:

          • examples/Objects.purs 似乎最接近我想要做的事情。如何修改该示例以允许 x 是数字或数字字符串?
          • 一种方法是创建一个类似data SoN = S String | N Number 的类型,然后使用&lt;|&gt; 运算符为SoN 类型编写一个IsForeign 实例,以结合这两种选择:read f = S &lt;$&gt; readString f &lt;|&gt; N &lt;$&gt; readNumber f跨度>
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-12
          • 1970-01-01
          • 2013-12-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多