The basic idea is that by defining three functions--recognize, dissect, and combine--conversion between any case type is possible.recognize is meant to recognize a string of a given case typedissect is meant to break down a string into an array of lower case substrings according to the given case typecombine is meant to take those substrings and join them back up into a string of the given case typeFor example, let's say someone wanted to add a new case type say, hyphenated case my-name-is-jonas.  With my code, all they would have to do is define the three aforementioned methods using the addType method, and every other case type would be able to be converted to and from hyphenated case.While this utility is pretty cool, I wonder if the complexity this solution adds to the code is worth it. Also, please let me know of any other thoughts that you might have.Here is my code, but I also made a jsFiddlevar CaseConverter = (function() {  // Utility functions  /**   *  Can be used by map function to lowercase array of strings   */  function toLowerCase(string) {    return string.toLowerCase();  }  // private  var caseMap = {}; // used to map methods for a case conversion  var listOfCases = []; // a list of the added case types  /**   *  Creates a function to convert a cased to string to the given type.   *  @param {string} methodName the name of the conversion method to add   *  @param {function} recognize Takes a string and returns true   *    if the string is of the given type.   *  @param {function} dissect Takes a string in the given case and   *    returns an array of lowercase substrings of the case blocks.   *    For example: [my, name, is, jonas] for myNameIsJonas   *                                           MyNameIsJonas   *                                           my_name_is_jonas   *  @param {function} combine Takes an array of case blocks and   *    returns them combined into a string of the desired case.   *    substrings.    */  function addType(methodName, recognize, dissect, combine) {    if (caseMap[methodName] !== undefined) {      throw A method  + methodName +  already exists;    } else {      caseMap[methodName] = {        methodName: methodName,        recognize: recognize,        dissect: dissect,        combine: combine      };      listOfCases.push(methodName);    }  }  // add all the types of cases  addType(toCamelCase, function(string) {    return /^([a-z]+)([A-Z][a-z]*)*$/.test(string);  }, function(string) {    return string.split(/(?=[A-Z])/).map(toLowerCase);  }, function(array) {    return array.reduce(function(combined, caseBlock) {      return combined + caseBlock.charAt(caseBlock).toUpperCase() + caseBlock.substring(1);    });  });  addType(toPascalCase, function(string) {    return /^([A-Z][a-z]*)+$/.test(string);  }, function(string) {    return string.split(/(?=[A-Z])/).map(toLowerCase);  }, function(array) {    return array.reduce(function(combined, caseBlock) {      return combined + caseBlock.charAt(caseBlock).toUpperCase() + caseBlock.substring(1);    }, );  });  addType(toUnderscoreCase, function(string) {    return /^[a-z]+(_[a-z]+)*$/.test(string);  }, function(string) {    return string.toLowerCase().split(_);  }, function(array) {    return array.join(_);  });  // someone could add hyphenated case here  // build the module  function createConverterFunction(typeTo) {    return function(string) {      if (typeof string !== string) throw string +  is not a string;      var typeFrom = listOfCases.find(function(type) {        return caseMap[type].recognize(string);      });      if (typeFrom === undefined) throw string +  is not a recognized case type      if (typeFrom === typeTo) {        return string;      } else {        var to = caseMap[typeTo];        var from = caseMap[typeFrom];        return to.combine(from.dissect(string));      }    };  }  var CaseConverter = {};  listOfCases.forEach(function(methodName) {    CaseConverter[methodName] = createConverterFunction(methodName);  })  return CaseConverter})();function test(method, string, expected) {  if (CaseConverter[method](string) !== expected)    throw Failded for:  + method + , string= + string + , expected= + expected + , result= + CaseConverter[method](string);}test(toCamelCase, myNameIsJonas, myNameIsJonas);test(toCamelCase, MyNameIsJonas, myNameIsJonas);test(toCamelCase, my_name_is_jonas, myNameIsJonas);test(toPascalCase, myNameIsJonas, MyNameIsJonas);test(toPascalCase, MyNameIsJonas, MyNameIsJonas);test(toPascalCase, my_name_is_jonas, MyNameIsJonas);test(toUnderscoreCase, myNameIsJonas, my_name_is_jonas);test(toUnderscoreCase, MyNameIsJonas, my_name_is_jonas);test(toUnderscoreCase, my_name_is_jonas, my_name_is_jonas);test(toCamelCase, a, a);test(toPascalCase, a, A);test(toUnderscoreCase, a, a);console.log(all tests passed);This question was inspired by this one
Extendable JavaScript module that converts between camelCase, PascalCase, and underscore_case
javascript;functional programming;factory method;modules
addType(toCamelCase, function(string) {  return /^([a-z]+)([A-Z][a-z]*)*$/.test(string);}, function(string) {  return string.split(/(?=[A-Z])/).map(toLowerCase);}, function(array) {  return array.reduce(function(combined, caseBlock) {    return combined + caseBlock.charAt(caseBlock).toUpperCase() + caseBlock.substring(1);  });});This is neat, pluggable, but looks bulky. Consider using ES6. Arrow functions can give you breathing space while template strings allow you to construct strings without breaking up strings and using + to concatenateAnother is that you use a list of arguments. It's not very clear which function is which. It would probably be better if you passed in an object with config. This way, they're named and order doesn't matter:addType({  name: 'toCamelCase',  recognizer: string => {...},  dissector: string => {...},  combiner: string => {...}});Also, should you want to omit some of the settings, you can set defaults which you can easily merge with Object.assign. In this case, we do a noop by returning the string as is:const defaults = {  name: 'toCamelCase',  recognizer: string => string,  dissector: string => string,  combiner: string => string,}let settings = Object.assign({}, defaults, settings);Also consider exposing addType to your namespace so that it can be utilized by other code. This way, you can plug in on the fly other converters, not just the ones you put in.caseMap[methodName] = {  methodName: methodName,  recognize: recognize,  dissect: dissect,  combine: combine};listOfCases.push(methodName);listOfCases appears to be just a list of methods that exist on caseMap. The problem with this is that there's this two things representing one thing issue. Forgetting to sync them would cause confusion in the code. To avoid this, you can simply use Object.keys(caseMap) to get an array of caseMap's keys, the method names.var typeFrom = listOfCases.find(function(type) {  return caseMap[type].recognize(string);});if (typeFrom === undefined) throw string +  is not a recognized case typeif (typeFrom === typeTo) {  return string;} else {  var to = caseMap[typeTo];  var from = caseMap[typeFrom];  return to.combine(from.dissect(string));}Instead of array.find, consider using array.filter. One advantage is that you are working with arrays through and through. The next set of operations is always assured that it gets an array even if it doesn't get the opportunity to call its callback (i.e.: array.filter won't find anything, array.slice and array.map will both get and return an empty array). After that, you can then check your results.var string = listOfCases.filter(type => caseMap[type].recognize(string))                        .slice(0, 1)                        .map(string => {                          const isSame = typeFrom === typeTo;                          const to     = caseMap[typeTo];                          const from   = caseMap[typeFrom];                          return isSame ? string : to.combine(from.dissect(string))                        })// No result? No parser probably handled it.if(!result[0]) throw new Error(`${string} is not a recognized case type`);return result[0] 