Enables applications to make effective use of JSON configuration files. Especially useful for backend services or CLI tools written in Dart but can also be used with Flutter.

Features

  • Type-safe processing of JSON configuration files
  • No need for code generation
  • Handles optional and required values
  • Can use defaults if values are missing
  • Supports environment variables (great to inject secrets)

Getting started

Assume a configuration file config.json with the following contents:

{
    "enable_graphql_playground": true,
    "enable_introspection": true,
    "enable_schema_download": true
}

To load such a configuration file in your application, create a class MyConfigurationService that extends ConfigurationService and defines typed properties for every configuration value you want to support:

class MyConfigurationService extends ConfigurationService {
  // ConfigurationValue<bool> defines a boolean value. You can use bool,
  // int, String, List<> as well as your own complex types (more below).
  final enableGraphQLPlayground = ConfigurationValue<bool>(
    // name of the value in the configuration file
    name: "enable_graphql_playground",
    // optionally specify a default that will be used the value doesn't exist in the configuration file
    defaultValue: const Optional(false),
  );

  final enableIntrospection = ConfigurationValue<bool>(
    name: "enable_introspection",
    // Values can be marked as required. This will throw an exception if the value is missing in the configuration file.
    isRequired: true,
  );

  final enableSchemaDownload = ConfigurationValue<bool>(
    name: "enable_schema_download",
    defaultValue: const Optional(false),
  );

  MyConfigurationService() {
    // register all configuration values so that they are picked up by the loader
    register(enableGraphQLPlayground);
    register(enableIntrospection);
    register(enableSchemaDownload);
  }
}

Load the configuration file with loadFromJson():

void main() async {
  final configurationService = MyConfigurationService();
  await configurationService.loadFromJson(filePath: 'config.json');

  print(configurationService.enableIntrospection.value);
}

Usage

Access a value from the configuration file

Assume the following configuration file exists:

{ "test": true }

The following code will read it:

final configurationValue = ConfigurationValue<bool>(name: 'test');

final configurationService = ConfigurationService();
configurationService.register(configurationValue);
await configurationService.loadFromJson(filePath: 'config.json');

print(configurationValue.value); // "true"

Check if a value exists

final configurationValue = ConfigurationValue<bool>(name: 'test');

final configurationService = ConfigurationService();
configurationService.register(configurationValue);
await configurationService.loadFromJson(filePath: 'config.json');

print(configurationValue.hasValue()); // "true" if value exists in config.json

Use defaults

final configurationValue = ConfigurationValue<bool>(name: 'test', defaultValue = Optional(true));

final configurationService = ConfigurationService();
configurationService.register(configurationValue);
await configurationService.loadFromJson(filePath: 'config.json');

print(configurationValue.hasValue()); // "true" even if it doesn't exist in config.json
print(configurationValue.hasValue(ignoreDefaults: true)); // only "true" if it exists in config.json

Mark values as required

final configurationValue = ConfigurationValue<bool>(name: 'test', isRequired: true);

final configurationService = ConfigurationService();
configurationService.register(configurationValue);

// will throw an exception if "test" can't be found in the file
await configurationService.loadFromJson(filePath: 'config.json');

print(configurationValue.value);

Allow overrides from environment variables

Using allowEnvironmentOverrides gives precedence to values found in the environment. Names of environment variables are determined by re-casing the configuration value's name to CONSTANT_CASE.

Examples:

  • verify_token becomes VERIFY_TOKEN
  • auth.verify_token becomes AUTH_VERIFY_TOKEN
final configurationValue = ConfigurationValue<bool>(name: 'test', isRequired: true);

final configurationService = ConfigurationService();
configurationService.register(configurationValue);

await configurationService.loadFromJson(filePath: 'config.json', allowEnvironmentOverrides: true);

print(configurationValue.value); // value that was found in env variable TEST, otherwise value from config file

Using configuration file sections

It is often desirable to partition a configuration file in section for better maintainability. A hierarchy of configuration value can be specified by using the dot notation.

Assume the following configuration file:

{
  "auth": {
    "verify_token": true
  }
}

The value can be accessed by specifying auth.verify_token as its name.

Using complex types

configuration_service understands int, String and bool natively as well as corresponding List types. To load custom complex types, specify a deserializer when registering the type:

configurationService.register(authEntityIdFallback, deserializers: {
      EntityIdFallbackConfig: EntityIdFallbackConfig.fromJson,
    });

This allows loading arbitrarily structured data from the configuration file. Make sure that the fromJson method uses correct json keys to access the values from the configuration data.

Libraries

configuration_service
Enables applications to make effective use of JSON configuration files. Especially useful for backend services or CLI tools written in Dart but can also be used with Flutter.