nodejs/node · error · ValueError

Dictionary key is not a string.

Error message

Dictionary key is not a string.

What it means

ValueError raised by GenerateTokens in gypi_to_gn.py when a dict key is not a Python str. GN identifiers must be string tokens, so any int/bool/None key from the GYP data is rejected before emission. This guard runs before the GN-identifier-validity check.

Source

Thrown at tools/gypi_to_gn.py:149

    elif isinstance(v, int):
      yield str(v)

    elif isinstance(v, list):
      yield '['
      for i, item in enumerate(v):
        if i > 0:
          yield ','
        for tok in GenerateTokens(item, level + 1):
          yield tok
      yield ']'

    elif isinstance(v, dict):
      if level > 0:
        yield '{'
      for key in sorted(v):
        if not isinstance(key, str):
          raise ValueError('Dictionary key is not a string.')
        if not key or key[0].isdigit() or not key.replace('_', '').isalnum():
          raise ValueError('Dictionary key is not a valid GN identifier.')
        yield key  # No quotations.
        yield '='
        for tok in GenerateTokens(v[key], level + 1):
          yield tok
      if level > 0:
        yield '}'

    else:  # Not supporting float: Add only when needed.
      raise ValueError('Unsupported type when printing to GN.')

  can_start = lambda tok: tok and tok not in ',}]='
  can_end = lambda tok: tok and tok not in ',{[='

  # Adds whitespaces, trying to keep everything (except dicts) in 1 line.
  def PlainGlue(gen):
    prev_tok = None

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Coerce all dict keys to str before passing the structure to GenerateTokens.
  2. Fix the upstream producer so it emits string keys.
  3. Sanitize the loaded GYP data with a recursive key-stringification pass.

Example fix

# before
data = {1: 'x', 'foo': 'y'}
tokens = list(GenerateTokens(data))

# after
def stringify_keys(o):
    if isinstance(o, dict):
        return {str(k): stringify_keys(v) for k, v in o.items()}
    if isinstance(o, list):
        return [stringify_keys(i) for i in o]
    return o
tokens = list(GenerateTokens(stringify_keys(data)))
Defensive patterns

Strategy: type-guard

Validate before calling

def all_keys_str(obj) -> bool:
    if isinstance(obj, dict):
        return all(isinstance(k, str) and all_keys_str(v) for k, v in obj.items())
    if isinstance(obj, list):
        return all(all_keys_str(i) for i in obj)
    return True

Type guard

def has_only_string_keys(d: dict) -> bool:
    return all(isinstance(k, str) for k in d)

Try / catch

try:
    list(GenerateTokens(data))
except ValueError as e:
    if 'not a string' in str(e):
        data = stringify_keys(data)  # coerce and retry

Prevention

When it happens

Trigger: Calling GenerateTokens on a dict whose keys include non-string types - typically the result of JSON parsing with numeric keys, or GYP data transformed by code that converted keys to ints.

Common situations: GYP inputs that were round-tripped through JSON or YAML with int keys, or programmatically built dicts using enumerate()/indices as keys.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/2470de582f07b1d9. Report an issue: GitHub.