Get environment specific configuration from a JSON object using Lodash

寵の児 提交于 2020-01-25 03:11:32

问题


Given that I have the following JSON object,

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"
                }
            }
        }
    }
};

Using Node & Lodash, is there any possibility of getting either connection or config. depending on what dbConfig.default[dbConfig.default.defaultDB] is set to.

So for instance if i set dbConfig.default.defaultDB=oracledb and process.env.NODE_ENV=development I want to be able to get dbConfig.default[dbConfig.default.defaultDB].config.development

Or if I set dbConfig.default.defaultDB=sqlite just to get dbConfig.default[dbConfig.default.defaultDB].connection

In other words, if the database has environment specific configuration then this will be in "config": {} and if not in "connection": {}

It doesn't have to be Lodash. It can also be plain javascript.


回答1:


Solution without 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];
}

Solution with lodash

Here I'm using lodash get function to get object field value or null if it doesn't exist. Also I'm using template string syntax: ${val} to format field path.

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

Btw, your configuration json is too complex, much better to have configuration per environment.




回答2:


Solution without [dependencies] (originally answered here, but not AngularJS-specific)

Your JSON is complex, yes, but it could also be smaller and more readable without all the duplication, where each environment has the same set of attributes, which may or may not vary, and would be needlessly duplicated.

With a simple algorithm (jsFiddle) you can dynamically parse your JSON configuration for specific property-name suffixes (property@suffix) and have a catalogue of environment-varying properties alongside non-varying properties, without artificially structuring your configuration and without repetition, including deeply-nested configuration objects.

You can also mix-and-match suffixes and combine any number of environmental or other arbitrary factors to groom your configuration object.

Example, snippet of pre-processed JSON config:

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

... and post-processed (given location.hostname='www.productionwebsite.com' and 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'
    }
}

Obviously you can pull those values at render-time with location.hostname and window.navigator.language. The algorithm to process the JSON itself isn't terribly complex (but you may still feel more comfortable with an entire framework for some reason, instead of a single function):

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

The property name suffix can have any number of suffixes after the '@', delimited by '&' (ampersand) and, where there are two properties with different but preferred suffixes, will be preferred in the order in which they are passed to the function. Suffixes that contain BOTH preferred strings will be preferred above all others. Suffixes found in the JSON that are not specified as preferred will be discarded.

Preference/discrimination will be applied top-down on your object tree, and if higher-level objects survive, they will be subsequently inspected for preferred suffixes.

With this approach, your JSON (I'm making some assumptions about which attributes vary between your environments and which do not) might be simplified as follows:

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"
    }
};

So that you could feed this into the prefer() function with these args+results:

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"
    }
};

for oracle, default/development environment:

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"
    }
};

Abstract usage and examples:

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

Caveats Usage of the @ in property name is NOT standard and is invalid in dot-notation, but so far has not broken any browsers we've tested this in. The UPSIDE of this is that it prevents developers from expecting they can refer to your pre-processed, suffixed attributes. A developer would have to be aware of, and a bit unconventional and refer to your attribute as a string (obj['key@suf']) to do that, which, by the way, is the reason this function is possible.

If future JavaScript engines reject it, substitute for any other tolerable convention, just be consistent. This algorithm has not been profiled for performance, or rigorously tested for other potential problems. In its current form, used one-time on startup/load, we have yet to run into problems with. As always, YMMV.



来源:https://stackoverflow.com/questions/37099750/get-environment-specific-configuration-from-a-json-object-using-lodash

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!