Skip to content

Commit 4a85fcc

Browse files
sashazykovclaude
andcommitted
Repair elided empty array slots like [1,,2] (0.15.0)
[1,,2] silently mangled to [[1],2] and [1,,,,2] raised. The empty slot made parse_array terminate early (emitting [1]), then the root comma-sequence wrap turned [1] and 2 into NDJSON [[1],2] — data corruption. Both are upstream-shared (jsonrepair v3.14.0 produces the same mangle / raise). Drop the elided slots instead: after a separator comma, parse_array now skips any further commas via a new skip_elided_commas helper, so [1,,2] -> [1,2], [1,,,,2] -> [1,2], [1, ,2] -> [1,2] (whitespace and comments between the commas are skipped too). Dropping — rather than inserting null — matches the existing leading-comma repair ([,1] -> [1]) and dirty-json. Divergence from upstream, commented at the site. Arrays only. An elided comma in an object ({"a":1,,"b":2}) still raises rather than guessing a missing key: it is a clean error with no data to mangle, so we hold upstream's behavior. Pinned in the spec. Differential vs main over a 42-input comma grid: every change is an array with elided commas going from mangle/raise to a correct drop; objects and all non-elided shapes (leading/trailing commas, ellipsis, missing commas, truncated nested containers) are unchanged. Bench flat across all four scenarios. rspec (204 examples, 100% line+branch), rubocop, rbs validate, steep all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 91bdbf3 commit 4a85fcc

6 files changed

Lines changed: 59 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changes
22

3+
### 2026-06-17 (0.15.0)
4+
5+
* Repair elided empty array slots: `[1,,2]``[1,2]`, `[1,,,,2]`
6+
`[1,2]`, `[1, ,2]``[1,2]`. After a separator comma, any further
7+
commas mark empty slots with no value between them and are dropped —
8+
matching how a leading comma is dropped (`[,1]``[1]`), and
9+
consistent with `dirty-json`. Divergence from upstream
10+
[jsonrepair](https://github.com/josdejong/jsonrepair), which as of
11+
v3.14.0 mangles `[1,,2]` into `[[1],2]` (its root comma-sequence wrap
12+
misfires) and raises on `[1,,,,2]`; commented at the site. Arrays
13+
only: an elided comma in an object (`{"a":1,,"b":2}`) still raises
14+
rather than guessing a missing key — there is no data to mangle, so
15+
the clean error is kept (and pinned in the spec). Differential vs
16+
main over a 42-input comma grid: every change is an array with elided
17+
commas going from mangle/raise to a correct drop, with objects and
18+
all non-elided shapes unchanged. Benchmarks flat.
19+
320
### 2026-06-17 (0.14.0)
421

522
* Repair a leading `+` on numbers, like in JSON5: `+1.23``1.23`,

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
json-repair (0.14.0)
4+
json-repair (0.15.0)
55

66
GEM
77
remote: https://rubygems.org/

lib/json/repair/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
module JSON
44
module Repair
5-
VERSION = '0.14.0'
5+
VERSION = '0.15.0'
66
end
77
end

lib/json/repairer.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,22 @@ def skip_ellipsis
434434
end
435435
end
436436

437+
# Repair elided empty array slots like [1,,2] or [1,,,,2]. Called right
438+
# after a separator comma: skip every further comma (each marks a slot
439+
# with no value between it and the previous one), so [1,,2] -> [1,2] and
440+
# [1,,,,2] -> [1,2]. Dropping the slot — rather than inserting null —
441+
# matches how a leading comma is dropped ([,1] -> [1]). Divergence from
442+
# upstream, which mangles [1,,2] into [[1],2] via its root comma-sequence
443+
# wrap and raises on [1,,,,2] (as of v3.14.0).
444+
def skip_elided_commas
445+
loop do
446+
parse_whitespace_and_skip_comments
447+
break unless @json[@index] == COMMA
448+
449+
@index += 1
450+
end
451+
end
452+
437453
# Parse a string enclosed by double quotes "...". Can contain escaped quotes
438454
# Repair strings enclosed in single quotes or special quotes
439455
# Repair an escaped string
@@ -857,6 +873,8 @@ def parse_array
857873
processed_comma = parse_character(COMMA)
858874
# repair missing comma
859875
@output = insert_before_last_whitespace(@output, ',') unless processed_comma
876+
# repair: drop elided empty slots like [1,,2] -> [1,2]
877+
skip_elided_commas if processed_comma
860878
end
861879

862880
skip_ellipsis

sig/json/repairer.rbs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ module JSON
6666
# or a similar construct in objects.
6767
def skip_ellipsis: () -> void
6868

69+
# Drop elided empty array slots like [1,,2] -> [1,2].
70+
def skip_elided_commas: () -> void
71+
6972
# Parse a string enclosed by double quotes "...". Can contain escaped quotes
7073
# Repair strings enclosed in single quotes or special quotes
7174
# Repair an escaped string

spec/json_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,25 @@
504504
expect(JSON.repair('[ , 1,2,3]')).to eq('[1,2,3]')
505505
end
506506

507+
it 'drops elided empty slots in an array' do
508+
expect(JSON.repair('[1,,2]')).to eq('[1,2]')
509+
expect(JSON.repair('[1,,,,2]')).to eq('[1,2]')
510+
expect(JSON.repair('[1, ,2]')).to eq('[1,2]')
511+
expect(JSON.repair('[1,/* gap */,2]')).to eq('[1,2]')
512+
expect(JSON.repair('["a",,"b"]')).to eq('["a","b"]')
513+
# works inside a nested array too
514+
expect(JSON.repair('[[1,,2],3]')).to eq('[[1,2],3]')
515+
# trailing empty slots collapse with the trailing-comma repair
516+
expect(JSON.repair('[1,,]')).to eq('[1]')
517+
end
518+
519+
it 'still raises on elided commas in an object (no silent mangle)' do
520+
# arrays drop the slot (above); objects keep raising rather than
521+
# guessing a key — a comma with no key:value pair is ambiguous and
522+
# there is no data to mangle, so we hold upstream's clean error
523+
expect { JSON.repair('{"a":1,,"b":2}') }.to raise_error(JSON::JSONRepairError)
524+
end
525+
507526
it 'strips a leading comma from an object' do
508527
expect(JSON.repair('{,"message": "hi"}')).to eq('{"message":"hi"}')
509528
expect(JSON.repair('{/* a */,/* b */"message": "hi"}')).to eq('{"message":"hi"}')

0 commit comments

Comments
 (0)