json-repair 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf5d06053ce264da6b60de4beef4a5718fd3482c81d295deea6dbf90fc63c098
4
- data.tar.gz: 9d343f549cf34414e0de45618cb19bd4a9b58b12e53c2f52da0380fc73b4d95a
3
+ metadata.gz: fc8c6dfaa0c00f4f8dcf62be1f5bd925fe1af4b0649a7986bf4354f85f1d27bd
4
+ data.tar.gz: dd91d930dfe19c7796e02898202e51b9cd573b0af51c632c975689a2882e2851
5
5
  SHA512:
6
- metadata.gz: 269f7de75ec6cadde857d2f7c10186b1d0c63a19aa3e863744957c95b98db8d0cef790deef8814d532ca62c0405d97cfc5f441c37be9f1c4e6c7f5cfe07eaa67
7
- data.tar.gz: 9cbc099462defe41b8bf96f590833d61de90f79c8e5b0f2d8a7d0ae04ee72dcc7e512c6f3886f67cb0e700dfb1046abd143b2adbde560c502d25700035e69834
6
+ metadata.gz: 5db30ce96f442ae57ff311a93481f9c991be8b3b13e45042d9abdd6a9f0562e0a43ef2eafaf40095528104f59bee7e19753b03e4376ac170071d83f66fe1c2ca
7
+ data.tar.gz: 3a4129cc0b8faefa3ded17ea4f3440d623bcc7972444dd2cd2d0a2f0884fcb1059017059599962370b44e2677a45e2ad8862bdf7174cc99487ff9566745fd010
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changes
2
2
 
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]`, and `[,,1]` → `[1]`. A comma with no
7
+ value between it and the previous one (or the opening bracket) marks
8
+ an empty slot, which is dropped — uniformly extending the existing
9
+ single-leading-comma repair (`[,1]` → `[1]`) to repeated and interior
10
+ positions, and consistent with `dirty-json`. Divergence from upstream
11
+ [jsonrepair](https://github.com/josdejong/jsonrepair), which as of
12
+ v3.14.0 mangles `[1,,2]`/`[,,1]` into `[[1],2]`/`[[],1]` (its root
13
+ comma-sequence wrap misfires) and raises on `[1,,,,2]`; commented at
14
+ the site. Arrays only: an elided comma in an object
15
+ (`{"a":1,,"b":2}`) still raises rather than guessing a missing key —
16
+ there is no data to mangle, so the clean error is kept (and pinned in
17
+ the spec). Differential vs main over a comma grid: every change is an
18
+ array with elided commas going from mangle/raise to a correct drop,
19
+ with objects and all non-elided shapes unchanged. Benchmarks flat.
20
+
21
+ ### 2026-06-17 (0.14.0)
22
+
23
+ * Repair a leading `+` on numbers, like in JSON5: `+1.23` → `1.23`,
24
+ `[+1]` → `[1]`, `{"a": +5}` → `{"a":5}`, `+.5` → `0.5`. The `+` is
25
+ consumed and dropped (JSON has no explicit plus), so a leading-zero
26
+ number is quoted exactly like its unsigned form (`+05` → `"05"`),
27
+ while a `+` inside an exponent is left untouched (`2e+5` →
28
+ `200000.0`). The `+` must be followed by a digit or a leading dot, so
29
+ a bare `+` (and `+abc`, `++1`, `+e5`) still raises — and requiring a
30
+ digit/dot keeps the 0.12.0 empty-mantissa exponent guard intact.
31
+ Divergence from upstream
32
+ [jsonrepair](https://github.com/josdejong/jsonrepair) (v3.14.0 leaves
33
+ `+1.23` unrepaired, raising "Unexpected end of json string"),
34
+ commented at the site. Mirrors the leading-dot number repair (0.9.0).
35
+
3
36
  ### 2026-06-12 (0.13.0)
4
37
 
5
38
  * Repair `#` hash line comments, like in Python, YAML, or Hjson:
data/CLAUDE.md CHANGED
@@ -24,7 +24,7 @@ A few repair heuristics deliberately go **beyond** upstream (leading-dot numbers
24
24
 
25
25
  ### Entry point
26
26
 
27
- `JSON.repair(str)` in `lib/json/repair.rb` first tries stdlib `JSON.parse` (fast path; opt out with `skip_json_loads: true`), and falls back to `JSON::Repairer.new(str).repair` when that raises. Either way the result is re-serialized with `JSON.generate`, so **output is canonical** — whitespace collapsed, numbers normalized, duplicate keys last-write-wins — and both paths agree on it. A `REPAIR_REQUIRED_PATTERN` regex routes inputs containing comments or invalid escapes straight to the Repairer even though the bundled `json` gem would accept them. `return_objects: true` returns the parsed Ruby value instead of a string; `JSON.repair_file(path)` / `JSON.repair_io(io)` are convenience wrappers forwarding both options.
27
+ `JSON.repair(str)` in `lib/json/repair.rb` first tries stdlib `JSON.parse` (fast path; opt out with `skip_json_loads: true`), and falls back to `JSON::Repairer.new(str).repair` when that raises. Either way the result is re-serialized with `JSON.generate`, so **output is canonical** — whitespace collapsed, numbers normalized, duplicate keys last-write-wins — and both paths agree on it. There is no pre-filter regex: the fast path simply runs `JSON.parse` and falls back to the Repairer on any `JSON::ParserError`. Comments are handled either way — the bundled `json` gem currently accepts `//` and `/*` comments (an implementation detail, not part of the `JSON.parse` contract) and `JSON.generate` drops them (`{} // c` → `{}`); were a future `json` release to reject them, they would simply raise and route to the Repairer, which strips comments too. Invalid escapes, NDJSON, Markdown fences, and trailing prose already make `JSON.parse` raise and so route to the Repairer. `return_objects: true` returns the parsed Ruby value instead of a string; `JSON.repair_file(path)` / `JSON.repair_io(io)` are convenience wrappers forwarding both options.
28
28
 
29
29
  `JSON::JSONRepairError` is the only error raised for unrecoverable inputs; it exposes the failure `#position`. If the Repairer ever emits a string stdlib cannot parse (a Repairer bug), the `JSON::ParserError` is wrapped in `JSONRepairError` rather than leaked.
30
30
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module JSON
4
4
  module Repair
5
- VERSION = '0.13.0'
5
+ VERSION = '0.15.0'
6
6
  end
7
7
  end
data/lib/json/repairer.rb CHANGED
@@ -434,6 +434,24 @@ module JSON
434
434
  end
435
435
  end
436
436
 
437
+ # Repair elided empty array slots like [1,,2] or [1,,,,2]. Called both
438
+ # at the start of an array (leading commas) and right after a separator
439
+ # comma: skip every comma found here (each marks a slot with no value
440
+ # between it and the previous one or the opening bracket), so [1,,2] ->
441
+ # [1,2], [1,,,,2] -> [1,2], and [,,1] -> [1]. Dropping the slot — rather
442
+ # than inserting null — extends the single-leading-comma repair ([,1] ->
443
+ # [1]) uniformly. Divergence from upstream, which mangles [1,,2] into
444
+ # [[1],2] via its root comma-sequence wrap and raises on [1,,,,2] (as of
445
+ # v3.14.0).
446
+ def skip_elided_commas
447
+ loop do
448
+ parse_whitespace_and_skip_comments
449
+ break unless @json[@index] == COMMA
450
+
451
+ @index += 1
452
+ end
453
+ end
454
+
437
455
  # Parse a string enclosed by double quotes "...". Can contain escaped quotes
438
456
  # Repair strings enclosed in single quotes or special quotes
439
457
  # Repair an escaped string
@@ -741,6 +759,24 @@ module JSON
741
759
  # Parse a number like 2.4 or 2.4e6
742
760
  def parse_number
743
761
  start = @index
762
+
763
+ # Divergence from upstream: accept and discard a leading "+". JSON5
764
+ # permits an explicit plus; JSON does not, so "+1.23" -> "1.23" and
765
+ # "{"a": +5}" -> {"a":5}. The "+" must be followed by a digit or a
766
+ # leading dot (mirroring the "-" branch below); otherwise this is not
767
+ # a number and we backtrack to the "+" so a bare "+" still raises.
768
+ # `start` stays on the "+", so the reset paths restore @index cleanly
769
+ # and the @index > start exponent guard below still implies a digit
770
+ # was consumed; the two emission sites drop the leading "+" before
771
+ # quoting. Upstream leaves "+1.23" unrepaired.
772
+ if @json[@index] == PLUS
773
+ @index += 1
774
+ unless digit?(@json[@index]) || @json[@index] == DOT
775
+ @index = start
776
+ return false
777
+ end
778
+ end
779
+
744
780
  if @json[@index] == '-'
745
781
  @index += 1
746
782
  if at_end_of_number?
@@ -802,7 +838,9 @@ module JSON
802
838
 
803
839
  if @index > start
804
840
  # repair a number with leading zeros like "00789"
805
- num = @json[start...@index]
841
+ # drop a leading "+" first (see the PLUS branch above), so "+05"
842
+ # quotes like "05" and "+1.23" emits as "1.23"
843
+ num = @json[start...@index].delete_prefix(PLUS)
806
844
  # the optional sign quotes "-05" like "05" (divergence from
807
845
  # upstream, whose unsigned check lets "-05" through unrepaired)
808
846
  has_invalid_leading_zero = num.match?(/^-?0\d/)
@@ -825,8 +863,9 @@ module JSON
825
863
  @index += 1
826
864
  parse_whitespace_and_skip_comments
827
865
 
828
- # repair: skip leading comma like in [,1,2,3]
829
- parse_whitespace_and_skip_comments if skip_character(COMMA)
866
+ # repair: skip leading commas (elided empty slots) like [,1,2,3]
867
+ # or [,,1] — the same drop applied after a separator comma below
868
+ skip_elided_commas
830
869
 
831
870
  initial = true
832
871
  while @index < @json.length && @json[@index] != CLOSING_BRACKET
@@ -837,6 +876,8 @@ module JSON
837
876
  processed_comma = parse_character(COMMA)
838
877
  # repair missing comma
839
878
  @output = insert_before_last_whitespace(@output, ',') unless processed_comma
879
+ # repair: drop elided empty slots like [1,,2] -> [1,2]
880
+ skip_elided_commas if processed_comma
840
881
  end
841
882
 
842
883
  skip_ellipsis
@@ -908,7 +949,8 @@ module JSON
908
949
  # repair numbers cut off at the end
909
950
  # this will only be called when we end after a '.', '-', or 'e' and does not
910
951
  # change the number more than it needs to make it valid JSON
911
- num = "#{@json[start...@index]}0"
952
+ # delete_prefix drops a leading "+" (see the PLUS branch in parse_number)
953
+ num = "#{@json[start...@index]}0".delete_prefix(PLUS)
912
954
  # quote a padded token that has an invalid leading zero, like "05e" ->
913
955
  # "05e0", applying the same rule as the end of parse_number (divergence
914
956
  # from upstream, which emits the invalid number raw)
@@ -66,6 +66,9 @@ module JSON
66
66
  # or a similar construct in objects.
67
67
  def skip_ellipsis: () -> void
68
68
 
69
+ # Drop elided empty array slots like [1,,2] -> [1,2].
70
+ def skip_elided_commas: () -> void
71
+
69
72
  # Parse a string enclosed by double quotes "...". Can contain escaped quotes
70
73
  # Repair strings enclosed in single quotes or special quotes
71
74
  # Repair an escaped string
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-repair
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aleksandr Zykov