Skip to content

asSnakeCase drops keys that are already snake_case #1635

Closed

Description

@joshunrau

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

  1. added
    BugType: existing behavior is wrong
    Difficulty: LowIsolated change, about 2 hours or less
    Area: InstrumentsInstrument authoring API, bundling, runtime, rendering, measures and the built-in library
    Good First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributor
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: InstrumentsInstrument authoring API, bundling, runtime, rendering, measures and the built-in libraryBugType: existing behavior is wrongDifficulty: LowIsolated change, about 2 hours or lessGood First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributorPriority: MediumReal value; schedule normally

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions