【问题标题】:Grunt task to assemble JSON files from filenames in JSON从 JSON 中的文件名组装 JSON 文件的 Grunt 任务
【发布时间】:2018-07-26 22:44:18
【问题描述】:

我有一个 JSON 文件,如下所示:

{
    "someRandomStuff": "myRandomStuff",
    "includeNodesFromFiles": {
        "item1": "item1.json",
        "item2": "item2.json",
        "item3": "item3.json"
    }
}

现在我想用每个文件中的 JSON 内容替换 item1、item2 和 item3,所以目标文件如下所示:

{
    "someRandomStuff": "myRandomStuff",
    "includeNodesFromFiles": {
        "item1": {"whatever": "...whatever was in item1.json"},
        "item2": {"whatever": "...whatever was in item2.json"},
        "item3": {"whatever": "...whatever was in item3.json"},
    }
}

或者类似的对于一个数组:

{
    "someRandomStuff": "myRandomStuff",
    "includeNodesFromFiles": [
        "item1.json",
        "item2.json",
        "item3.json"
    ]
}

收件人:

{
    "someRandomStuff": "myRandomStuff",
    "includeNodesFromFiles": [
        {"whatever": "...whatever was in item1.json"},
        {"whatever": "...whatever was in item2.json"},
        {"whatever": "...whatever was in item3.json"}
    ]
}

我怎么能用 Grunt 做到这一点?到目前为止,我还没有找到可以开箱即用的 Grunt 任务。

Grunt 新手,请多多包涵。

【问题讨论】:

    标签: javascript json gruntjs


    【解决方案1】:

    简短回答:这是一个非常自定义的要求,据我所知,没有现有的 grunt 插件可以实现这一点。


    解决办法:

    您需要创建自己的 grunt 插件来处理此类需求。以下步骤描述了如何实现这一点:

    1. 首先创建一个插件文件如下。让我们将文件命名为json-replace.js

      json-replace.js

      /**
       * Custom grunt plugin replaces JSON values (filepatha) with JSON file content.
       */
      module.exports = function(grunt) {
      
        'use strict';
      
        var path = require('path');
      
        /**
         * Custom grunt multi task to replace values in JSON.
         */
        grunt.registerMultiTask('jsonReplace', 'Replace values in JSON', function() {
      
          // Read the configuration values.
          var src = this.data.src;
          var dest = this.data.dest;
          var keyName = this.data.key;
      
          var baseDir = path.dirname(src);
      
          // Default options
          var opts = this.options({
            indent: 2
          });
      
          /**
           * Determines whether the passed value is an Array.
           * @param {*} value - A reference to the value to check.
           * @returns {Boolean} - true if the value is an Array, otherwise false.
           */
          function isArray(value) {
            return Array.isArray(value);
          }
      
          /**
           * Determines whether the passed value is an Object.
           * @param {*} value - A reference to the value to check.
           * @returns {Boolean} - true if the value is an Object, otherwise false.
           */
          function isObject(value) {
            return Object.prototype.toString.call(value) === '[object Object]';
          }
      
          /**
           * Reads a file's contents, parsing the data as JSON.
           * @param {String} srcPath - The filepath to the JSON file to parse.
           * @returns {Object}- The parsed JSON data.
           */
          function readJson(srcPath) {
            return grunt.file.readJSON(srcPath);
          }
      
          /**
           * Writes JSON data to a file.
           * @param {String} destPath - A filepath for where to save the file.
           * @param {Object|Array} data - Value to covert to JSON and saved to file.
           * @param {Number} [indent=2] - The no. of spaces to indent the JSON.
           */
          function writeJson(destPath, data, indent) {
            indent = (typeof indent !== 'undefined') ? indent : 2;
            grunt.file.write(destPath, JSON.stringify(data, null, indent));
            grunt.log.writeln('Saved \x1b[96m1\x1b[0m file');
          }
      
          /**
           * Checks whether a file exists and logs any missing files to console.
           * @param {String} filePath - The filepath to check for its existence.
           * @returns {Boolean} - true if the filepath exists, otherwise false.
           */
          function fileExists(filePath) {
            if (!grunt.file.exists(filePath)) {
              grunt.fail.warn('Unable to read \"' + filePath + '\"');
              return false;
            }
            return true;
          }
      
          /**
           * Checks whether type of value is a string and logs an error if not.
           * @param {*} value - The value to check
           * @returns {Boolean} - true if type of value is 'string', otherwise false.
           */
          function isString(value) {
            if (typeof value !== 'string') {
              grunt.fail.warn('Value type must be a string: found \"' + value + '\"');
              return false;
            }
            return true;
          }
      
          /**
           * Processes each Array item for a given key.
           * @param {Object} data - The parsed JSON data to process.
           * @param {String} keyName - Name of key whose Array values to process.
           * @param {String} baseDir - Base directory path of the source json file.
           */
          function processArrayItems(data, keyName, baseDir) {
            var replacement = [];
      
            data[keyName].forEach(function(item) {
              var fullPath = path.join(baseDir, item);
      
              if (isString(item) && fileExists(fullPath)) {
                replacement.push(readJson(fullPath));
              }
            });
            data[keyName] = replacement;
            writeJson(dest, data, opts.indent);
          }
      
          /**
           * Processes an Objects key/value pair for a given Object.
           * @param {Object} data - The parsed JSON data to process.
           * @param {String} keyName - Name of key whose property values to process.
           * @param {String} baseDir - Base directory path of the source json file.
           */
          function processObjectValues(data, keyName, baseDir) {
            var replacement = {};
      
            Object.keys(data[keyName]).forEach(function(key) {
              var accessor = data[keyName][key];
              var fullPath = path.join(baseDir, accessor);
      
              if (isString(accessor) && fileExists(fullPath)) {
                replacement[key] = readJson(fullPath);
              }
            });
      
            data[keyName] = replacement;
            writeJson(dest, data, opts.indent);
          }
      
          // Read the source JSON file
          var srcData = readJson(src);
      
          // Check if the `key` provided exists in source JSON.
          if (!srcData[keyName]) {
            grunt.fail.warn('Missing given key "' + keyName + '" in ' + src);
          }
      
          // Invoke the appropriate processing for key value.
          if (isArray(srcData[keyName])) {
            processArrayItems(srcData, keyName, baseDir);
          } else if (isObject(srcData[keyName])) {
            processObjectValues(srcData, keyName, baseDir);
          } else {
            grunt.fail.warn('Value for "' + keyName + '" must be object or array');
          }
      
        });
      };
      
    2. json-replace.js 保存在一个名为custom-grunt-tasks 的文件夹中,该文件夹位于您的项目根目录中(即与Gruntfile.jspackage.json 处于同一级别)。例如:

      .
      ├── Gruntfile.js
      ├── custom-grunt-tasks    <---
      │   └── json-replace.js   <---
      ├── node_modules
      │   └── ...
      ├── package.json
      └── ...
      
    3. 将以下任务添加到您的Gruntfile.js

      Gruntfile.js

      module.exports = function(grunt) {
      
        grunt.loadTasks('custom-grunt-tasks');
      
        grunt.initConfig({
          jsonReplace: {    // <-- Task
            targetA: {      // <-- Target
              src: 'path/to/source.json',
              dest: 'path/to/output/file.json',
              key: 'includeNodesFromFiles'
            }
          }
          // ...
        });
      
        grunt.registerTask('default', ['jsonReplace']);
      }
      

      注意事项:(关于上面Gruntfile.js的配置)

      • 读取grunt.loadTasks('custom-grunt-tasks'); 的行从名为custom-grunt-tasks 的目录中加载自定义插件(即json-replace.js)。

      • 一个名为jsonReplace 的任务被添加到grunt.initConfig({...}),其中包含一个任意命名为targetATarget

      • src 属性的值应替换为指向您的源 .json 文件的有效文件路径。

      • dest 属性的值应替换为新的.json 文件应保存到的文件路径。

      • key 的值应替换为有效的键名。提供的键的名称应该是包含 .json 文件路径的对象或数组的键(例如,includeNodesFromFiles,如您的两个示例中所示)


    其他信息:

    1. json-replace.jsmulti-taskable,这基本上意味着您可以根据需要在jsonReplace 任务中配置多个目标。例如:

      // ...
      jsonReplace: {    // <-- Task
        targetA: {      // <-- Target
          src: 'path/to/source.json',
          dest: 'path/to/output/file.json',
          key: 'includeNodesFromFiles'
        },
        targetB: {      // <-- Another Target
          src: 'path/to/another/source.json',
          dest: 'path/to/output/another/file.json',
          key: 'anotherKeyName'
        }
      }
      // ...
      

      如果您想处理多个.json 文件,多个目标可能会很有用。

    2. json-replace.js 期望 key(例如 includeNodesFromFiles)的值是:

      • 具有嵌套键/值对的对象(根据您的第一个示例),其中每个嵌套键都有一个现有 .json 文件的文件路径值。
      • 或者,一个数组(根据您的第二个示例),其中数组的每个项目都是现有 .json 文件的文件路径值。

      • 注意:如果给定的 JSON key 的结构与上述两个结构中的任何一个匹配,则会将错误记录到控制台。

    3. json-replace.js 默认使用两个空格缩进生成的.json 文件的内容。但是,如果要更改此设置,可以使用 indent 选项。例如,以下任务配置会将生成的 .json 文件缩进四个空格:

      // ...
      jsonReplace: {
        options: {
          indent: 4  // <-- Custom indent option
        },
        targetA: {
          src: 'path/to/source.json',
          dest: 'path/to/output/file.json',
          key: 'includeNodesFromFiles'
        }
      }
      // ... 
      

    重要提示: 在源 .json 文件中定义文件路径值时(例如 item1.jsonitem2.json 等),此解决方案希望它们与源 @987654363 相关@ 文件本身。

    【讨论】:

      【解决方案2】:

      这是一个非常简单的任务,只需加载文件并更改对象中的值(所有文件路径都相对于gruntfile.js):

          grunt.registerTask('mytask', 'My super task', () => {
              // file with json with file paths
              //{"someRandomStuff":"myRandomStuff","includeNodesFromFiles":{"item1":"item1.json","item2":"item2.json","item3":"item3.json"}}
              let main = JSON.parse(fs.readFileSync('myjson.json', 'utf8'));
              Object.keys(main.includeNodesFromFiles).forEach((key) => {
                  main.includeNodesFromFiles[key] = JSON.parse(fs.readFileSync(main.includeNodesFromFiles[key], 'utf8'));
              });
              //... do some stuff
              grunt.log.writeln(JSON.stringify(main)); //{"someRandomStuff":"myRandomStuff","includeNodesFromFiles":{"item1":{},"item2":{},"item3":{}}}
      });
      

      【讨论】:

        【解决方案3】:

        这是我想出的解决方案。如果文件存在,它会递归地用文件替换文件名。还接受要包含的文件的基本 URL:

        grunt.registerTask('buildJson', function(name) {
        
            let config = grunt.config.get('buildJson'); // Get the config options from gruntInitConfig for our task.
            let options = name ? (config[name] || config) : config; // If there isn't a config option that matches, use the object itself
        
            function genJson(path, baseUrl = '') {
        
              function checkIfFile(fPath) {
                try {
                  if (fs.lstatSync(fPath).isFile()) {
                    return true;
                  } else {
                    return false;
                  }
                } catch (e) {
                  if (e.code == 'ENOENT') {
                    return false;
                  } else if (e.code == 'ENAMETOOLONG') {
                    return false;
                  } else {
                    console.error(e);
                  }
                }
              }
        
              var json = JSON.parse(fs.readFileSync(path, {
                encoding: 'utf8'
              }));
        
              return JSON.stringify(json, function(key, value) {
                if (checkIfFile(baseUrl + value)) {
                  return JSON.parse(genJson(baseUrl + value));
                } else {
                  return value;
                }
              }, 2);
            }
        
            let files = grunt.file.expand(options.srcFiles);
        
            for (let file in files) {
        
              let srcPath = files[file];
              // Determine the output path to write our merged json file.
              let outputPath = path.join(options.destDir, path.basename(srcPath));
              let destJson = genJson(srcPath, options.baseUrl);
        
              grunt.file.write(outputPath, destJson); // Write to disk.
        
            }
        
          });
        

        【讨论】:

          猜你喜欢
          • 2013-06-07
          • 2018-03-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-22
          • 2014-11-10
          • 2015-10-29
          • 1970-01-01
          相关资源
          最近更新 更多