【问题标题】:Get environment specific configuration from a JSON object using Lodash使用 Lodash 从 JSON 对象获取特定于环境的配置
【发布时间】:2016-09-03 03:25:33
【问题描述】:

鉴于我有以下 JSON 对象,

dbConfig = {
    "db": "default",
    "default": {
        "defaultDB": "sqlite",
        "init": "init",
        "migrations": {
            "directory": "migrations",
            "tableName": "migrations"
        },
        "pool": {
            "min": "2",
            "max": "10"
        },
        "sqlite": {
            "client": "sqlite3",
            "connection": {
                "filename": "data/default/sqlitedb/test.db"
            }
        },
        "oracle": {
            "client": "oracledb",
            "config": {
                "development": {
                    "user": "test",
                    "pass": "test",
                    "db": "test"
                },
                "production": {
                    "user": "test",
                    "pass": "test",
                    "db": "test"
                },
                "test": {
                    "user": "test",
                    "pass": "test",
                    "db": "test"
                }
            }
        }
    }
};

使用节点和Lodash,是否有可能获得connectionconfig.,具体取决于dbConfig.default[dbConfig.default.defaultDB] 的设置。

例如,如果我设置dbConfig.default.defaultDB=oracledbprocess.env.NODE_ENV=development 我希望能够得到dbConfig.default[dbConfig.default.defaultDB].config.development

或者如果我设置dbConfig.default.defaultDB=sqlite 只是为了得到dbConfig.default[dbConfig.default.defaultDB].connection

换句话说,如果数据库具有特定于环境的配置,那么它将在"config": {} 中,如果不在"connection": {}

不一定是 Lodash。它也可以是纯 javascript。

【问题讨论】:

    标签: javascript node.js lodash env


    【解决方案1】:

    没有 lodash 的解决方案

    var defaultDbName = dbConfig.default[dbConfig.default.defaultDB];
    
    var db;
    if (defaultDb === 'sqllite') {
      db = dbConfig.default[defaultDb].connection;
    } else {
      var env = process.env.NODE_ENV;
      db = dbConfig.default[defaultDb].config[env];
    }
    

    使用 lodash 的解决方案

    这里我使用 lodash get 函数获取对象字段值,如果不存在则使用 null。我也使用模板字符串语法:${val} 来格式化字段路径。

    var defaultDbName = dbConfig.default[dbConfig.default.defaultDB];
    var defaultDbConf = dbConfig.default[defaultDb];
    var env = process.env.NODE_ENV;
    
    var db = defaultDbConf.connection || _.get(defaultDbConf, `config.${env}`);
    

    顺便说一句,您的配置 json 太复杂了,最好根据环境进行配置。

    【讨论】:

    • 太棒了。谢谢
    • 很多配置都这么复杂,只是看起来更糟,因为不必要地重复了非环境特定的属性。也就是说,这并不比每次复制整个 .properties 文件的习惯差环境,然后确保所有属性在新功能进入最终部署环境时保持同步。请参阅下面的零/最少重复机制,该机制向对象属性名称添加可读符号,可以在运行/渲染时或作为预部署工作流程的一部分通过单个函数递归过滤。
    【解决方案2】:

    没有 [dependencies] 的解决方案(最初回答 here,但不是 AngularJS 特定的)

    您的 JSON 很复杂,是的,但它也可以更小,更易读,无需所有重复,其中每个环境具有相同的属性集,这些属性可能会发生变化,也可能不会发生变化,并且会被不必要地复制。

    使用simple algorithm (jsFiddle),您可以动态解析特定属性名称后缀 (property@suffix) 的 JSON 配置,并拥有随环境变化的属性和不变属性的目录,无需人为地构建您的配置,也无需重复,包括深度嵌套的配置对象。

    您还可以混合搭配后缀并结合任意数量的环境或其他任意因素来修饰您的配置对象。

    示例,预处理 JSON 配置的 sn-p:

    var config = {
        'help': {
            'BLURB': 'This pre-production environment is not supported. Contact Development Team with questions.',
            'PHONE': '808-867-5309',
            'EMAIL': 'coder.jen@lostnumber.com'
        },
        'help@www.productionwebsite.com': {
            'BLURB': 'Please contact Customer Service Center',
            'BLURB@fr': 'S\'il vous plaît communiquer avec notre Centre de service à la clientèle',
            'BLURB@de': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
            'PHONE': '1-800-CUS-TOMR',
            'EMAIL': 'customer.service@productionwebsite.com'
        },
    }
    

    ...和后处理(给定 location.hostname='www.productionwebsite.com' 和 'de' 的 navigator.language):

    prefer(config,['www.productionwebsite.com','de']); // prefer(obj,string|Array<string>)
    
    JSON.stringify(config); // {
        'help': {
            'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
            'PHONE': '1-800-CUS-TOMR',
            'EMAIL': 'customer.service@productionwebsite.com'
        }
    }
    

    显然,您可以在渲染时使用 location.hostname 和 window.navigator.language 提取这些值。处理 JSON 本身的算法并不是非常复杂(但出于某种原因,您可能仍然对整个框架感到更舒服,而不是单个函数):

    function prefer(obj,suf) {
        function pr(o,s) {
            for (var p in o) {
                if (!o.hasOwnProperty(p) || !p.split('@')[1] || p.split('@@')[1] ) continue; // ignore: proto-prop OR not-suffixed OR temp prop score
                var b = p.split('@')[0]; // base prop name
                if(!!!o['@@'+b]) o['@@'+b] = 0; // +score placeholder
                var ps = p.split('@')[1].split('&'); // array of property suffixes
                var sc = 0; var v = 0; // reset (running)score and value
                while(ps.length) {
                    // suffix value: index(of found suffix in prefs)^10
                    v = Math.floor(Math.pow(10,s.indexOf(ps.pop())));
                    if(!v) { sc = 0; break; } // found suf NOT in prefs, zero score (delete later)
                    sc += v;
                }
                if(sc > o['@@'+b]) { o['@@'+b] = sc; o[b] = o[p]; } // hi-score! promote to base prop
                delete o[p];
            }
            for (var p in o) if(p.split('@@')[1]) delete o[p]; // remove scores
            for (var p in o) if(typeof o[p] === 'object') pr(o[p],s); // recurse surviving objs
        }
        if( typeof obj !== 'object' ) return; // validate
        suf = ( (suf || suf === 0 ) && ( suf.length || suf === parseFloat(suf) ) ? suf.toString().split(',') : []); // array|string|number|comma-separated-string -> array-of-strings
        pr(obj,suf.reverse());
    }
    

    属性名称后缀可以在“@”之后有任意数量的后缀,由“&”(和号)分隔,并且如果有两个属性具有不同但首选的后缀,则将按照它们的顺序优先传递给函数。包含 BOTH 首选字符串的后缀将优先于所有其他字符串。在 JSON 中找到的未指定为首选的后缀将被丢弃。

    首选项/区分将自上而下应用于您的对象树,如果更高级别的对象幸存下来,随后将检查它们的首选后缀。

    使用这种方法,您的 JSON(我正在对您的环境之间哪些属性不同而哪些属性没有变化做出一些假设)可能会简化如下:

    dbConfig = {
        "pool": {
            "min": "2",
            "max": "10"
        },
        "init": "init",
        "migrations": {
            "directory": "migrations",
            "tableName": "migrations"
        },
        "db":
            "client": "sqlite",
            "filename": "data/default/sqlitedb/development.db"
            "filename@tst": "data/default/sqlitedb/test.db"
            "filename@prd": "data/default/sqlitedb/production.db"
        },
        "db@oracle": {
            "client": "oracle",
            "user": "devuser",
            "user@tst": "testdbuser",
            "user@prd": "testdbuser",
            "pass": "devpass",
            "pass@tst": "testdbpass",
            "pass@prd": "testdbpass",
            "db": "devdb",
            "db@tst": "testdbschema",
            "db@prd": "testdbschema"
        }
    };
    

    这样您就可以使用这些 args+结果将其输入到 prefer() 函数中:

    对于 sqlite,测试环境:

    prefer(dbConfig,'tst');
    JSON.stringify(dbConfig); // dbConfig: {
        "pool": {
            "min": "2",
            "max": "10"
        },
        "init": "init",
        "migrations": {
            "directory": "migrations",
            "tableName": "migrations"
        },
        "db": {
            "client": "sqlite",
            "filename": "data/default/sqlitedb/test.db"
        }
    };
    

    对于 oracle,默认/开发环境:

    prefer(dbConfig,'oracle'); // oracle, dev(default) env
    JSON.stringify(dbConfig); // dbConfig: {
        "pool": {
            "min": "2",
            "max": "10"
        },
        "init": "init",
        "migrations": {
            "directory": "migrations",
            "tableName": "migrations"
        },
        "db": {
            "client": "oracle",
            "user": "devdbuser",
            "pass": "devdbpass",
            "db": "devdbschema"
        }
    };
    
    prefer(dbConfig,'oracle,prd'); // oracle, production env
    JSON.stringify(dbConfig); // dbConfig: {
        "pool": {
            "min": "2",
            "max": "10"
        },
        "init": "init",
        "migrations": {
            "directory": "migrations",
            "tableName": "migrations"
        },
        "db": {
            "client": "oracle",
            "user": "prddbuser",
            "pass": "prddbpass",
            "db": "prddbschema"
        }
    };
    

    抽象用法和例子:

    var o = { 'a':'apple', 'a@dev':'apple-dev', 'a@fr':'pomme',
              'b':'banana', 'b@fr':'banane', 'b@dev&fr':'banane-dev',
              'c':{ 'o':'c-dot-oh', 'o@fr':'c-point-oh' }, 'c@dev': { 'o':'c-dot-oh-dev', 'o@fr':'c-point-oh-dev' } };
    
    /*1*/ prefer(o,'dev');        // { a:'apple-dev', b:'banana',     c:{o:'c-dot-oh-dev'}   }
    /*2*/ prefer(o,'fr');         // { a:'pomme',     b:'banane',     c:{o:'c-point-oh'}     }
    /*3*/ prefer(o,'dev,fr');     // { a:'apple-dev', b:'banane-dev', c:{o:'c-point-oh-dev'} }
    /*4*/ prefer(o,['fr','dev']); // { a:'pomme',     b:'banane-dev', c:{o:'c-point-oh-dev'} }
    /*5*/ prefer(o);              // { a:'apple',     b:'banana',     c:{o:'c-dot-oh'}       }
    

    注意事项 在属性名称中使用 @ 不是标准的,并且在点表示法中无效,但到目前为止还没有破坏我们在其中测试过的任何浏览器。这样做的好处是它可以防止开发人员期望他们可以参考您的 pre - 处理后的属性。开发人员必须意识到,并且有点不合常规,并将您的属性称为字符串 (obj['key@suf']) 才能做到这一点,顺便说一句,这就是这个函数成为可能的原因。

    如果未来的 JavaScript 引擎拒绝它,替换任何其他可容忍的约定,只要保持一致即可。 该算法尚未针对性能进行分析,也未针对其他潜在问题进行严格测试。 以目前的形式,在启动/加载时使用一次,我们还没有遇到问题。 一如既往,YMMV。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-20
      • 1970-01-01
      • 2017-07-03
      • 1970-01-01
      • 1970-01-01
      • 2019-10-05
      • 2017-11-22
      相关资源
      最近更新 更多