json-repair 0.5.0 → 0.6.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/CLAUDE.md +2 -2
- data/README.md +7 -0
- data/Rakefile +11 -1
- data/Steepfile +5 -0
- data/lib/json/repair/cli.rb +6 -5
- data/lib/json/repair/version.rb +1 -1
- data/lib/json/repair.rb +5 -2
- data/lib/json/repairer.rb +14 -4
- data/sig/json/repair/cli.rbs +34 -3
- data/sig/json/repair.rbs +2 -0
- data/sig/json/repairer.rbs +10 -1
- 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: b4892b62960d0c4d27976577c506585ea8df11bfbf14dc1a9b40952947ad1f20
|
|
4
|
+
data.tar.gz: fe26e3da464e5356bd33021e5b08a7a1f1ad938e125f0ef384777ff64b13f813
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cf46eae9e05e718c978e7a503578bd867bacce1b71565764ec88af8b762f498ceceada6ce37d73663e6bcff56b85013746c7a82e84eda6eb55ed57e3535fe0d
|
|
7
|
+
data.tar.gz: f2fc802dd1bfbd26bb04f34ca2902189d11537d4bbbebc131d14ef7672bcd9f2dd01bcc2157182ba6801eea0f92693883086197013add4f86f69e6fcf32e5a3b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
### 2026-05-12 (0.6.0)
|
|
4
|
+
|
|
5
|
+
* `JSON.repair` accepts a `return_objects:` keyword argument. Pass
|
|
6
|
+
`return_objects: true` to receive the parsed Ruby value (Hash, Array,
|
|
7
|
+
or scalar) instead of a serialized JSON string. Default is `false`,
|
|
8
|
+
preserving the existing return-a-string contract. Mirrors Python's
|
|
9
|
+
`return_objects` option on
|
|
10
|
+
[`json_repair`](https://github.com/mangiucugna/json_repair).
|
|
11
|
+
|
|
3
12
|
### 2026-05-12 (0.5.0)
|
|
4
13
|
|
|
5
14
|
* `JSON::JSONRepairError#position` returns the input index at which the
|
data/CLAUDE.md
CHANGED
|
@@ -5,13 +5,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
7
7
|
- `bin/setup` — install dependencies via Bundler.
|
|
8
|
-
- `bundle exec rake` — default task; runs
|
|
8
|
+
- `bundle exec rake` — default task; runs RSpec, RuboCop, RBS validate, and Steep.
|
|
9
9
|
- `bundle exec rspec` — run the test suite.
|
|
10
10
|
- `bundle exec rspec spec/json_spec.rb:42` — run a single example by line number; nearly all behavioral specs live in `spec/json_spec.rb`.
|
|
11
11
|
- `bundle exec rubocop` — lint. Project-specific exclusions in `.rubocop.yml` deliberately disable several `Metrics/*` cops for `lib/json/repairer.rb` and `lib/json/repair/string_utils.rb` because the parser is long by design — don't try to "fix" it by chopping methods up.
|
|
12
12
|
- `bin/console` — IRB with the gem preloaded.
|
|
13
13
|
- `bundle exec rake install` / `bundle exec rake release` — local install / publish to rubygems.org.
|
|
14
|
-
- Type checking: `Steepfile` checks `lib/` against `sig/`.
|
|
14
|
+
- 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`.
|
|
15
15
|
|
|
16
16
|
Ruby `>= 3.0.0` is required (per gemspec). CI runs against Ruby 3.3.1.
|
|
17
17
|
|
data/README.md
CHANGED
|
@@ -31,6 +31,13 @@ puts repaired_json # Outputs: {"name": "Alice", "age": 25}
|
|
|
31
31
|
|
|
32
32
|
The `repair` method takes a string containing JSON data and returns a corrected version of this string, ensuring it is valid JSON.
|
|
33
33
|
|
|
34
|
+
Pass `return_objects: true` to get the parsed Ruby value (Hash, Array, or scalar) instead of a string:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
JSON.repair('{a: 1, b: [2, 3,]}', return_objects: true)
|
|
38
|
+
# => {"a" => 1, "b" => [2, 3]}
|
|
39
|
+
```
|
|
40
|
+
|
|
34
41
|
## Command line
|
|
35
42
|
|
|
36
43
|
The gem ships a `json-repair` executable. It reads from stdin or a file and writes to stdout, `--output FILE`, or back over the input file with `--overwrite`.
|
data/Rakefile
CHANGED
|
@@ -9,4 +9,14 @@ require 'rubocop/rake_task'
|
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
desc 'Validate RBS signatures in sig/'
|
|
13
|
+
task :rbs do
|
|
14
|
+
sh 'bundle exec rbs validate'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
desc 'Run Steep type check against sig/'
|
|
18
|
+
task :steep do
|
|
19
|
+
sh 'bundle exec steep check'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
task default: %i[spec rubocop rbs steep]
|
data/Steepfile
CHANGED
data/lib/json/repair/cli.rb
CHANGED
|
@@ -34,7 +34,8 @@ module JSON
|
|
|
34
34
|
|
|
35
35
|
def run(argv)
|
|
36
36
|
positional = catch(:halt) { parser.parse(argv) }
|
|
37
|
-
|
|
37
|
+
halt = @halt
|
|
38
|
+
return halt if halt
|
|
38
39
|
|
|
39
40
|
input_path = positional.first
|
|
40
41
|
return 1 unless validate(positional, input_path)
|
|
@@ -61,7 +62,7 @@ module JSON
|
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def read_input(input_path)
|
|
64
|
-
raw = input_path ? File.read(input_path) : @stdin.read
|
|
65
|
+
raw = (input_path ? File.read(input_path) : @stdin.read).to_s
|
|
65
66
|
raw.force_encoding(Encoding::UTF_8)
|
|
66
67
|
raise JSON::JSONRepairError, 'input is not valid UTF-8' unless raw.valid_encoding?
|
|
67
68
|
|
|
@@ -69,10 +70,10 @@ module JSON
|
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def write_output(repaired, input_path)
|
|
72
|
-
if @overwrite
|
|
73
|
+
if @overwrite && input_path
|
|
73
74
|
replace_in_place(input_path, repaired)
|
|
74
|
-
elsif @output_path
|
|
75
|
-
File.write(
|
|
75
|
+
elsif (output_path = @output_path)
|
|
76
|
+
File.write(output_path, repaired)
|
|
76
77
|
else
|
|
77
78
|
@stdout.write(repaired)
|
|
78
79
|
@stdout.write("\n") unless repaired.end_with?("\n")
|
data/lib/json/repair/version.rb
CHANGED
data/lib/json/repair.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
require_relative 'repair/version'
|
|
4
6
|
require_relative 'repairer'
|
|
5
7
|
|
|
@@ -13,7 +15,8 @@ module JSON
|
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
def self.repair(json)
|
|
17
|
-
Repairer.new(json).repair
|
|
18
|
+
def self.repair(json, return_objects: false)
|
|
19
|
+
repaired = Repairer.new(json).repair
|
|
20
|
+
return_objects ? JSON.parse(repaired) : repaired
|
|
18
21
|
end
|
|
19
22
|
end
|
data/lib/json/repairer.rb
CHANGED
|
@@ -227,9 +227,19 @@ module JSON
|
|
|
227
227
|
if processed_colon || truncated_text
|
|
228
228
|
# repair missing object value
|
|
229
229
|
@output << 'null'
|
|
230
|
+
# :nocov:
|
|
230
231
|
else
|
|
232
|
+
# Unreachable through JSON.repair: if we got here, the colon-repair
|
|
233
|
+
# branch above ran, which required start_of_value? to be true. Every
|
|
234
|
+
# char that satisfies start_of_value? (see REGEX_START_OF_VALUE plus
|
|
235
|
+
# quote chars) is consumable by some parse_* method, so parse_value
|
|
236
|
+
# cannot return false in this state. Preserved for parity with the
|
|
237
|
+
# upstream JS parser; if a future change to REGEX_START_OF_VALUE or
|
|
238
|
+
# parse_unquoted_string invalidates that invariant, this branch
|
|
239
|
+
# becomes live and the :nocov: will hide it.
|
|
231
240
|
throw_colon_expected
|
|
232
241
|
end
|
|
242
|
+
# :nocov:
|
|
233
243
|
end
|
|
234
244
|
end
|
|
235
245
|
|
|
@@ -725,10 +735,10 @@ module JSON
|
|
|
725
735
|
processed_value = parse_value
|
|
726
736
|
end
|
|
727
737
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
738
|
+
# repair: remove trailing comma
|
|
739
|
+
# (the `while processed_value` loop above only exits when processed_value
|
|
740
|
+
# is falsy, so the upstream JS `if (!processedValue)` guard is redundant)
|
|
741
|
+
@output = strip_last_occurrence(@output, ',')
|
|
732
742
|
|
|
733
743
|
# repair: wrap the output inside array brackets
|
|
734
744
|
@output = "[\n#{@output}\n]"
|
data/sig/json/repair/cli.rbs
CHANGED
|
@@ -1,16 +1,47 @@
|
|
|
1
1
|
module JSON
|
|
2
2
|
module Repair
|
|
3
3
|
class CLI
|
|
4
|
-
# `::IO | ::StringIO` because `::StringIO` is not an `::IO` subclass
|
|
5
|
-
# specs inject `::StringIO` instances
|
|
6
|
-
# that implements `#read` / `#write` / `#puts` would work too.
|
|
4
|
+
# `::IO | ::StringIO` because `::StringIO` is not an `::IO` subclass
|
|
5
|
+
# and the specs inject `::StringIO` instances.
|
|
7
6
|
type stream = ::IO | ::StringIO
|
|
8
7
|
|
|
8
|
+
# Marked `private_constant` in `lib/json/repair/cli.rb`. RBS has no
|
|
9
|
+
# `private_constant` syntax, so the declaration is unavoidably public
|
|
10
|
+
# in the signature; do not rely on it from outside this class.
|
|
11
|
+
OVERWRITE_DESC: ::String
|
|
12
|
+
|
|
13
|
+
@stdin: stream
|
|
14
|
+
@stdout: stream
|
|
15
|
+
@stderr: stream
|
|
16
|
+
@output_path: ::String?
|
|
17
|
+
@halt: ::Integer?
|
|
18
|
+
@overwrite: bool
|
|
19
|
+
|
|
9
20
|
def self.call: (::Array[::String] argv, ?stdin: stream, ?stdout: stream, ?stderr: stream) -> ::Integer
|
|
10
21
|
|
|
11
22
|
def initialize: (?stdin: stream, ?stdout: stream, ?stderr: stream) -> void
|
|
12
23
|
|
|
13
24
|
def call: (::Array[::String] argv) -> ::Integer
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def run: (::Array[::String] argv) -> ::Integer
|
|
29
|
+
|
|
30
|
+
def validate: (::Array[::String] positional, ::String? input_path) -> bool
|
|
31
|
+
|
|
32
|
+
def validation_error: (::Array[::String] positional, ::String? input_path) -> ::String?
|
|
33
|
+
|
|
34
|
+
def read_input: (::String? input_path) -> ::String
|
|
35
|
+
|
|
36
|
+
def write_output: (::String repaired, ::String? input_path) -> void
|
|
37
|
+
|
|
38
|
+
def replace_in_place: (::String input_path, ::String repaired) -> void
|
|
39
|
+
|
|
40
|
+
def parser: () -> ::OptionParser
|
|
41
|
+
|
|
42
|
+
def define_options: (::OptionParser opts) -> void
|
|
43
|
+
|
|
44
|
+
def halt_with: (::String message) -> void
|
|
14
45
|
end
|
|
15
46
|
end
|
|
16
47
|
end
|
data/sig/json/repair.rbs
CHANGED
data/sig/json/repairer.rbs
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
module JSON
|
|
2
2
|
class Repairer
|
|
3
|
-
|
|
3
|
+
# `untyped` (not `::String`) because the parser idiomatically uses
|
|
4
|
+
# `@json[@index]` past EOF, relying on Ruby returning `nil` as a
|
|
5
|
+
# sentinel. Declaring `@json: ::String` makes steep infer `String?`
|
|
6
|
+
# from `String#[]` and rejects ~15 downstream call sites that pass
|
|
7
|
+
# the result to methods expecting `String`. Tightening would either
|
|
8
|
+
# require pervasive nil-extraction at every indexing site or a
|
|
9
|
+
# private accessor — both of which break parity with the upstream
|
|
10
|
+
# JS source (see CLAUDE.md and `.rubocop.yml` exceptions for
|
|
11
|
+
# `lib/json/repairer.rb`).
|
|
12
|
+
@json: untyped
|
|
4
13
|
|
|
5
14
|
@index: Integer
|
|
6
15
|
|