{"id":17279685,"url":"https://github.com/ortham/json-schema-env-config","last_synced_at":"2025-03-26T14:46:10.939Z","repository":{"id":57285176,"uuid":"326220919","full_name":"Ortham/json-schema-env-config","owner":"Ortham","description":"Define config in JSON schema, load it from environment variables.","archived":false,"fork":false,"pushed_at":"2021-08-12T20:47:55.000Z","size":256,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-02T08:03:17.156Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ortham.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-02T16:16:49.000Z","updated_at":"2024-10-28T14:42:35.000Z","dependencies_parsed_at":"2022-09-17T13:01:21.427Z","dependency_job_id":null,"html_url":"https://github.com/Ortham/json-schema-env-config","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ortham%2Fjson-schema-env-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ortham%2Fjson-schema-env-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ortham%2Fjson-schema-env-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ortham%2Fjson-schema-env-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ortham","download_url":"https://codeload.github.com/Ortham/json-schema-env-config/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245675622,"owners_count":20654431,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T09:18:16.833Z","updated_at":"2025-03-26T14:46:10.915Z","avatar_url":"https://github.com/Ortham.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"json-schema-env-config\n======================\n\nThis library allows you to define an application's configuration parameters\nusing JSON schema, and to then load configuration from environment variables\nusing that schema, without having to explicitly name the parameter's environment\nvariables.\n\nThis library is built using the TypeScript types for JSON Schema drafts 4 and 6\nthat are provided by the `@types/json-schema` package. Drafts 5, 7 and 2019-09\nare probably also compatible. See the section below for details.\n\nThis library does not validate that the resulting config actually conforms to\nthe JSON schema: it's only interested in the data types.\n\n## Usage\n\nThe library exports two functions:\n\n```typescript\n/**\n * Load config from the given environment variables, identifying relevant\n * variables and performing type conversion according to the given schema and\n * options.\n * @param env The environment variables to load config from.\n * @param schema The configuration object's JSON schema.\n * @param options Options that control how property names are mapped to\n *                environment variable names.\n * @return A configuration object containing values loaded from the given\n *         environment variables.\n */\nfunction loadFromEnv(\n  env: NodeJS.ProcessEnv,\n  schema: JSONSchema,\n  options: EnvVarNamingOptions = {\n    case: 'snake_case',\n    propertySeparator: '__',\n    prefix: undefined\n  }\n): Record\u003cstring, JSONType\u003e\n\n/**\n * Override property values in arrays of homogeneous objects using config loaded\n * from environment variables, performing type conversion according to the given\n * schema and options.\n * @param config The existing config containing the arrays to override.\n * @param env The environment variables to load overriding config from.\n * @param schema The configuration object's JSON schema.\n * @param options Options that control how property names are mapped to\n *                environment variable names.\n * @return A copy of the configuration object that has array values overridden\n *         according to the given environment variables.\n */\nexport function overrideArrayValues(\n  config: Record\u003cstring, JSONType\u003e,\n  env: NodeJS.ProcessEnv,\n  schema: JSONSchema,\n  options: EnvVarNamingOptions = {\n    case: 'snake_case',\n    propertySeparator: '__',\n    prefix: undefined,\n    truncateTargetArrays: undefined,\n    extendTargetArrays: undefined\n  }\n): Record\u003cstring, JSONType\u003e\n```\n\nSee `tests/environment.test.ts` and `tests/override.test.ts` for many examples\nof these function being called with different inputs and outputs.\n\nTo see debug logging output, export `DEBUG=json-schema-env-config`.\n\n## Deriving environment variable names\n\nA config property's environment variable name can be derived by:\n\n1. taking the JSON pointer to that option\n2. removing the `#/` prefix\n3. adding the value of `options.prefix` as a prefix, if set\n4. replacing `/` with `_` (or the value of `options.propertySeparator` if set)\n5. converting all `camelCase` words to `snake_case` (or the value of\n   `options.case` if set).\n\nFor example, `#/camelCase/variable1` can be set using the\n`camel_case__variable_1` environment variable.\n\nEnvironment variable values are parsed as JSON, and must be parseable as the\ntype given in the JSON Schema, e.g. a `boolean` property's environment variable\nvalue must be `true` or `false`. There are two exceptions to this:\n\n* arrays may be given as JSON or CSV. JSON is tried first.\n* the `integer` JSON Schema `type` value must be a number with no fractional\n  part: unlike earlier JSON Schema specifications, an exponent will be accepted.\n\nIf a property can have multiple types (e.g. `type` is set to an array of\nstrings, or a keyword like `anyOf` is used with different types), then the\nenvironment variable value is parsed as each type in the order that they are\nlisted: the first successfully parsed value is used to set the value of the\nproperty. Note that because environment variable values are strings, they will\nalways successfully parse as the `string` type.\n\nIf a property's environment variable is not set or its value cannot be\nsuccessfully parsed, the property is left unset. Properties are set in the order\nthey are given in the schema, and objects are set before their properties. If\na property's value can be set and any of its ancestor objects are not already\nset, the ancestor objects will first be initialised to empty objects.\n\n### Loading values from the filesystem\n\nEvery config property has a second environment variable, which is named as if\nthe config property had a child property named `file`. For example, in addition\nto `camel_case__variable_1`, `#/camelCase/variable1` can be set using the\n`camel_case__variable_1__file` environment variable.\n\nWhile the environment variables without the `__file` suffix have their values\nused directly, the `__file`-suffixed variables must be set to the path to a file.\nThe file content is read as a UTF-8 string and leading and trailing whitespace\nis trimmed, and the resulting value is set as the config property's value. If\nthe file cannot be read or is empty after whitespace is trimmed, the file's\nexistence is ignored.\n\nIf the suffixed and non-suffixed env vars are both defined, the non-suffixed env\nvar overrides the suffixed env var. For example, `camel_case__variable_1__file`\ncan be set to the path to a file containing a value, but it will be ignored if\n`camel_case__variable_1` is also set.\n\n### Unnamed properties\n\nThis library is able to load values for config properties that are defined in\nJSON Schema using `patternProperties` and `additionalProperties`. However, as\nthe names of these properties are not predetermined, they are extracted from\nthe names of qualifying environment variables.\n\nAn environment variable qualifies if its name starts with the expected env var\nname for the parent object of the pattern properties. If the `type` of the\npattern property is `null`, `boolean`, `string`, `integer`, `number` or `array`,\nthe env var name suffix (everything after the parent object's expected env var\nname) is used as the property name.\n\nIf the `type` of the pattern property is `object` and the env var name suffix\nalso includes the env var name substring for one of the object's `properties`,\nthe substring from the start of the env var name suffix to the start of the\nproperty's substring is used as the property name. If none of the object's named\nproperties appear in the env var name, the whole suffix is used as the property\nname, just like for the other `type` values.\n\nFor example, given the schema\n\n```json\n{\n    \"type\": \"object\",\n    \"properties\": {\n        \"book\": {\n            \"type\": \"object\",\n            \"patternProperties\": {\n                \".*LENGTH\": {\n                    \"type\": \"number\"\n                },\n                \".*metadata\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"length\": {\n                            \"type\": \"number\"\n                        },\n                        \"author\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\nThe environment variable `book__metadata__lengthsuffix='{\"author\":{\"Joe\"}}'` has\na name starts with `book__`, so it qualifies as potentially configuring a\npattern property. It doesn't match the `.*LENGTH` pattern because patterns are\ncase-sensitive. It does match the `.*metadata` pattern, and although the\n`length` substring matches the `length` property, it's not at the end of the\nstring and isn't followed by the property separator `__`, so can't set the\n`length` property. As such, the env var is used to set the config object below.\n\n```javascript\n{\n    metadata__lengthsuffix: {\n        author: \"Joe\"\n    }\n}\n```\n\nThe process for discovering additional properties is equivalent to discovering\nproperties for the pattern `.*`.\n\n### Setting properties in array element objects\n\nGiven a config property that is an array of homogeneous objects, as well as\nsetting the value of the whole array, it's possible to do one of the following:\n\n* Set a property to the same value for every element of an array\n* Set a property value for each element of an array.\n\nThis includes the ability to set the values of nested properties, including\nthose defined as pattern or additional properties.\n\nIt's not possible to combine or nest setting every or each element of arrays,\ni.e. you can't set a property in every/each element of an array in every/each\nelement of another array. However, you can set properties in each and every\nelement of the same array: properties will be applied to each element before\nthey are applied to every element.\n\nAn array of objects is considered to by homogeneous if its schema satisfies all\nof the following conditions:\n\n1. `items` and/or `additionalItems` are defined.\n2. If `items` is defined and not an array, its `type` property is set to\n   `object`.\n3. If `items` is defined and an array, all elements of the array are deeply\n   equal and have a `type` property is set to `object`.\n4. If `additionalItems` is defined, its `type` property is set to `object`.\n5. If `items` and `additionalItems` are both defined and `items` is an array,\n   its first element must be deeply equal to the value of `additionalItems`.\n6. If `items` and `additionalItems` are both defined and `items` is not an\n   array, its value must be deeply equal to the value of `additionalItems`.\n\n#### Setting a single property value for every element\n\nIt's possible to set a single value for a property in every element of an array\nof homogeneous objects by defining an environment variable that has a name of\nthe form `\u003cenv var name for array\u003e__every__\u003cenv var name for property\u003e` and a\nvalue that is of the correct type for the target property.\n\nFor example, given a schema like\n\n```json\n{\n    \"type\": \"object\",\n    \"properties\": {\n    \"array\": {\n        \"type\": \"array\",\n        \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n            \"prop1\": {\n            \"type\": \"string\"\n            },\n            \"prop2\": {\n            \"type\": \"number\"\n            }\n        }\n        }\n    }\n    }\n}\n```\n\nand an existing config value of\n\n```javascript\n{\n    array: [\n        { prop1: 'a', prop2: 0 },\n        { prop1: 'b' }\n    ]\n}\n```\n\nand setting the environment variable `array__every__prop_2=1` would cause\n`overrideArrayValues()` to return\n\n```javascript\n{\n    array: [\n        { prop1: 'a', prop2: 1 },\n        { prop1: 'b', prop2: 1 }\n    ]\n}\n```\n\n#### Setting a property value for each element\n\nIt's also possible to set a different value for a property in each element of\nan array of homogeneous objects by defining an environment variable that has a\nname of the form `\u003cenv var name for array\u003e__each__\u003cenv var name for property\u003e`\nand a value that is an array of values that are of the correct type for the\ntarget property.\n\nEach element in the environment variable value array will be applied to the\ntarget property in the corresponding element object in the target array. If\nthe target and value arrays are of different lengths, changes will only be made\nup to the length of the shorter array. This behaviour can be changed by setting\nthe `truncateTargetArrays` and `extendTargetArrays` to `true`.\n\nFor example, given a schema like\n\n```json\n{\n    \"type\": \"object\",\n    \"properties\": {\n    \"array\": {\n        \"type\": \"array\",\n        \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n            \"prop1\": {\n            \"type\": \"string\"\n            },\n            \"prop2\": {\n            \"type\": \"number\"\n            }\n        }\n        }\n    }\n    }\n}\n```\n\nand an existing config value of\n\n```javascript\n{\n    array: [\n        { prop1: 'a', prop2: 0 },\n        { prop1: 'b' }\n    ]\n}\n```\n\nand setting the environment variable `array__each__prop_2=1,2` would cause\n`overrideArrayValues()` to return\n\n```javascript\n{\n    array: [\n        { prop1: 'a', prop2: 1 },\n        { prop1: 'b', prop2: 2 }\n    ]\n}\n```\n\n## JSON Schema compatibility\n\nThe input JSON schema must contain no schema references. If a schema contains\nreferences, they can be resolved before the schema is passed to this library:\nthere are several existing libraries that can resolve JSON references.\n\nAs this library is only concerned with config data structures and not with\nvalidation, most JSON schema keywords are irrelevant and ignored. Only the\nfollowing keywords are used:\n\n* `type`\n* `properties`\n* `additionalProperties`\n* `patternProperties`\n* `items`\n* `additionalItems`\n* `anyOf`\n* `oneOf`\n* `allOf`\n\nOther keywords that may be relevant to defining the structure of a JSON document\nbut which are currently ignored by this library are:\n\n* `if`\n* `then`\n* `else`\n* `dependencies` / `dependentSchemas`\n* `unevaluatedItems`\n* `unevaluatedProperties`\n\nAs the JSON Schema specification evolves, it may also add additional relevant\nkeywords.\n\n### Limitations\n\nAs this library allows properties to be set individually and does not perform\nvalidation against the schema, it may produce invalid config if a property can\nhave different types depending on its siblings.\n\nFor example:\n\n```js\nconst config = loadFromEnv(\n    {\n        any_of_property__key_1: '3.14',\n        any_of_property__key_2: 'true'\n    },\n    {\n        type: 'object',\n        properties: {\n            anyOfProperty: {\n                anyOf: [\n                    {\n                        type: 'object',\n                        additionalProperties: false,\n                        properties: {\n                            key1: {\n                                type: 'number'\n                            }\n                        }\n                    },\n                    {\n                        type: 'object',\n                        additionalProperties: false,\n                        properties: {\n                            key1: {\n                                type: 'string'\n                            },\n                            key2: {\n                                type: 'boolean'\n                            }\n                        }\n                    }\n                ]\n            }\n        }\n    }\n);\n\n// This combination of variables would fail schema validation, as the\n// value for key1 conforms to the first schema, but the value for key2\n// conforms to the second, and the two are incompatible.\nexpect(config.anyOfProperty.key1).toBe(3.14);\nexpect(config.anyOfProperty.key2).toBe(true);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fortham%2Fjson-schema-env-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fortham%2Fjson-schema-env-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fortham%2Fjson-schema-env-config/lists"}