【问题标题】:NodeJS RESTful API - How to handle 'undefined' request variables properly?NodeJS RESTful API - 如何正确处理“未定义”请求变量?
【发布时间】:2019-03-09 09:58:51
【问题描述】:

我正在使用 NodeJS 和 Express 开发一个 RESTful API。
我注意到传入的请求有时缺少一些预期的变量,这会导致程序崩溃,说它无法将变量的值设置为 'undefined' 值 - 因为请求没有到达值。
示例:
应用程序需要 variableY,但正在发送 variableX:

 formData: { variableX: 'valueX' }

程序期望接收变量Y,代码如下:

const checkVariables = Joi.validate({ 
    variableY: req.body.variableY,
}, schema);

应用程序崩溃并出现以下错误:

TypeError: Cannot read property 'variableY' of undefined

我想了一些方法来处理这个问题,包括在应用程序启动时声明变量并一起使用它们,使用try-catch
另一种方法是使用if-elseif-chainingcase-switch,但正如你所理解的,我当然正在寻找实现这一目标的最干净的方法。
有什么想法吗?

谢谢。

** 编辑 **
仅使用对象取得进展并设法实现结果。一旦试图到达它的任何内部字段,无论如何都会抛出错误,例如:
if(req.body.variableY == undefined){console.log('The expected variable is undefined');} //true

当验证处理“未定义”对象内的字段时:
if(req.body.variableY.dataId == undefined){console.log('The expected variable is undefined');} //crashes
再次抛出以下错误:
TypeError: Cannot read property 'variableX' of undefined

在做了更多的挖掘之后,找到了这个 Stackoverflow 线程:
How to check if object property exists with a variable holding the property name?
尝试使用 hasOwnProperty,但抛出相同类型的错误:
TypeError: Cannot read property 'hasOwnProperty' of undefined

尝试使用try-catch 包装变量声明,仍然没有用:

try{
    var variableX = req.body.variableX
    var variableXDataId = req.body.variableX.dataId
}
catch(e){
    res.status(400).send('Wrong request error: Please check your request variables and try again');
}

因为这是一个非常基本的验证,应该由大多数 RESTful API 解决(验证您在请求中获得了预期的传入变量,因此程序不会因出现无法处理的错误而崩溃 - 什么此类问题(预期/意外请求验证)的常见解决方案是什么?

谢谢。

【问题讨论】:

    标签: node.js express request undefined req


    【解决方案1】:

    解决方案是设置一个默认的空对象来替换父级的 undefined:

    // checking for body.variableX.variableZ with object destructuring ES6
    const {body = {}} = request;
    const {variableX = {}, variableY} = body;
    const {variableZ} = variableX.variableZ;
    
    // or prior ES6
    var body = request.body || {};
    var variableX = body.variableX || {};
    var variableY = variableX.variableY;
    
    // or in a statement
    var variableY = request.body && request.body.variableX ? request.body.variableX.variableY : undefined;
    

    基于此,您可以创建自己的函数,例如 getValue(request, 'body.variableX.variableY'),如果任何父级或最终值未定义,则返回 null:

    // asumes the value in the path is either object or undefined
    function getValue(rootObj, path = '') {
        const parts = key.split('.');
        let value = rootObj || {};
        let part;
        while ((part = parts.shift()) && value !== null) {
            value = value[part] || null;
        }
        return value;
    };
    

    【讨论】:

      【解决方案2】:

      您可以使用快速验证器https://www.npmjs.com/package/express-validator 验证传入的请求。然后将其添加到您的控制器,其中 a、b、c、d 是您要验证的参数

      const nonEmptyFields = ['a', 'b', 'c', 'd'];
      
        nonEmptyFields.forEach(field => req.assert(field, `${field} cannot be blank`).notEmpty());
      
        const errors = req.validationErrors();
      
        if (errors) {
          return res.status(400).send(errors);
        }

      要验证字段内的字段,您可以尝试这样做

      typeof(req.body && req.body.name !== undefined)
      

      【讨论】:

      • 谢谢linux。那么字段中的字段呢?例如 req.body.variableX.dataId?
      【解决方案3】:

      您可以采取另一种方法,在到达checkVariables之前检查req.body

      let body = req.body;
      
      // data - your req.body
      // requiredKeys - is an array of strings , [ key1, key2 ... keyN]  | string[]
      
           const setKeys = ( data, requiredKeys )=>{
      
               if( !typeof requiredKeys.length ){
                  requiredKeys = [];
               }
      
               if(requiredKeys.length) requiredKeys.forEach( k =>{
      
                   k = k.replace(/\+/g,'/');
      
                   let keysList = [];
      
                   if( /\/+/g.test(k)){
                     keysList = k.split('/');
                   }else{
                    keysList = [k];
                   }
      
                   let [firstKey, ...rest] = keysList;
      
                   if( typeof data[firstKey] === 'undefined' ){
                     data[firstKey] = {};
                   }
      
                   if( rest.length ){
      
                      data[firstKey] = setKeys(data[firstKey], [rest.join('/')] );
      
                   }
      
               })
      
               return data;
      
            }
      
      let checkedData= setKeys(body, ['variableT','variableP/noname/emptyObj','custom/object/does/not/exist/but/it/will/be/created/here']);
      
      const checkVariables = Joi.validate(checkedData, schema);
      

      更新

      您将在下面找到一个工作示例,说明在 /(比如说 /usersStatus/:id )请求期间事情应该如何工作:

      const express = require('express')
      const app = express()
      const port = 3000
      
      const setKeys = (data, requiredKeys) => {
      
        if (!typeof requiredKeys.length) {
          requiredKeys = [];
        }
      
        if (requiredKeys.length) requiredKeys.forEach(k => {
      
          k = k.replace(/\+/g, '/');
      
          let keysList = [];
      
          if (/\/+/g.test(k)) {
            keysList = k.split('/');
          } else {
            keysList = [k];
          }
      
          let [firstKey, ...rest] = keysList;
      
          if (typeof data[firstKey] === 'undefined') {
            data[firstKey] = {};
          }
      
          if (rest.length) {
      
            data[firstKey] = setKeys(data[firstKey], [rest.join('/')]);
      
          }
      
        })
      
        return data;
      
      }
      
      /**
       * Mock some data
       */
      const getUserData = (req, res, next) => {
      
        if (typeof req.body === 'undefined') {
          req.body = {};
        }
      
        req.body = {
          variableY: {
            someName: 23
          },
          variableZ: {
            name: 3,
            type: {
              id: 5,
              typeName: 'something',
              tags: ['a', 'b', 'c']
            }
          }
        };
      
        console.log('Middleware 1 getUserData');
      
        next();
      
      }
      
      /**
       * 1. Setup our middleware for checking keys
       *    "requiredKeys" is an array of strings
       */
      const middlewareSetKeys = (requiredKeys, wrappedMiddleware) => {
      
        return (req, res, next) => {
      
          console.log('Middleware 2 middlewareSetKeys');
      
          if (typeof req.body === "undefined") {
            console.log('Leaving Middleware 2 since we don\'t have req.body');
            next();
          }
      
          /**
           *  Update "req.body" with keys that we want to have available
           *  in our next middleware
           */
          req.body = setKeys(req.body, requiredKeys);
      
          if (typeof wrappedMiddleware === 'function') {
      
            return wrappedMiddleware.call(this, req, res, next);
      
          } else {
            next();
          }
      
        }
      
      }
      
      /**
       *  2. Let's assume a "user status" situation
       *      2.1.  We need userInfo from database
       *      2.2.  Some info won't be retrieved, unless the user accesed some parts of the website to trigger some mechanisms that allows those fields to be exposed, therefore the lack of keys
       *      2.3.  But we know those keys/objects, and we still want to be present so our code won't crash.
       */
      
      // lets call our getUserData
      app.get(
        '/', // this path is for some userInfo
        getUserData, // this returns userInfo and appends it to `req.data`
        middlewareSetKeys([
          'userActivity/daily/jobs', // these won't exist in getUserData because the user is lazy and he didn't apply for any JOBS
          'userStatus/active/two-weeks-ago', // these won't exist in getUserData because the user joined two days ago. BUT WE STILL NEED IT coz reazons.
        ]), // We set our desired-later-to-use keys
        (req, res, next) => {
      
          /**
           * 3. Now our req.body will have our keys 
           *    even if they didn't exist in the getUserData middleware
           */
          console.log('Middleware 3 Your middleware');
      
          console.log(req.body);
          res.setHeader('Content-Type', 'application/json');
          res.send(JSON.stringify(req.body, null, 2))
      
        })
      
      app.listen(port, () => console.log(`Example app listening on port ${port}!`))

      【讨论】:

      • 它仅适用于'top'对象,一旦您尝试验证未定义对象内的字段,再次引发以下错误:TypeError: Cannot read property 'variableY' of undefined 找到此帖子后:[@ 987654321@ 尝试使用 hasOwnProperty,但抛出了相同类型的错误:TypeError: Cannot read property 'hasOwnProperty' of undefined 无论如何要克服这个问题?这是验证传入请求的基础。
      • 我已经通过深度对象扫描更新了我的答案。根据您的编辑,您似乎需要已经有一个必填字段/键的列表并根据该列表检查req.body,您不能只调用任何未定义的键而不让脚本检查它们。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-13
      • 1970-01-01
      • 1970-01-01
      • 2018-05-23
      • 1970-01-01
      相关资源
      最近更新 更多