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': '[email protected]'
},
'[email protected]': {
'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': '[email protected]'
},
}
... 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': '[email protected]'
}
}
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.