使用Lodash从JSON对象获取特定于环境的配置

时间:2016-05-08 12:17:30

标签: javascript node.js lodash env

鉴于我有以下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"
                }
            }
        }
    }
};

使用Node& Lodash,根据connection设置的内容,是否有可能获得config.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。

2 个答案:

答案 0 :(得分:2)

没有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太复杂了,每个环境的配置要好得多。

答案 1 :(得分:1)

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

你的JSON是复杂的,是的,但它也可以更小,更易读,没有所有重复,每个环境都有相同的属性集,可能会也可能不会发生变化,并且会不必要地重复。

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

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

示例,预处理的JSON配置片段:

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 '和navigator.language of' de '):

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());
}

属性名称后缀可以在'@'后面加上任意数量的后缀,由'&amp;'分隔(&符号),并且,有两个属性具有不同但优选的后缀,将按它们传递给函数的顺序排列。包含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 + results:

将它输入到prefer()函数中

for sqlite,test env:

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'}       }

<强>注意事项 @ in属性名称的使用不是标准的,并且在点符号中是无效的,但到目前为止还没有破坏我们测试过的任何浏览器。这可以防止开发人员期望他们可以参考你的pre处理后缀属性。开发人员必须要注意,并且有点不同寻常,并将您的属性称为字符串(obj ['key @ suf'])来执行此操作,顺便说一下,这是此功能可能的原因。< / p>

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