json-repair 0.11.0 → 0.11.2
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 +4 -4
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +32 -0
- data/CLAUDE.md +1 -1
- data/lib/json/repair/version.rb +1 -1
- data/lib/json/repairer.rb +29 -8
- data/sig/json/repairer.rbs +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d7b2f30c2451f62471beabed342ff8360f644e4ae703fa3e0f5490e44d4f0d1
|
|
4
|
+
data.tar.gz: 254abaa4ab104a0cc6650d02f099ebb00b0600ef213b00866da266159b6c1a37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bfd417574888a7ff44a03870f48ab6e02c7bb7ae80189262e093425ba52e3943982f6ab36170c1cac6a3871e3d3379469b8319bbaa6defe8a0d473a7be6fe2a9
|
|
7
|
+
data.tar.gz: 208611ef2a09d4e3a5c91afe98334dfc7443a3db56de9624cfa82a3dea1e0c0480de18227646a96dac3c46f64af526e229f70dd9de4cd41cc1160a703b132a0b
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
# Merge our Exclude lists with RuboCop's defaults (vendor/**/*, tmp/**/*, …)
|
|
2
|
+
# instead of replacing them — CI vendors gems into vendor/bundle, which
|
|
3
|
+
# RuboCop must keep skipping.
|
|
4
|
+
inherit_mode:
|
|
5
|
+
merge:
|
|
6
|
+
- Exclude
|
|
7
|
+
|
|
1
8
|
AllCops:
|
|
2
9
|
TargetRubyVersion: 3.0
|
|
10
|
+
Exclude:
|
|
11
|
+
# gitignored local planning notes and scratch tooling (see CLAUDE.md)
|
|
12
|
+
- docs/**/*
|
|
3
13
|
|
|
4
14
|
Style/Documentation:
|
|
5
15
|
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
### 2026-06-12 (0.11.2)
|
|
4
|
+
|
|
5
|
+
* Fix the 0.11.0 doubled-colon repair silently mangling objects with a
|
|
6
|
+
stray junk word between pairs. `{"value_1": true, COMMENT "value_2":
|
|
7
|
+
"data"}` returned `{"value_1":true,"COMMENT":"value_2\": \"data"}`
|
|
8
|
+
(the junk word became a key and swallowed the real pair), and
|
|
9
|
+
`{ "key": "value" COMMENT "key2": "value2" }` returned a single
|
|
10
|
+
glued string value. Both shapes now raise "Object key expected"
|
|
11
|
+
again at the same positions as upstream
|
|
12
|
+
[jsonrepair](https://github.com/josdejong/jsonrepair) v3.14.0,
|
|
13
|
+
restoring the pre-0.11.0 behavior: the merge is skipped when the
|
|
14
|
+
pair already needed a missing-colon repair or the value string was
|
|
15
|
+
itself salvaged by the unescaped-quote repair — signals that the
|
|
16
|
+
pair was malformed in a way the merge would compound, not fix. The
|
|
17
|
+
salvage signal survives string concatenation: in
|
|
18
|
+
`{"a": "b" x "c" + "d": "e"}` the `+ "d"` segment no longer clears
|
|
19
|
+
it (caught in review by Copilot). All
|
|
20
|
+
0.11.0 repairs (canonical, greedy, escaped quotes, unquoted
|
|
21
|
+
keys/values) are unchanged. Go and Python `json_repair` instead
|
|
22
|
+
drop the junk word; we deliberately keep raising rather than
|
|
23
|
+
silently discarding input (see the 0.11.0 note).
|
|
24
|
+
|
|
25
|
+
* Fix a `TypeError` crash on input ending in a lone backslash inside a
|
|
26
|
+
string: `"abc\` now repairs to `"abc"` (likewise `"\` → `""`,
|
|
27
|
+
`["abc\` → `["abc"]`, `{"a": "b\` → `{"a":"b"}`), matching upstream
|
|
28
|
+
[jsonrepair](https://github.com/josdejong/jsonrepair) v3.14.0. This
|
|
29
|
+
was a porting bug — JS `charAt` past EOF returns `''` where Ruby
|
|
30
|
+
`String#[]` returns `nil`, so the invalid-escape repair in
|
|
31
|
+
`parse_string` crashed on `str << nil` instead of ending the string,
|
|
32
|
+
violating the contract that `JSONRepairError` is the only error
|
|
33
|
+
raised. Found by differential fuzzing during the 0.11.0 review.
|
|
34
|
+
|
|
3
35
|
### 2026-06-12 (0.11.0)
|
|
4
36
|
|
|
5
37
|
* Repair object string values with unescaped quotes around a colon
|
data/CLAUDE.md
CHANGED
|
@@ -14,7 +14,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
14
14
|
- `bundle exec rake install` / `bundle exec rake release` — local install / publish to rubygems.org (release prompts for a rubygems MFA OTP).
|
|
15
15
|
- Type checking: `Steepfile` checks `lib/` against `sig/`. `bundle exec steep check` (typecheck) and `bundle exec rbs validate` (sig syntax) both run in CI and as part of the default rake task. `steep` and `rbs` are dev dependencies in the `Gemfile`.
|
|
16
16
|
|
|
17
|
-
Ruby `>= 3.0.0` is required (per gemspec). CI runs against Ruby 3.3.
|
|
17
|
+
Ruby `>= 3.0.0` is required (per gemspec). CI runs against all currently maintained Ruby branches (3.3, 3.4, 4.0).
|
|
18
18
|
|
|
19
19
|
## Architecture
|
|
20
20
|
|
data/lib/json/repair/version.rb
CHANGED
data/lib/json/repairer.rb
CHANGED
|
@@ -32,6 +32,7 @@ module JSON
|
|
|
32
32
|
@json = json
|
|
33
33
|
@index = 0
|
|
34
34
|
@output = +''
|
|
35
|
+
@repaired_unescaped_quote = false
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def repair
|
|
@@ -68,7 +69,7 @@ module JSON
|
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
# repair redundant end quotes
|
|
71
|
-
while
|
|
72
|
+
while [CLOSING_BRACE, CLOSING_BRACKET].include?(@json[@index])
|
|
72
73
|
@index += 1
|
|
73
74
|
parse_whitespace_and_skip_comments
|
|
74
75
|
end
|
|
@@ -236,7 +237,6 @@ module JSON
|
|
|
236
237
|
|
|
237
238
|
initial = true
|
|
238
239
|
while @index < @json.length && @json[@index] != CLOSING_BRACE
|
|
239
|
-
processed_comma = true
|
|
240
240
|
if initial
|
|
241
241
|
initial = false
|
|
242
242
|
else
|
|
@@ -296,8 +296,14 @@ module JSON
|
|
|
296
296
|
end
|
|
297
297
|
|
|
298
298
|
# repair: an object string value with unescaped quotes around a
|
|
299
|
-
# colon, like {"a": "b": "c"}
|
|
300
|
-
|
|
299
|
+
# colon, like {"a": "b": "c"}. Skipped when this pair already
|
|
300
|
+
# needed a repair that makes the merge compound garbage: a
|
|
301
|
+
# missing colon (the "key" was a stray junk word, like
|
|
302
|
+
# {"v1": true, COMMENT "v2": "data"}) or a value glued together
|
|
303
|
+
# by the unescaped-quote repair (like
|
|
304
|
+
# {"k": "v" COMMENT "k2": "v2"}); both keep raising, matching
|
|
305
|
+
# upstream
|
|
306
|
+
repair_doubled_colon if processed_value && processed_colon && !@repaired_unescaped_quote
|
|
301
307
|
end
|
|
302
308
|
|
|
303
309
|
if @json[@index] == CLOSING_BRACE
|
|
@@ -316,9 +322,13 @@ module JSON
|
|
|
316
322
|
# (the unescaped-quotes reading of the input). Greedy: keeps merging
|
|
317
323
|
# while another `: "..."` follows. Only the string-colon-string
|
|
318
324
|
# shape is repaired; anything else falls through to the regular
|
|
319
|
-
# error paths.
|
|
320
|
-
#
|
|
321
|
-
#
|
|
325
|
+
# error paths. The call site additionally requires the pair's colon
|
|
326
|
+
# to be present in the input and the value string to have parsed
|
|
327
|
+
# without the unescaped-quote repair (@repaired_unescaped_quote) —
|
|
328
|
+
# when either repair already fired, the pair was malformed in a way
|
|
329
|
+
# this merge would compound, not fix. Divergence from upstream
|
|
330
|
+
# (which raises "Object key expected" as of v3.14.0), matching the
|
|
331
|
+
# Go and Python json-repair libraries on the canonical case.
|
|
322
332
|
def repair_doubled_colon
|
|
323
333
|
loop do
|
|
324
334
|
colon = @index
|
|
@@ -400,6 +410,9 @@ module JSON
|
|
|
400
410
|
# and fixing the string by inserting a quote there, or stopping at a
|
|
401
411
|
# stop index detected in the first iteration.
|
|
402
412
|
def parse_string(stop_at_delimiter: false, stop_at_index: -1)
|
|
413
|
+
# fresh parse (the backtracking re-invocations below rebuild the
|
|
414
|
+
# string from scratch, so they reset too); see repair_doubled_colon
|
|
415
|
+
@repaired_unescaped_quote = false
|
|
403
416
|
skip_escape_chars = @json[@index] == BACKSLASH
|
|
404
417
|
if skip_escape_chars
|
|
405
418
|
# repair: remove the first escape character
|
|
@@ -509,6 +522,7 @@ module JSON
|
|
|
509
522
|
|
|
510
523
|
# repair unescaped quote
|
|
511
524
|
str = "#{str[...o_quote]}\\#{str[o_quote..]}"
|
|
525
|
+
@repaired_unescaped_quote = true
|
|
512
526
|
elsif stop_at_delimiter && unquoted_string_delimiter?(@json[@index])
|
|
513
527
|
# we're in the mode to stop the string at the first delimiter
|
|
514
528
|
# because there is an end quote missing
|
|
@@ -531,7 +545,9 @@ module JSON
|
|
|
531
545
|
return true
|
|
532
546
|
elsif @json[@index] == BACKSLASH
|
|
533
547
|
# handle escaped content like \n or ★
|
|
534
|
-
|
|
548
|
+
# nil at EOF: '' mirrors JS charAt, making the invalid-escape
|
|
549
|
+
# repair below a no-op that ends the string
|
|
550
|
+
char = @json[@index + 1] || ''
|
|
535
551
|
escape_char = ESCAPE_CHARACTERS[char]
|
|
536
552
|
if escape_char
|
|
537
553
|
str << @json[@index, 2]
|
|
@@ -806,7 +822,12 @@ module JSON
|
|
|
806
822
|
# repair: remove the end quote of the first string
|
|
807
823
|
@output = strip_last_occurrence(@output, '"', strip_remaining_text: true)
|
|
808
824
|
start = @output.length
|
|
825
|
+
# the segments form one logical string value: keep the doubled-colon
|
|
826
|
+
# guard's flag set when an earlier segment needed the unescaped-quote
|
|
827
|
+
# repair (parse_string resets it on entry)
|
|
828
|
+
repaired_earlier_segment = @repaired_unescaped_quote
|
|
809
829
|
parsed_str = parse_string
|
|
830
|
+
@repaired_unescaped_quote ||= repaired_earlier_segment
|
|
810
831
|
@output = if parsed_str
|
|
811
832
|
# repair: remove the start quote of the second string
|
|
812
833
|
remove_at_index(@output, start, 1)
|
data/sig/json/repairer.rbs
CHANGED