Repository navigation
asSnakeCase drops keys that are already snake_case #1635
Description
asSnakeCase drops every key that is already in snake_case. It only copies a key when snakeCase(key) !== key, so a key like age or first_name is left out of the result. The return type SnakeCasedProperties<T> still claims every key is there, so TypeScript does not catch it. The instrument authoring guide describes the function as returning "a copy of obj with its keys converted to snake_case". This is public runtime API (/runtime/v1/@opendatacapture/runtime-core). An instrument that builds its submission with it silently loses those fields. A required field then fails validation on submit. An optional field is just missing from the saved record.
Where
packages/runtime-core/src/utils.ts:7-11:
for (const key in target) {
if (snakeCase(key) !== key) {
result[snakeCase(key)] = target[key];
}
}The promise this breaks is in packages/instrument-guidelines/AGENTS.md:673.
Reproduce
asSnakeCase({ age: 30, first_name: 'Jane', lastName: 'Doe' });Actual: { last_name: 'Doe' }
Expected: { age: 30, first_name: 'Jane', last_name: 'Doe' }, which is what SnakeCasedProperties<T> declares.
Tests
packages/runtime-core/src/__tests__/utils.test.ts: it.fails('should keep keys that are already snake_case, so the result matches SnakeCasedProperties<T>'). It will go red once the source is fixed. Change it.fails to it.
Suggested fix
Drop the condition and always assign result[snakeCase(key)] = target[key]. The change alters what runtime/v1 returns, so mention it in the release notes. It is unlikely that any instrument depends on keys being dropped.
Found while raising unit test coverage in #1632.
Activity