Skip to content

fix: preserve scalar keys ending in "[]" across stringify/parse - #312

Open
spokodev wants to merge 2 commits into
npm:mainfrom
spokodev:fix/scalar-key-ending-in-brackets
Open

spokodev wants to merge 2 commits into
npm:mainfrom
spokodev:fix/scalar-key-ending-in-brackets

Conversation

@spokodev

@spokodev spokodev commented Sep 2, 2026 •

Copy link
Copy Markdown

ini.parse(ini.stringify(obj)) turns a scalar key that ends in [] into an array. stringify writes { 'foo[]': 'bar' } as foo[]=bar, which is the bracketedArray array marker, so parse returns { foo: ['bar'] }. bracketedArray is on by default.

Fix

  • stringify writes such a key as "foo[\u005d"=bar. The JSON string decodes to foo[], but the raw key does not end in []".
  • parse treats a quoted key as an array only when the raw key ends in []" (or []'). That is the form stringify already uses for array keys that need quoting, such as "[foo[]"=first, so those files parse as before.
  • The align width uses the same encoded key.
  • The key [] itself and bracketedArray: false are unchanged.

Compatibility

Text written by main parses the same with this change. I encoded 1824 objects with main (19 keys such as [a, a=b, quoted keys and keys with [], times 8 values, as scalar, array and section, with 4 option sets) and parsed the output with both versions: 0 differences. The one change on the parse side is that a quoted key whose final [] is written with escapes, such as "foo[\u005d", is now read as a scalar.

Tests

  • Round-trip for { 'foo[]': 'bar' }, { '[foo[]': 'bar' } and { '[foo': ['first', 'second'] }.
  • flag[] # note and flag # note[].
  • align with a key ending in [].

Breaking any part of the change fails at least one of them (checked with 7 targeted mutations). npm test passes with 100% coverage.

@spokodev
spokodev requested a review from a team as a code owner September 2, 2026 23:15
@jeffkl
jeffkl requested a balanced review from Copilot October 30, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved serialization and parsing regressions can lose array values, misclassify commented keys, and break the documented alignment option.

3 open issues
What changed in this PR

This PR addresses scalar keys ending in [] being mistaken for arrays during INI stringify/parse round trips.

Changes:

  • Quote scalar keys ending in [] when bracketed-array mode is enabled.
  • Treat quoted keys literally during parsing.
  • Add a round-trip regression case for a scalar key ending in [].
File Description
test/​bar.js Adds a scalar-key round-trip regression case.
lib/​ini.js Updates key quoting and array-marker detection.

Review effort: Balanced

Comment thread lib/ini.js Outdated
children.push(k)
} else {
out += safe(k).padEnd(padToChars, ' ') + separator + safe(val) + eol
const safeKey = opt.bracketedArray && k.endsWith('[]') ? JSON.stringify(k) : safe(k)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 673df11. The width now comes from the same encoded key that is written, so stringify({ 'foo[]': 'bar', x: 'y' }, { align: true }) puts both = in one column. Added a test for it.

Comment thread lib/ini.js Outdated
if (opt.bracketedArray) {
isArray = keyRaw.length > 2 && keyRaw.slice(-2) === '[]'
const rawKey = match[2].trim()
isArray = !isQuoted(rawKey) && rawKey.length > 2 && rawKey.slice(-2) === '[]'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 673df11. The array suffix is checked on the decoded key again, as on main, so flag[] # note gives { flag: [true] } and flag # note[] gives { flag: true }. Both are now tests.

Comment thread lib/ini.js Outdated
if (opt.bracketedArray) {
isArray = keyRaw.length > 2 && keyRaw.slice(-2) === '[]'
const rawKey = match[2].trim()
isArray = !isQuoted(rawKey) && rawKey.length > 2 && rawKey.slice(-2) === '[]'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 673df11. Quoted array keys keep the format main writes ("[foo[]"=first) and still parse as arrays. A scalar key ending in [] is now written as "foo[\u005d", which JSON-decodes to foo[] but does not end in []", so the parser can tell the two apart. Added round-trip cases for { '[foo': ['first', 'second'] } and { '[foo[]': 'bar' }.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants