Skip to content

English__Data Sources__Dictionary

Ramirez Vargas, José Pablo edited this page Nov 7, 2024 · 2 revisions

Dictionary Data Source

wj-config provides the Dictionary data source that allows the use of flat configuration sources, which can come from varied places. It is actually the base class for the Environment and Single Value data sources.

Definition of a Flat Data Source

A flat data source is a data source with no hierarchy. This means an object that has keys (properties), and that the values of said properties are not objects with keys; values are scalar values like strings, numbers or Booleans. The following is an example of a flat object in JavaScript, and it contrasts it with a non-flat object:

const flatObject = {
    KeyA: 123,
    KeyB: 'A string',
    KeyC: true,
    KeyD: false
};

const nonFlatObject = {
    KeyA: 456,
    KeyB: {
        SubKeyB_KeyA: 'A string'
    },
    KeyC: true
};

The second object in the example does not qualify to be a dictionary data source because the value of property KeyB is itself an object.

Using Flat Objects

If you have been reading this wiki, you know that wj-config produces a hierarchical configuration object, and that its hierarchy is dictated by the data sources. If this is true, and since there is this Dictionary data source, it must be able to contribute to hierarchy, even when itself is not allowed to have a hierarchy.

This data source allows the dictionary keys to specify the hierarchy by using a hierarchy separator. By default, the Dictionary data source expects : to be the hierarchy separator. The following is an example of a dictionary object that can be used with the Dictionary data source:

const myDic = {
    'KeyA': 456,
    'KeyB:SubKeyB_KeyA': 'A string',
    'KeyC': true
};

This example is actually the flattened version of the non-flat object found in the first example. The Dictionary data source will parse the property names (keys) and will split the values at the places where the hierarchy separator is found, which is the colon (:) by default.

How To Use the Dictionary Data Source

As with all other data sources, there is a specialized addDictionary() method in the configuration builder that assists with this job. This function defines three parameters in the form of 2 overloads:

addDictionary<NewT extends Record<string, any>>(
    dictionary: Record<string, ConfigurationValue> | (() => Promise<Record<string, ConfigurationValue>>),
    hierarchySeparator?: string,
    prefix?: string
): IBuilder<MergeResult<T, NewT>>;

addDictionary<NewT extends Record<string, any>>(
    dictionary: Record<string, ConfigurationValue> | (() => Promise<Record<string, ConfigurationValue>>),
    hierarchySeparator?: string,
    predicate?: Predicate<string>
): IBuilder<MergeResult<T, NewT>>;
  1. The dictionary object, or a function that returns the dictionary object.
  2. An optional string that will be used as hierarchy separator, which will be : if nothing is specified.
  3. An optional string to be interpreted as a prefix, or a predicate function.

The first one is the main data parameter. As with all other data sources, it may take the dictionary object directly or it may take a function that returns the dictionary object. The function approach is the recommended approach when doing any form of conditional configuration if obtaining the dictionary object will incur in potentially-wasted CPU cycles.

The second parameter has already been explained extensively; it is used to define hierarchies in a dictioanry (flat) object. Its default value is a colon (:) but any string can be specified here.

The final parameter works as a filter. This filter can be provided in one of two forms: A string, or a predicate function. The former triggers prefix logic, which means that only the properties whose name starts with the given prefix are allowed to contribute to the final configuration object. The latter allows the developer to put whatever logic is required to determine which properties will end up contributing to the final configuration object.

The following code snippets show the two filtering mechanisms:

const config = await wjConfig()
    .addDictionary(myDic, ':', 'CNFG_')
    ...
    .build();
const config = await wjConfig()
    .addDictionary(myDic, ':', n => n.includes('TopSecret'))
    ...
    .build();

The first code snippet tells the dictionary data source to only use the properties whose name start with the prefix CNFG_; the second code snippet provides a function that receives the property name and returns true (meaning include the property in the final configuration object) if the property name contains the string TopSecret.

The first one is the most common one, but the predicate function allows the developer to run arbitrary checks and is therefore the ultimate filtering tool.

Data Requirements

The following is the list of requirements when using dictionary data sources:

  1. A hierarchy separator must be provided.
  2. The hierarchy separator must be a string.
  3. The prefix (or predicate function) is optional.
  4. The dictionary object may be null or undefined. This is valid.
  5. When the dictionary object is provided directly, it is immediately validated to determine if it is a flat object, and if not, then an error will be thrown, right there on the spot.
  6. When the dictionary object is not directly provided and instead a function that returns it is provided, the resulting object will not undergo flatness validation. Instead, the internal function that inflates the object will throw an error if it finds a hierarchy (a non-scalar/non-leaf/node property value).

Hopefully the above list is clear enough as to not need futher explanation. If anything is unclear, drop an issue in the project's repository.

Data Type Coercion

This data source is the only out-of-the-box data source that attempts to convert string values to other data types.

Any property value of type string in the dictionary object will undergo the following parsing algorithm:

  1. null and undefined values are left alone.
  2. The strings true and false are converted to their Boolean equivalents.
  3. The string value is tested with a regular expression to determine if it is an integer. It can be in decimal or hexadecimal notation (0x[0-9a-fA-F]+).
  4. The string value is tested with a regular expression to determine if it is a floating-point value. Only decimal notation is allowed.
  5. If none of the above attempts yield fruits, then the original string value is kept as-is.